当前位置:网站首页>Interrupt sharing variables with other functions and protection of critical resources

Interrupt sharing variables with other functions and protection of critical resources

2022-07-01 08:43:00 Craftsman in Jianghu

volatile

volatile Conceptual function

volatile( English translation : Changeable ) Is a feature modifier keyword , Prevent compiler Modify the variable related code Optimize , Read the value of the variable again every time you use , Instead of using the backup in the register .
volatile The literal meaning is not easy to understand , In fact, it reminds the compiler that this variable is mutable , Don't optimize it !

XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;

For external hardware , The above four statements represent different operations , There are four different movements , But the compiler optimizes these four statements , Think that there is only XBYTE[2]=0x58( Ignore the first three statements , Only one machine code is generated ). If you type volatile, Then the compiler will compile one by one and generate the corresponding machine code ( Generate four codes ).

Use... When declaring variables volatile Several cases of embellishment

The following situations need to be declared with volatile Keyword modifies it :

1)、 Hardware registers for parallel devices ( Such as : Status register )
2)、 A non automatic variable that will be accessed in an interrupt service subroutine (Non-automatic variables)
3)、 A variable shared by several tasks in a multithreaded application

Really understand volatile Total importance .

1、 A parameter can be either const It can also be volatile Do you ? Explain why .

answer : Yes .
For example, a read-only status register .
volatile Don't let the compiler optimize it .const Is not to let the program modify it .

2、 A pointer can be volatile Do you ? Explain why .

answer : Yes .
For example, when an interrupt service subroutine modifies a to point to a buffer The pointer of .

3、 The following function is used to calculate the square of an integer , Can it achieve the desired design objectives ? If not , Try to answer any questions :

int square(volatile int *ptr)
{
    
    return ((*ptr) * (*ptr));
}

answer : You can't . This code is intended to return ptr The square of the value pointed to , but *ptr Point to one volatile Type parameter , The compiler will produce code similar to the following :

int square(volatile int* &ptr) // Here the parameter should be declared as a reference , Otherwise, only copies will be used in the function body , External cannot be changed 
{
    
    int a = *ptr;
    int b = *ptr;
    return a*b;
}

because *ptr The value of may change between two fetching statements , therefore a and b It could be different . result , This code may not return the square value you expect ! The correct code is as follows :

int square(volatile int*ptr)
{
    
    int a = *ptr;
    return a*a;
}

volatile Used in the following places :

1)、 The variables modified in interrupt service program for other programs to detect need to add volatile;
2)、 In a multitasking environment, the shared flag among tasks should be marked with volatile;
3)、 Memory mapped Hardware register Usually add volatile explain , Because every time you read and write it, it may have a different meaning ;

In the above cases, the integrity of data should also be considered ( For example, the mark is interrupted and rewritten in the middle of reading ),volatile It's not atomic , Therefore, we should consider the access conflict of critical resources .

1) It can be realized by closing the middle of the line , Or other methods ;
2) Task scheduling can be disabled in , Or other methods ;
3) We can only rely on the good design of hardware .

A critical region 、 Critical resources

A critical region : It refers to a program segment that accesses common resources . Shared resources are called Critical resources .
Critical resources : It cannot be used by multiple threads at the same time ( Such as multitasking , For example, interrupt the service program ) Access features , Only one thread can be used at a time . for example : Shared device or memory . The printer ; Interrupt service program and other programs 、 Interruptions and tasks 、 Task and task shared variables, etc .

Atomic manipulation 、 Atomicity 、 atom

atom : It refers to the basic particles that can not be subdivided in chemical reaction . This is a concept in physical chemistry , The fundamental property of atoms is indivisibility .
Atomicity : The indivisibility of a transaction , All operations of a transaction are either executed without interruption , Or none of them have been implemented .
Atomic manipulation : It refers to a series of operations that cannot be interrupted . It can be a one-step operation , It can also be a multi-step operation . Atomic operations depend on the underlying system CPU Realization . Atomic operation is closely related to the critical region , It can be said that atomic operation is the demand caused by the critical region .

Protection of critical resources

