Happy year of the tiger! I wish you greater progress next year!
Reference content: Chapter 10 of [wildfire] uCOS-III Kernel Implementation and application development Practical Guide - based on STM32.
1 critical section
Critical section, also known as critical zone. For multithreading, it is an inseparable and context switchable code. For uCOS, it is a piece of code that cannot be interrupted. The critical segment cannot be interrupted. It is necessary to turn off the interrupt or lock scheduler (OSSched) to protect the critical segment.
What happens when a code segment is broken? As can be seen from the description just now, there are two situations when the critical section is interrupted:
- External interrupt.
- System scheduling. PendSV abnormal interrupt will be generated in system scheduling, which can also be attributed to interrupt.
Therefore, the essence of critical section protection is to control the opening and closing of interruption.
uCOS defines the macro for entering the critical section and the macro for exiting the critical section. These two macros realize the opening and closing of interrupts respectively.
- OS_CRITICAL_ENTER() or CPU_CRITICAL_ENTER()
- OS_CRITICAL_EXIT() or CPU_CRITICAL_EXIT()
There is also a macro to store the status of the interrupt:
- CPU_SR_ALLOC()
2 protection of critical section
2.1 interrupt instruction of Cortex-M kernel
In CM kernel, interrupt is controlled by CPS instruction.
CPSID I ;PRIMASK=1,Off interrupt CPSIE I ;PRIMASK=0,Open interrupt CPSID F ;FAULTMASK=1,Guan anomaly CPSIE F ;FAULTMASK=0,Abnormal opening
PRIMASK and faultmaster are two of the three interrupt mask registers in CM kernel. The other one is BASEPRI. If the last one is not used, it will not be introduced.
- PRIMASK: a register with only one bit. After it is set to 1, all maskable exceptions are turned off, leaving only NMI (non maskable interrupt) and hardware FAULT (hard FAULT) to respond. Its default value is 0, indicating that there is no shutdown interrupt.
- FAULTMASK: a register with only 1 bit. When it is set to 1, only NMI (non maskable interrupt) can respond, and all other exceptions, even hardware FAULT (hard FAULT), also shut up. Its default value is 0, indicating that there is no off exception.
Therefore, you can also modify PRIMASK (or FAULTMASK) through MSR instruction to turn on or off interrupt:
MOVS R0, #1 MSR PRIMASK, R0 ; Write 1 to PRIMASK Disable all interrupts MOVS R0, #0 MSR PRIMASK, R0 ; Write 0 to PRIMASK Enable interrupt
2.2 on interrupt and off interrupt
2.2.1 turn off interrupt CPU_SR_Save()(cpu_a.asm)
What this function does:
- Storage interrupt status: store the value of the special register PRIMASK register into the general register R0 through the MRS instruction. (when calling the assembly subroutine back in C, R0 is used as the return value of the function, so when CPU_SR_Save() is called in C, a variable must be declared in advance to store CPU_. SR_ The return value of save(), that is, the value of R0 register, that is, the value of PRIMASK.)
- Turn off interrupt: use CPS instruction to set the value of PRIMASK register to 1.
- The subroutine returns.
; CPU_SR CPU_SR_Save (void); (Critical section shutdown interruption, R0 (return value) CPU_SR_Save MRS R0, PRIMASK ; take PRIMASK The value of the register is stored in R0 in CPSID I ; Off interrupt BX LR
2.2.2 interrupt CPU_SR_Restore()(cpu_a.asm)
What this function does:
- Restore interrupt state: store the value of general register R0 into special register primask through MSR instruction. (when calling the assembly subroutine back in C, the first parameter is passed to the general register R0. Therefore, when calling CPU_SR_Restore() in C, a parameter is required to be passed, which is the value of PRIMASK saved before entering the critical section.
- The subroutine returns.
; void CPU_SR_Restore (CPU_SR cpu_sr); (Critical section interruption, R0 (as formal parameter) CPU_SR_Restore MSR PRIMASK, R0 ; take R0 Value stored in PRIMASK Register BX LR
Why not use CPS instruction directly when opening and interrupting? You will understand later in the application section (section 2.3.2).
2.2.3 macro definition encapsulation (cpu.h)
Finally, in CPU H, the functions of on interrupt and off interrupt are encapsulated into a macro for easy calling.
/*********************************CPU Register data type definition*********************************/ typedef volatile CPU_INT32U CPU_REG32; typedef CPU_REG32 CPU_SR; /*********************************Definition of critical section*********************************/ #define CPU_SR_ALLOC() CPU_ SR cpu_sr = (CPU_SR)0 / / used to store interrupt status #define CPU_INT_DIS() do { cpu_sr = CPU_SR_Save(); } while(0) / / close the interrupt and store the interrupt status #define CPU_INT_EN() do { CPU_SR_Restore(cpu_sr); } while(0) / / restore the interrupt state #define CPU_CRITICAL_ENTER() do { CPU_INT_DIS(); } while(0) #define CPU_CRITICAL_EXIT() do { CPU_INT_EN(); } while(0) /*********************************Function declaration (cpu_a.asm)*********************************/ void CPU_IntDis (void); void CPU_IntEn (void); CPU_SR CPU_SR_Save (void); void CPU_SR_Restore (CPU_SR cpu_sr);
2.3 Application of critical section protection
2.3.1 application of critical section of the first floor
If there is such a critical segment code:
/* Critical segment code protection */ { /* Start of critical section */ { /* Execute critical segment code without interruption */ } /* End of critical section */ }
Then the format defined by the above macro is:
/* Critical segment code protection */ { CPU_SR_ALLOC(); /* cpu_sr = 0 */ CPU_INT_DIS(); /* Off interrupt */ /* Start of critical section */ { /* Execute critical segment code without interruption */ } /* End of critical section */ CPU_INT_EN(); /* Open interrupt */ }
If you expand it, it becomes:
/* Critical segment code protection */ { CPU_SR cpu_sr = (CPU_SR)0; /* (a) Define a variable to store the interrupt state and initialize the cpu_sr = 0 */ cpu_sr = CPU_SR_Save(); /* (b) cpu_sr Save the current interrupt status and turn off the interrupt */ /* Start of critical section */ { /* Execute critical segment code without interruption */ } /* End of critical section */ CPU_SR_Restore(cpu_sr); /* (c) cpu_sr Write interrupt status to restore the previous interrupt status */ }
The process is as follows:
- (a) Before the critical section starts, define a variable cpu_sr, used to store the value of PRIMASK, that is, used to save the interrupt state.
- (b) First, store the value of the current interrupt state (i.e. PRIMASK) in the cpu_sr. Because the interrupt is not closed at present, PRIMASK = 0, cpu_sr = 0. Then turn off the interrupt so that PRIMASK = 1.
- (c) After executing the critical section of code, restore the previous interrupt state and turn the cpu_sr writes to PRIMASK. Current PRIMASK = 1, cpu_sr = 0. After writing, PRIMASK = 0. Restore the previous interrupt state.
2.3.2 application of multi-layer critical section
If it is a nesting of two layers of critical segment code:
/* Critical segment code protection */ { /* Start of critical section 1 */ { /* Start of critical section 2 */ { /* Execute critical segment code without interruption */ } /* End of critical section 2 */ } /* End of critical segment 1 */ }
Before entering critical section 1, it is necessary to close the interrupt; Before entering critical segment 2, it is also necessary to close the interrupt, so the format defined by the macro is:
/* Critical segment code protection */ { CPU_SR_ALLOC(); /* cpu_sr = 0 */ CPU_INT_DIS(); /* Off interrupt (Critical Section 1) */ /* Start of critical section 1 */ { CPU_SR_ALLOC(); /* cpu_sr = 0 */ CPU_INT_DIS(); /* Off interrupt (critical section 2) */ /* Start of critical section 2 */ { /* Execute critical segment code without interruption */ } /* End of critical section 2 */ CPU_INT_EN(); /* On / off (critical section 2) */ } /* End of critical segment 1 */ CPU_INT_EN(); /* On / off (Critical Section 1) */ }
Expand the macro definition, and the code can be equivalent to:
/* Critical segment code protection */ { CPU_SR cpu_sr1 = (CPU_SR)0; /* (a) Define a variable to store the interrupt state and initialize the cpu_sr1 = 0 */ cpu_sr1 = CPU_SR_Save(); /* (b) cpu_sr1 Save the current interrupt status and turn off the interrupt */ /* Start of critical section 1 */ { CPU_SR cpu_sr2 = (CPU_SR)0; /* (c) Define a variable to store the interrupt state and initialize the cpu_sr2 = 0 */ cpu_sr2 = CPU_SR_Save(); /* (d) cpu_sr2 Save the current interrupt status and turn off the interrupt */ /* Start of critical section 2 */ { /* Execute critical segment code without interruption */ } /* End of critical section 2 */ CPU_SR_Restore(cpu_sr2); /* (e) cpu_sr2 Write interrupt status to restore the previous interrupt status */ } /* End of critical segment 1 */ CPU_SR_Restore(cpu_sr1); /* (f) cpu_sr1 Write interrupt status to restore the previous interrupt status */ }
The process is as follows:
- (a) Before the start of critical segment 1, define a variable cpu_sr1 is used to store interrupt status.
- (b) First, store the value of the current interrupt state (i.e. PRIMASK) in the cpu_sr1. Because the interrupt is not closed at present, PRIMASK = 0, cpu_sr1 = 0. Then turn off the interrupt so that PRIMASK = 1.
- (c) Before the start of critical section 2, another variable CPU is defined_ Sr2 is used to store interrupt status.
- (d) First, store the value of the current interrupt state (i.e. PRIMASK) in the cpu_sr2. Because the interrupt has been turned off, PRIMASK = 1, cpu_sr2 = 1. Then close the interrupt to make PRIMASK = 1 (in fact, PRIMASK has been 1 before).
- (e) After executing critical segment 2, restore the previous interrupt state and turn the cpu_sr2 writes to PRIMASK. Current PRIMASK = 1, cpu_sr2 = 1. After writing, PRIMASK = 1. Restore the previous interrupt state, that is, it is still in the off state. (this step is the key to understand why the CPS instruction is not directly used for interrupt opening. If CPS is used to directly open the interrupt, it will appear: when returning to critical segment 1, it is found that the interrupt is actually on!)
- (f) After executing critical segment 1, restore the previous interrupt state and turn the cpu_sr1 is written to PRIMASK. Current PRIMASK = 1, cpu_sr1 = 0. After writing, PRIMASK = 0. Restore the previous interrupt state, that is, the interrupt state.
3. Measure off interruption time
Ignore this part first, because it is not the part I focus on. Study it later.