ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FreeRTOS临界区:关中断还是锁调度器

FreeRTOS临界区:关中断还是锁调度器 写多任务代码迟早会遇到这段困惑g_counter 1; /* 这行会被别的任务/中断打断吗需要保护吗 */如果会被打断且可能导致数据错乱你就要用临界区把它保护起来。但 FreeRTOS 给了两种保护手段taskENTER_CRITICAL(); /* 关中断 */ /* ... */ taskEXIT_CRITICAL(); vTaskSuspendAll(); /* 锁调度器 */ /* ... */ xTaskResumeAll();新手常困惑这两个到底区别在哪该用哪个为什么网上教程说法不一一句话区分关中断是谁都不许打断我连中断都不行锁调度器是任务不许切换但中断可以来。两者保护的敌人不同代价也不同。这篇把临界区的两种实现讲透给出明确的选用规则。这是后续第三系列同步通信的基础——理解临界区才能理解为什么需要互斥量、信号量。一、先看为什么需要临界区竞态条件先理解为什么g_counter 1可能出问题。在 C 语言里它看起来是一行但在机器指令层面是三步g_counter 1; 实际展开为 1. 从内存读 g_counter 到寄存器 (LDR) 2. 寄存器 1 (ADD) 3. 把寄存器写回内存 (STR)如果任务 A 执行完第 2 步还没写回被任务 B 抢占B 也读 g_counter旧值、加 1、写回。然后 A 恢复把它的 1 结果写回——B 的更新被覆盖了。初始 g_counter 5 A 读到 5 A 计算 516还没写回 ── 被抢占 ── B 读到 5仍是旧值 B 计算 516 B 写回 6 ── A 恢复 ── A 写回 6 最终 g_counter 6本该是 7这就是竞态条件Race Condition结果取决于任务切换的时机不可预测。竞态的触发条件是共享变量的访问不是原子的可被中途打断。临界区的作用就是让这段访问变成不可打断的原子操作。二、两种临界区机制机制 1关中断taskENTER/EXIT_CRITICALtaskENTER_CRITICAL(); /* 关闭可屏蔽中断低于阈值的中断 */ g_counter 1; /* 这段不会被任何中断或任务打断 */ taskEXIT_CRITICAL(); /* 恢复中断 */行为把configMAX_SYSCALL_INTERRUPT_PRIORITY以下的所有中断都关掉。期间• 没有任何中断能触发• 没有任何任务切换因为切换靠中断驱动• 这段代码绝对独占 CPU。保护范围同时防任务和中断打断。最强的保护。机制 2锁调度器vTaskSuspendAll / xTaskResumeAllvTaskSuspendAll(); /* 暂停任务调度 */ g_counter 1; /* 不会被别的任务打断 */ xTaskResumeAll(); /* 恢复调度 */行为禁止任务切换调度器不工作但中断仍然开启。期间• 中断照常发生、照常执行• 但中断里即使唤醒了更高优先级任务也不会立即切换要等 ResumeAll• 这段代码不会被别的任务打断但会被中断打断。保护范围只防任务打断不防中断。较弱的保护。三、核心区别对比关中断taskENTER_CRITICAL锁调度器vTaskSuspendAll防任务切换是是防中断是否中断照常实时性影响大中断延迟小中断不受影响适用场景任务和中断都可能访问的共享数据只在任务间共享的数据能否嵌套能计数式能计数式能否调用阻塞 API否会死锁/断言否会断言最关键的区别关中断会延迟所有中断响应包括串口接收、定时器等影响系统实时性锁调度器不影响中断只影响任务调度。四、选用规则三步决策规则 1共享数据有没有被中断访问/* 情况 A数据只在任务间共享中断不碰 */ static uint32_t g_task_counter; /* 只有任务读写 */ /* 情况 B数据被中断和任务共同访问 */ static volatile uint32_t g_isr_counter; void TIM2_IRQHandler(void) { g_isr_counter; } /* 中断里改 */ void SomeTask(void) { g_isr_counter 0; } /* 任务里改 */• 情况 A锁调度器就够只防任务• 情况 B必须关中断要同时防中断。规则 2临界区里会不会调用阻塞 API两种机制都不能在临界区内调用阻塞 APIosDelay、osMessageQueueGet 带超时等• 关中断后阻塞 → 整个系统死锁中断关了tick 不走永远等不到• 锁调度器后阻塞 → FreeRTOS 断言失败configASSERT 触发。如果需要带等待的资源访问不要用临界区用互斥量第 28 篇讲。临界区只适合极短的、不需等待的操作。规则 3临界区多长临界区越长对实时性影响越大。经验上限机制建议最大时长关中断几十微秒绝对不要超过 100μs锁调度器几百微秒到几毫秒不影响中断宽容度高超过这些时长就应该改用互斥量可以阻塞等待不占 CPU。五、代码示例两种机制的正确用法示例 1保护任务中断共享的计数器关中断/** * file critical_demo.c * brief 临界区使用示例 * date 2026-07-15 * version V1.0 - 初版创建 */ #include cmsis_os2.h #include task.h #include stdio.h static volatile uint32_t g_shared_counter 0U; /** * brief 模拟中断里修改共享变量 * note 实际工程在 ISR 里调这里用函数模拟 */ void SimulatedIsr_Modify(void) { g_shared_counter; } /** * brief 任务里安全地读改共享变量关中断法 * details 因为中断也会改 g_shared_counter必须关中断保护 * 否则读-改-写之间可能被中断插入。 * note 临界区极短几条指令关中断开销可接受 */ static void Task_ReadModifySafe(void *argument) { (void)argument; for (;;) { uint32_t local_copy; taskENTER_CRITICAL(); /* 关中断 */ local_copy g_shared_counter; /* 读 */ local_copy 100U; /* 改在局部变量上 */ g_shared_counter local_copy; /* 写 */ taskEXIT_CRITICAL(); /* 开中断 */ osDelay(100); } }示例 2保护纯任务间的链表操作锁调度器/** * brief 任务间共享的链表用锁调度器保护 * details 链表操作涉及多步指针修改不能被别的任务打断。 * 但中断不碰这个链表所以锁调度器就够不必关中断。 * note 链表操作可能耗时几十微秒关中断太久会影响实时性 * 锁调度器更合适 */ static void Task_ListAppendSafe(Node_t *new_node) { vTaskSuspendAll(); /* 锁调度器 */ List_Append(g_shared_list, new_node); /* 多步指针操作 */ (void)xTaskResumeAll(); /* 恢复 */ }示例 3嵌套是安全的两种机制都支持嵌套内部用计数器记录深度taskENTER_CRITICAL(); /* count1 */ taskENTER_CRITICAL(); /* count2中断仍关 */ /* ... */ taskEXIT_CRITICAL(); /* count1中断仍关 */ taskEXIT_CRITICAL(); /* count0恢复中断 */所以你可以在已关中断的函数里调用另一个也关中断的函数不会提前开中断。六、CMSIS-RTOS v2 的封装CMSIS-RTOS v2 提供了对应封装但本质上还是调原生 API/* v2 的临界区接口 */ void osKernelLock(void); /* 锁调度器 计数 */ void osKernelUnlock(void); int32_t osKernelRestoreLock(int32_t lock); /* 但更常见的是直接用 FreeRTOS 原生v2 没完全覆盖 */ taskENTER_CRITICAL(); taskEXIT_CRITICAL(); vTaskSuspendAll(); xTaskResumeAll();实际工程中临界区这块直接用 FreeRTOS 原生 API 更普遍因为 v2 的封装不够完整。这不算违反用 v2原则——临界区属于底层机制用原生 API 是合理的混用第 03 篇讲过混用纪律。七、什么时候不该用临界区改用互斥量临界区有三个局限超过局限就该换互斥量局限互斥量怎么解决不能阻塞等待互斥量可以 osMutexAcquire(mutex, timeout) 带超时等待关中断影响实时性互斥量不关中断只阻塞拿不到锁的任务不能保护长操作互斥量可以保护任意长度的临界区但互斥量有自己的代价优先级翻转、开销更大。两者适用边界场景用什么极短的变量读改写几条指令临界区中断和任务共享的数据关中断临界区任务间共享、操作短锁调度器任务间共享、操作长可能几毫秒互斥量需要等待资源可能阻塞互斥量第三系列第 22~29 篇会深入讲互斥量。现在你只需记住临界区是短而快的保护互斥量是长或需等待的保护。八、本篇 API 速查功能原生 FreeRTOSCMSIS-RTOS v2说明关中断临界区taskENTER/EXIT_CRITICAL()无直接封装防任务中断锁调度器vTaskSuspendAll()/xTaskResumeAll()osKernelLock/Unlock/RestoreLock只防任务中断内关中断portSET_INTERRUPT_MASK_FROM_ISR()—ISR 专用配置相关宏作用configMAX_SYSCALL_INTERRUPT_PRIORITY关中断临界区只关低于此优先级的中断第 08 篇configASSERT临界区内调阻塞 API 会触发断言开发期开启选用速查条件选择中断会访问该数据关中断只有任务访问操作 几十微秒锁调度器操作较长或需等待互斥量第三系列在 ISR 里portSET_INTERRUPT_MASK_FROM_ISR总结1. 临界区解决竞态条件——让共享变量的读改写变成不可打断的原子操作。2. 关中断taskENTER_CRITICAL防任务中断最强但影响实时性只适合极短操作几十微秒。3. 锁调度器vTaskSuspendAll只防任务不防中断影响小适合任务间共享且中断不碰的数据。4. 选用三规则看中断是否访问、看是否需阻塞、看操作时长。5. 两种机制都不能在内部调用阻塞 API都支持嵌套。6. 长操作或需等待的场景改用互斥量第三系列讲别硬用临界区。
返回列表