The core of critical resource protection is to Critical resource competition , Let it not be destroyed . In order not to be destroyed , Then let's Critical resources can only be used by one thread at a time . Those who can achieve this effect will immediately think of the switch interruption , Start and stop the dispatcher .
Like in FreeRTO The implementation of critical section is realized by switch interrupt .

function API function explain
Enter the critical area taskENTER_CRITICAL()
taskENTER_CRITICAL_FROM_ISR() Used to interrupt
Exit critical region taskEXIT_CRITICAL()
taskEXIT_CRITICAL_FROM_ISR() Used to interrupt

Protection of critical resources – Atomic manipulation

If you visit = Critical resource itself is an atomic operation ( For example, an instruction can access and complete ), In this way, there is no need to deal with the switch interrupt . Like the early CPU The assembly of an instruction is an atomic operation , But then the instructions changed , such as X86 Block operation of 、SIMD Such instructions are no longer atomic operations . Therefore, even in a single core microprocessor, every assembly instruction cannot be regarded as an atomic operation , The only sure thing is atomic , I'm afraid there are only read and write registers .
stay linux The kernel provides a series of functions to implement atomic operations in the kernel , These functions fall into two categories , Perform atomic operations on bitwise and integer variables respectively . What they have in common is that the operation is atomic in any case , Kernel code can safely call them without interruption . Both bit and integer variable atomic operations depend on the underlying CPU Atomic operation implementation of , So all these functions are related to CPU Is closely related to the architecture of .

Protection of critical resources – lock

stay linux in , The functions to realize file locking are lock and fcntl, among lock Used to apply a suggestive lock to a file , and fcntl You can not only apply a suggestive lock , A forced lock can also be applied . meanwhile ,fcntl It can also lock the Moyi records of documents , That is, the record lock . Record locks are divided into read locks ( Shared lock ) And write lock ( Repulsive lock ).
spinlocks , It's a kind of lock , Spin lock is a kind of lock introduced to prevent multiprocessor concurrency , It is widely used in interrupt processing and other parts of the kernel ( For a single processor , To prevent concurrency in interrupt processing, we can simply turn off the interrupt , You don't need a spin lock ).
Spin locks can only be held by one kernel task at most , If a kernel task is trying to request one that has been contested ( Has been held ) The spin lock of , Then the task will be busy all the time —— rotate —— Wait for the lock to be available again . If it's not disputed , The kernel task that requests it can immediately get it and continue . Spin locking can prevent more than one kernel task from entering the critical region at any time , Therefore, this kind of lock can effectively avoid kernel tasks running concurrently on multiprocessors competing for shared resources . in fact , The original intention of spin lock is : Lightweight locking in the short term . A contested spin lock causes the requesting thread to spin while waiting for the lock to be available again ( A waste of processor time ), So spin locks should not be held for too long . If you need to lock for a long time , It is best to use semaphore .
The basic form of spin lock is as follows :

    spin_lock(&mr_lock);
  // A critical region 
  spin_unlock(&mr_lock);

Because spinlocks can only be held by up to one kernel task at a time , Therefore, only one thread is allowed to exist in the critical zone at a time . This well meets the locking service required by symmetric multiprocessing machines . On a single processor , Spin lock is just a switch to set kernel preemption . If kernel preemption does not exist , Then the spin lock will be completely removed from the kernel at compile time .
To put it simply , Spin lock in the kernel is mainly used to prevent concurrent access to critical areas in multiprocessors , Prevent competition caused by kernel preemption . In addition, spin lock does not allow task sleep ( Task sleep with spin lock will cause self deadlock —— Because sleep may cause kernel tasks holding locks to be rescheduled , And apply for the lock you have already held again ), It can be used in interrupt context .
Deadlock : Suppose there are one or more kernel tasks and one or more resources , Each kernel is waiting for one of these resources , But all the resources have been occupied . This will happen. All kernel tasks are waiting for each other , But they will never release the resources they already possess , So no kernel task can get the required resources , Can't continue to run , This means that a deadlock has occurred . Self - destructiveness means that you own a certain resource , Then I apply for the resources I have possessed , Obviously, it is impossible to obtain this resource again , So it's self binding

