5.1 什么是临界段

临界段用一句话概括就是一段在执行的时候不能被中断的代码段。
通常是全局变量
不能同时发生更改
中断可以打断临界段、因此对临界段的保护就是对中断的快和关的控制。

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

为了快速地开关中断, Cortex-M 内核专门设置了一条 CPS 指令,有 4 种用法

  1. CPSID I ;PRIMASK=1 ;关中断
  2. CPSIE I ;PRIMASK=0 ;开中断
  3. CPSID F ;FAULTMASK=1 ;关异常
  4. CPSIE F ;FAULTMASK=0 ;开异常

PRIMASK 和 FAULTMAST 是 Cortex-M 内核 里面三个中断屏蔽寄存器中的两个,还有一个是 BASEPRI。

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

但是, 在 FreeRTOS 中,对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽,不受FreeRTOS 管理。用户可以设置 BASEPRI 的值来选择性的给一些非常紧急的中断留一条后路。

5.3 关中断

FreeRTOS 关中断的函数在 portmacro.h 中定义, 分不带返回值和带返回值两种。
嵌套:指的是在函数调用的时候,里面再调用相关的函数

  1. /* 不带返回值的关中断函数,不能嵌套,不能在中断里面使用 */
  2. #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
  3. void vPortRaiseBASEPRI( void )
  4. {
  5. uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  6. __asm
  7. {
  8. /* Set BASEPRI to the max syscall priority to effect a critical
  9. section. */
  10. msr basepri, ulNewBASEPRI
  11. dsb
  12. isb
  13. }
  14. }
  15. /* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */
  16. #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
  17. uint32_t ulPortRaiseBASEPRI( void )
  18. {
  19. uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  20. __asm
  21. {
  22. /* Set BASEPRI to the max syscall priority to effect a critical
  23. section. */
  24. mrs ulReturn, basepri
  25. msr basepri, ulNewBASEPRI
  26. dsb
  27. isb
  28. }
  29. return ulReturn;
  30. }

5.3.1 不带返回值的关中断函数

  1. 不带返回值的关中断函数,不能嵌套,不能在中断里面使用。不带返回值的意思是:在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来,即不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用。

5.3.2 带返回值的关中断函数

  1. 带返回值的关中断函数,可以嵌套,可以在中断里面使用。 带返回值的意思是:在往 BASEPRI 写入新的值的时候,先将 BASEPRI 的值保存起来,在更新完BASEPRI 的值的时候,将之前保存好的 BASEPRI 的值返回,返回的值作为形参传入开中断函数。

5.4 开中断

FreeRTOS 开中断的函数在 portmacro.h 中定义

  1. /* 不带中断保护的开中断函数 */
  2. #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
  3. /* 带中断保护的开中断函数 */
  4. #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
  5. void vPortSetBASEPRI( uint32_t ulBASEPRI )
  6. {
  7. __asm
  8. {
  9. msr basepri, ulBASEPRI
  10. }
  11. }

5.5 进入/退出临界区的宏

  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 )

进入和退出临界段的宏分中断保护版本和非中断版本, 但最终都是通过开/关中断来实现。有关开/光中断的底层代码我们已经讲解,那么接下来的退出和进入临界段的代码配套注释来理解即可。

5.5.1 不带中断保护版本、不能嵌套

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

5.5.2 带中断保护版本,可以嵌套

  1. /* ==========进入临界段,带中断保护版本,可以嵌套=============== */
  2. /* 在 task.h 中定义 */
  3. define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
  4. /* 在 portmacro.h 中定义 */
  5. #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
  6. /* 在 portmacro.h 中定义 */
  7. static inline uint32_t ulPortRaiseBASEPRI( void )
  8. {
  9. uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  10. __asm
  11. {
  12. /* Set BASEPRI to the max syscall priority to effect a critical
  13. section. */
  14. mrs ulReturn, basepri
  15. msr basepri, ulNewBASEPRI
  16. dsb
  17. isb
  18. }
  19. return ulReturn;
  20. }

5.5.3 总结

  1. // 临界区相关 关中断相关
  2. // 这两个函数是port.c中的
  3. extern void vPortEnterCritical( void ); // 这个调用portDISABLE_INTERRUPTS
  4. extern void vPortExitCritical( void ); // 这个调用portENABLE_INTERRUPTS
  5. #define portENTER_CRITICAL() vPortEnterCritical()
  6. #define portEXIT_CRITICAL() vPortExitCritical()
  7. #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() // 在port.c中被调用
  8. static inline void vPortRaiseBASEPRI( void )
  9. {
  10. uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  11. __asm
  12. {
  13. /* Set BASEPRI to the max syscall priority to effect a critical
  14. section. */
  15. msr basepri, ulNewBASEPRI
  16. dsb
  17. isb
  18. }
  19. }
  20. #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
  21. static inline uint32_t ulPortRaiseBASEPRI( void )
  22. {
  23. uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  24. __asm
  25. {
  26. /* Set BASEPRI to the max syscall priority to effect a critical
  27. section. */
  28. mrs ulReturn, basepri
  29. msr basepri, ulNewBASEPRI
  30. dsb
  31. isb
  32. }
  33. return ulReturn;
  34. }
  35. /* 不带中断保护的开中断函数 */
  36. #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
  37. /* 带中断保护的开中断函数 */
  38. #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
  39. static inline void vPortSetBASEPRI( uint32_t ulBASEPRI )
  40. {
  41. __asm
  42. {
  43. msr basepri, ulBASEPRI
  44. }
  45. }
  1. void vPortEnterCritical( void )
  2. {
  3. portDISABLE_INTERRUPTS();
  4. uxCriticalNesting++;
  5. if ( uxCriticalNesting == 1 )
  6. {
  7. //configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 )
  8. }
  9. }
  10. void vPortExitCritical( void )
  11. {
  12. //configASSERT( uxCriticalNesting );
  13. uxCriticalNesting--;
  14. if( uxCriticalNesting == 0 )
  15. {
  16. portENABLE_INTERRUPTS();
  17. }
  18. }
  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 )

5.6 应用

在 FreeRTOS 中,对临界段的保护出现在两种场合,一种是在中断场合一种是在非中断场合,具体的应用见

  1. /* 在中断场合,临界段可以嵌套 */
  2. {
  3. uint32_t ulReturn;
  4. /* 进入临界段,临界段可以嵌套 */
  5. ulReturn = taskENTER_CRITICAL_FROM_ISR();
  6. /* 临界段代码 */
  7. /* 退出临界段 */
  8. taskEXIT_CRITICAL_FROM_ISR( ulReturn );
  9. }
  1. /* 在非中断场合,临界段不能嵌套 */
  2. {
  3. /* 进入临界段 */
  4. taskENTER_CRITICAL();
  5. /* 临界段代码 */
  6. /* 退出临界段*/
  7. taskEXIT_CRITICAL();
  8. }