临界区

RTOS(实时操作系统)中,临界区(Critical Section)是指一段需要以原子性方式执行的代码。这意味着在执行临界区代码时,不允许其他任务或中断干扰,以确保数据一致性和操作的正确性。

临界区的必要性

在 RTOS 中,任务是并发运行的,尤其是在多任务环境中,可能会发生以下情况:

  1. 任务切换(上下文切换):在访问共享资源时,另一个任务可能会抢占当前任务。
  2. 中断触发:中断服务程序可能会在任务执行时访问共享资源。
  3. 数据竞争问题:多个任务或中断同时访问同一资源时,可能会引发数据损坏或错误操作。

临界区的作用就是防止以上问题,通过保护共享资源或关键代码段,确保并发环境下的正确性。

定义临界区

临界区通常是指一段需要访问共享资源(如全局变量、硬件寄存器、内存等)的代码。以下是一些典型的例子:

  • 修改全局变量。
  • 操作队列、信号量、互斥锁等共享资源。
  • 访问外部硬件设备寄存器。
1
2
3
4
5
6
7
8
int shared_resource = 0;
void update_shared_resource(void) {
// 临界区开始
taskENTER_CRITICAL();
shared_resource++; // 对共享资源的修改
taskEXIT_CRITICAL();
// 临界区结束
}

如何实现临界区

在 RTOS 中,实现临界区主要在两个方面:

禁止任务间切换

在临界区代码执行期间,暂停任务调度,防止任务之间的切换。这种方法适用于不涉及中断的代码。

  • 在 FreeRTOS 中使用:
    • taskENTER_CRITICAL() 和 taskEXIT_CRITICAL(): 禁止调度器,防止任务切换,保护临界区代码。
1
2
3
taskENTER_CRITICAL();
// 临界区代码
taskEXIT_CRITICAL();

如果所使用的 FreeRTOS 移植未使用 configMAX_SYSCALL_INTERRUPT_PRIORITY 内核配置常量 (也称为 configMAX_API_CALL_INTERRUPT_PRIORITY),则调用 taskENTER_CRITICAL() 会全局禁用中断。如果所使用的 FreeRTOS 移植使用了 configMAX_SYSCALL_INTERRUPT_PRIORITY 内核配置常量,则调用 taskENTER_CRITICAL() 会禁用优先级等于或低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置的优先级的中断, 并启用所有高于此优先级的中断。

禁止中断

在临界区代码执行期间,关闭中断,防止中断访问共享资源。

  • 在 FreeRTOS 中使用:
    • taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR(): 完全禁用中断,这种方法适合保护非常短的关键代码段,但会导致系统延迟处理其他中断。
1
2
3
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
// 临界区代码
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );

临界区必须尽量简短,否则会对本来可以嵌套的 高优先级中断的响应时间产生不利影响。 每次调用 taskENTER_CRITICAL_FROM_ISR() 时, 都必须有对应的 taskEXIT_CRITICAL_FROM_ISR() 调用。

不得从临界区调用 FreeRTOS API 函数。

如果使用的 FreeRTOS 移植支持中断嵌套,则调用 taskENTER_CRITICAL_FROM_ISR() 将禁用优先级等于或低于由configMAX_SYSCALL_INTERRUPT_PRIORITY(或 configMAX_API_CALL_INTERRUPT_PRIORITY)内核配置常量设置的优先级的中断, 同时启用所有其他高于此优先级的中断。 如果使用的 FreeRTOS 移植不支持中断嵌套,则 taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR() 将不起作用。