The realization of critical resource protection of bare metal single chip microcomputer

The protection of critical resources is liunx、RTOS There are encapsulated in the system API function , There is no single-chip bare metal API, It needs to be handled by users themselves . But the principle and method are the same .

Realization of critical section switch interrupt

(1) The first method : Directly use the statement to open or close interrupts to control .

The process : Close the interrupt –>> Critical resources –>> Open the interrupt

#define INT_DIS() disable_interrupt() // Turn off interrupt 
#define INT_EN() enable_interrupt() // Turn on interrupt 

advantage : Simple , Fast execution ( There's only one command ), It has outstanding advantages in occasions where critical protection operation is frequent .
shortcoming : Critical areas cannot be nested , There are hidden dangers in the nesting of critical areas . If in A The critical code area of the function calls another function B,B There are also critical code areas in functions , from B When function returns , The interrupt is turned on . This will result in A Function subsequent code loses critical protection .

 Example : Prohibit interrupt methods to protect critical resources 
 Great circulation ( backstage )
	INT_DIS() // Disable timed interrupts 
    
	 Access critical resources ;
    
	INT_EN() // Enable allow timed interrupts 
    
 Timer interrupt ( The front desk )
	 Manipulate global variables A;

(2) The second method : Embedded generic approach

Before switching off, push the register where the total interrupt allows the control bit status onto the stack and save it , Then turn off the interrupt protection critical area code , Then decide whether to turn on the interrupt according to the control word saved in the stack . After the critical code is executed , The interrupt permissive state will be restored to the state before entering the critical zone .

(3) The third method :

Save the state of the general interrupt permission control bit into a variable before switching off , Then turn off the interrupt protection critical code , Then decide whether to resume the interrupt according to the saved control word . It can also restore the interrupt allowable state before entering when exiting the critical zone .
The process : The interrupt status is stored in the variable –>> Close the interrupt –>> Global variables A–>> Enable interrupt according to interrupt status

shortcoming : Each critical code segment consumes an additional two bytes of storage space .

void EnterCritical(unsigned int *pSRVal)
{
    
    *pSRVal = _get_SR_register(); // Save interrupt state 
    INT_DIS(); // No interruptions , Enter the critical area . Here we have to consider : What is the state before entering the critical zone , If it is forbidden to interrupt ?
}
 
void ExitCritical(unsigned int * pSRVal)
{
    
    if(*pSRVal & GIE) // Judge the state before entering the critical zone , If it is an enable interrupt state , Then start interrupt .
    {
    
        INT_EN();
    }
}

void Function_A(void)
{
    
    unsigned int GIE_Val;
    ....
    EnterCritical(&GIE_Val); // Enter the critical code area , Save the current interrupt status in GIE_Val variable ;
    ......
    ...... // Critical area code 
    ExitCritical(&GIE_Val); // Exit critical region , And according to GIE_Val The variable determines whether to interrupt ;
    .....
}

(4) The fourth method : Use software to simulate stack behavior .

Count the number of times to enter the critical code and the number of times to exit the critical code , If there is a calling relationship between critical codes , Only interrupt and switch the outermost critical code area .
The process : Similar to the third method , But interrupts only operate on the outermost layer

unsigned char criticalNesting = 0;
unsigned int  interruptStatusValue = 0;

void EnterCritical(void)
{
    
    if(criticalNesting == 0) // Only operate on the outermost layer 
    {
    
        interruptStatusValue = _get_SR_register(); // Save interrupt state 
        INT_DIS(); // No interruptions , Enter the critical area . Here we have to consider : What is the state before entering the critical zone , If it is forbidden to interrupt ?
    }
    criticalNesting++; // Global variables , Is the nested count of critical segments 
}

void ExitCritical(void)
{
    
    criticalNesting--;
    if(criticalNesting == 0) // Only operate on the outermost layer .
    {
    
        if(interruptStatusValue & GIE) // Judge the state before entering the critical zone , If it is an enable interrupt state , Then start interrupt .
        {
    
            INT_EN();
        }
    }
}

Operating critical resource copies

sometimes , If the process of accessing critical resources is relatively long , You can make a copy of a critical resource , Data processed with a copy as a module .
So above ‘ The first method ’ As an example to illustrate the implementation method :

Close the interrupt –>> Accessing global variables A–>> Copy copy a–>> Open the interrupt ->> Operate on replica copies a

lock

If critical resources are more complex , If you use copy, it is also a resource consuming problem , This situation can be solved by making a lock !
Let's say 51 Take the front and back platform program architecture of bare metal kernel as an example : Timers share critical resources with large loops .

 Example : Lock to protect critical resources 
 Great circulation ( backstage )
	ET0 = 0; // Disable timed interrupts 
	Lock = 1;
	ET0 = 1; // Enable allow timed interrupts 

	 Access critical resources ;

	ET0 = 0; // Disable timed interrupts 
	Lock = 0;
	ET0 = 1; // Enable allow timed interrupts 

 Timer interrupt ( The front desk )
	if(lock == 0)
    {
    
         Operating critical resources ;
    }
    else
    {
    
        ;
    }

if Lock = 1; And Lock = 0; The corresponding assembly instruction is an atomic operation, which can protect this lock without switch interrupt

 Example : Lock to protect critical resources 
 Great circulation ( backstage )
	Lock = 1; // If the corresponding assembly instruction of this statement is an atomic operation, the lock can be protected without switch interrupt 
    
	 Access critical resources ;
    
	Lock = 0; // If the corresponding assembly instruction of this statement is an atomic operation, the lock can be protected without switch interrupt 
    
 Timer interrupt ( The front desk )
	if(lock ==0)
    {
    
         Operating critical resources ;
    }
    else
    {
    
        ;
    }

51 MCU realizes mutually exclusive signals to realize critical resource protection

for instance : If A Thread variables a As a temporary storage area , If an interrupt occurs halfway through the operation , This variable is also used in interrupts , When the interrupt returns , Variable a The content in has been destroyed , process A I don't know that , So we get the wrong running result .
The way to avoid seems simple : Take a variable as a flag , by 0 Time means no shared resources are used , by 1 When, it means that . Check the sign before use , by 1 Wait for time , by 0 Time set 1, And use resources , Put the sign back after use 0, So the problem of resource conflict is solved .
Is it? ? The problem shifted ---- When you check the variables , Found that it was 0, I'm going to put it in 1 when , The process is interrupted , Then other processes will still use the resource impolitely , The hell is in multitasking systems , When CPU When control comes back , The process that just used this resource has not used up this resource , So something went wrong .
You can see it , There must be one such instruction : First check a variable , When it's for 0 Time set 1 And jump , And this series of processes should not be interrupted , Instructions with this ability are called " Atomic operation instructions ".
because 51 There is no such directive , That's why many people upstairs say they want to shut down and interrupt . in fact , This method of switching off is also used in our desktop system ! LINUX A lot of off interrupts are used .
although 51 There is no such instruction as above , But there is another instruction :DJNZ— Subtract first and then check , And jump to... According to the comparison results , And this is enough to complete the critical zone operation . To initialize the system, you must set s Set the initial value as 1.

mutex_loop:
        DJNZ s, mutex_wait ; First the s reduce 1, To determine s Is it 0, Not for 0 Jump to the mutex_wait To execute , Otherwise, execute in sequence .
        .... Shared resource operation code ( That's the critical area )
        INC s
        RET
mutex_wait:
        INC s
        JMP mutex_loop

The above code is in any case ( Flying doesn't count , ha-ha ) It will not happen that two processes enter the critical zone at the same time .
You must think of , If the above code is written as a ⼦ Program , Then it can be used as a standard function for C Called , congratulations , This is the standard semaphore .

summary

1、volatile Interrupting global variables only ensures visibility , There is no guarantee of atomicity . Always understand that interrupt global variables are critical resources , Shared access needs protection .
2、 When accessing critical resources, you should pay attention to the problem of access nesting .

原网站

版权声明
本文为[Craftsman in Jianghu]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/182/202207010831454253.html