什么是临界段?

就是一段在执行的时候不能被中断的代码段。

那么什么情况下临界段会被打断?


系统调度 在FreeRTOS,系统调度,最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切
换,所以还是可以归结为中断。
既然这样,FreeRTOS 对临界段的保护最终还是回到对中断的开和关的控制。
外部中断

Cortex-M 内核快速关中断指令

  1. CPSID I ;PRIMASK=1 ;关中断
  2. CPSIE I ;PRIMASK=0 ;开中断
  3. CPSID F ;FAULTMASK=1 ;关异常
  4. CPSIE F ;FAULTMASK=0 ;开异常
名字 功能描述
PRIMASK 这是个只有单一比特的寄存器。在它被置1后,就关掉所有可屏蔽的异常, 只剩下NMI和硬FAULT可以响应。
它的缺省值是0,表示没有关中断。
FAULTMASK 这是个只有1个位的寄存器。当它置1时,只有NMI才能响应,所有其它的 异常,甚至是硬FAULT,也通通闭嘴。
它的缺省值也是0,表示没有关异 常。
BASEPRI 这个寄存器最多有9位(由表达优先级的位数决定)。它定义了被屏蔽优先 级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关 (优先级号越大,优先级越低)。但若被设成0,则不关闭任何中断,0也是 缺省值。

关中断

不带返回值__关中断函数

  1. /* 不带返回值的关中断函数,不能嵌套,不能在中断里面使用 */ (1)
  2. /*在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来,
  3. **即不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,
  4. **也就意味着这样的函数不能在中断里面调用。*/
  5. #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
  6. void vPortRaiseBASEPRI( void )
  7. {
  8. uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; (1)-①
  9. /*****configMAX_SYSCALL_INTERRUPT_PRIORITY 是 一 个 在FreeRTOSConfig.h 中定义的宏,
  10. ******即要写入到 BASEPRI 寄存器的值。该宏默认定义为 191,高四位有效,即等于 0xb0,或者是 11,
  11. ******即优先级大于等于 11 的中断都会被屏蔽,11 以内的中断则不受 FreeRTOS 管理
  12. *****/
  13. __asm
  14. {
  15. msr basepri, ulNewBASEPRI (1)-②
  16. //将 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值写入BASEPRI 寄存器,实现关中断
  17. dsb
  18. isb
  19. }
  20. }

带返回值__关中断函数

  1. /* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */ (2)
  2. #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
  3. /**************************/
  4. /*不带返回值的关中断函数,不能嵌套,不能在中断里面使用。*/
  5. /**************************/
  6. ulPortRaiseBASEPRI( void )
  7. {
  8. uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; (2)-①
  9. __asm
  10. {
  11. mrs ulReturn, basepri (2)-②
  12. /**************************/
  13. /*保存 BASEPRI 的值,记录当前哪些中断被关闭。*/
  14. /**************************/
  15. msr basepri, ulNewBASEPRI (2)-③
  16. /**************************/
  17. /*更新 BASEPRI 的值。*/
  18. /**************************/
  19. dsb
  20. isb
  21. }
  22. return ulReturn; (2)-④ //返回原来 BASEPRI 的值。
  23. }

开中断

  1. /* 不带中断保护的开中断函数 */
  2. #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
  3. //不带中断保护的开中断函数,直接将 BASEPRI 的值设置为 0,与portDISABLE_INTERRUPTS()成对使用
  4. /* 带中断保护的开中断函数 */
  5. #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
  6. //带中断保护的开中断函数,将上一次关中断时保存的 BASEPRI 的值作为形参 ,与 portSET_INTERRUPT_MASK_FROM_ISR()成对使用
  7. void vPortSetBASEPRI( uint32_t ulBASEPRI ) //开中断函数,具体是将传进来的形参更新到 BASEPRI 寄存器。
  8. {
  9. __asm
  10. {
  11. msr basepri, ulBASEPRI
  12. }
  13. }

进入、退出临界段

  1. #define taskENTER_CRITICAL() portENTER_CRITICAL()
  2. #define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
  3. #define taskEXIT_CRITICAL() portEXIT_CRITICAL()
  4. #define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

进入

  1. /* 在 port.c 中定义 */
  2. void vPortEnterCritical( void )
  3. {
  4. portDISABLE_INTERRUPTS();
  5. uxCriticalNesting++; // 静态变量,表示临界段嵌套计数器。默认初始化为 0xaaaaaaaa , 在调度器动时会被重新初始化为 0
  6. if ( uxCriticalNesting == 1 )
  7. //如果 uxCriticalNesting 等于 1,即一层嵌套,要确保当前没有中断活跃,
  8. //即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0。
  9. {
  10. configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
  11. }
  12. }
  13. /* 在 portmacro.h 中定义 */
  14. #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
  15. /* 在 portmacro.h 中定义 */
  16. static portFORCE_INLINE void vPortRaiseBASEPRI( void )
  17. {
  18. uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  19. __asm
  20. {
  21. msr basepri, ulNewBASEPRI
  22. dsb
  23. isb
  24. }
  25. }
  1. #define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
  2. /* 在 portmacro.h 中定义 */
  3. #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
  4. /* 在 portmacro.h 中定义 */
  5. static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
  6. {
  7. uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  8. __asm
  9. {
  10. mrs ulReturn, basepri
  11. msr basepri, ulNewBASEPRI
  12. dsb
  13. isb
  14. }
  15. return ulReturn;
  16. }

退出