临界区代码保护
临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。对于临界区,FreeRTOS使用taskENTER_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 、 taskEXIT_CRITICAL() 、
taskEXIT_CRITICAL_FROM_ISR(x)四个宏来保护临界区。这些宏通常会通过修改BASEPRI寄存器来禁用中断,从而确保在临界区执行时,不会有更高优先级的中断打断。
/* 进入临界区 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
/* 中断中进入临界区 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/* 退出临界区 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()
/* 中断中退出临界区 */
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
- 任务级:在任务级进入临界区时,会关闭中断,确保在执行期间不会进行任务切换。该临界区是可以嵌套的,即在程序中可以重复地进入临界区,只要后续重复退出相同次数的临界区即可。
- 中断级:在中断级进入临界区时,同 样 是 将 BASEPRI 寄 存 器 设 置 为 宏configMAX_SYSCALL_INTERRUPT_PRIORITY 的值,以达到关闭中断的效果,但与从任务进入临界区不同的是,从中断进入临界区时不支持嵌套。
使用格式示例:
taskENTER_CRITICAL() ;
{
… … /* 临界区 */
}
taskEXIT_CRITICAL() ;
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
{
… … /* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );
注意:
- 确保每次调用 进入临界区的函数必须配对退出临界区的函数,以避免中断状态不正确的情况。
- 若taskENTER_CRITICAL()是用于从非中断中进入临界区,不能在中断服务函数中调用函数taskENTER_CRITICAL(),否则就会通过断言报错。
- 尽量缩短临界区的执行时间,避免长时间禁止任务调度,这样可以减少对系统响应性的影响。
- 中断级进入临界区时不支持嵌套调用,注意保持中断的状态一致性,避免由于多次禁用中断而导致的状态混乱。
调度器挂起与恢复
在执行临界区代码时,FreeRTOS还需要管理任务调度器的状态。调用taskENTER_CRITICAL()时,FreeRTOS会暂时挂起任务调度,这意味着在临界区内不会进行任务切换,以避免因任务调度导致的数据不一致。一旦临界区代码执行完毕,调用taskEXIT_CRITICAL()恢复调度器。这时,调度器会检查是否有高优先级任务需要运行,从而确保系统能在完成临界区的操作后,及时响应其他任务。
函数 | 描述 |
---|---|
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器 |
使用格式示例:
vTaskSuspendAll() ;
{
… … /* 内容 */
}
xTaskResumeAll() ;
特点:
- 与临界区不同的是,挂起任务调度器不关闭中断,仅防止任务之间的资源争夺,中断依旧可以响应。
- 挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全。
注意:
- 尽量缩短挂起时间:挂起调度器的时间应尽量短,以免影响系统的实时性能和响应能力。
- 嵌套调用:可以多次调用 vTaskSuspendAll(),但需要确保每次都对应调用 xTaskResumeAll() 来恢复调度。
- 避免死锁:确保在挂起调度器时,不会导致其他任务或中断依赖于当前执行的任务,从而引发死锁。