在MCU(微控制器)编程中,临界资源和临界区是处理并发操作(如多任务、中断与主程序交互)时的核心概念,直接影响系统的稳定性和数据安全性。以下是更深入的解析:
一、临界资源(Critical Resource)
定义:指被多个执行实体(如任务、中断服务程序ISR)共享,且其操作需要保持原子性(不可分割)的资源。
核心特点:
- 共享性:至少被两个及以上执行实体访问
- 敏感性:非原子操作可能导致数据错误或系统异常
常见类型:
- 软件资源:全局变量、静态变量、数据缓冲区、链表/队列等数据结构
- 硬件资源:外设寄存器(如UART数据寄存器)、I/O端口、定时器计数器
- 总线资源:I2C、SPI等共享总线,同一时间只能有一个设备占用
问题示例:
两个任务同时操作全局变量count
:
- 任务A读取
count=5
,准备执行count++
(计划结果6) - 任务B在任务A写入前读取
count=5
,也执行count++
- 最终
count
变为6(正确应为7),导致数据错误
二、临界区(Critical Section)
定义:访问或操作临界资源的那段代码。需要保证互斥性——同一时间只能有一个执行实体进入并执行。
临界区的边界:
- 进入(Entry):获取资源访问权限的时刻
- 退出(Exit):释放资源访问权限的时刻
示例:
// 临界资源:全局计数器
uint16_t system_counter = 0;
// 任务中的临界区
void task_update_counter(void) {
while(1) {
// 临界区开始
system_counter += 1; // 非原子操作(读-改-写)
// 临界区结束
vTaskDelay(10);
}
}
// 中断中的临界区
void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
// 临界区开始
system_counter += 10; // 同样需要保护
// 临界区结束
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
三、临界区保护机制
根据应用场景(有无RTOS、中断参与等),常用以下保护方法:
1. 中断开关(最基础方法)
原理:通过禁用全局中断,防止ISR打断当前临界区操作。
STM32示例:
// 进入临界区
uint32_t primask = __get_PRIMASK(); // 保存中断状态
__disable_irq(); // 禁用全局中断
// 临界区操作
system_counter += 1;
// 退出临界区
__set_PRIMASK(primask); // 恢复中断状态
注意:
- 禁用时间必须尽可能短,避免影响中断响应
- 适用于单任务+中断的简单系统
2. RTOS临界区API
在多任务系统中,RTOS提供专用接口保护临界区(如FreeRTOS):
// 任务级临界区
taskENTER_CRITICAL(); // 进入临界区(禁用部分中断)
system_counter += 1;
taskEXIT_CRITICAL(); // 退出临界区(恢复中断)
// 中断级临界区
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR(); // 进入
system_counter += 10;
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus); // 退出
优势:
- 仅禁用必要的中断(根据RTOS配置的中断优先级)
- 平衡安全性和系统响应速度
3. 互斥锁(Mutex)
适用于多任务间的临界区保护(无中断参与):
SemaphoreHandle_t xMutex; // 定义互斥锁
// 任务1
void vTask1(void *pvParam) {
while(1) {
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
// 临界区操作
system_counter += 1;
xSemaphoreGive(xMutex); // 释放锁
vTaskDelay(10);
}
}
// 任务2(类似操作)
特点:
- 任务获取不到锁时会阻塞等待
- 支持优先级继承(避免优先级反转)
四、最佳实践
- 最小化原则:临界区代码尽可能短,减少资源独占时间
- 避免嵌套:嵌套临界区可能导致死锁或中断长时间禁用
- 分层保护:
- 中断与任务共享:用中断开关或RTOS中断级API
- 任务间共享:用互斥锁或任务级临界区API
- 原子操作优先:对简单变量(如32位以内),优先使用CPU原子指令
通过合理设计临界区保护机制,可以有效避免并发访问导致的数据不一致问题,确保MCU系统稳定运行。