freeRTOS学习笔记(十二)--信号量
文章目录
- 一、同步和互斥
- 1.1 同步
- 同步的核心场景与目标
- FreeRTOS中常用的同步机制
- 1. 二进制信号量(Binary Semaphore)
- 2. 计数信号量(Counting Semaphore)
- 3. 事件标志组(Event Groups)
- 4. 任务通知(Task Notifications)
- 5. 消息队列(Message Queues)
- 同步与互斥的区别
- 总结
- 1.2 互斥
- 互斥的核心问题:资源竞争与数据不一致
- FreeRTOS中互斥的实现:互斥锁(Mutex)
- 互斥与同步的核心区别
- 互斥的使用原则
- 总结
一、同步和互斥
1.1 同步
在FreeRTOS中,同步(Synchronization) 指的是多个任务之间(或任务与中断之间)为了协调执行节奏、共享事件或传递状态而进行的时序协作机制。其核心目的是确保任务按照预期的逻辑顺序执行,避免因执行时机错乱导致的数据错误、资源冲突或功能失效。
同步的核心场景与目标
在实时操作系统(RTOS)中,多任务并发执行时,往往需要通过同步解决以下问题:
- “等待事件”:一个任务需要等待另一个任务(或中断)完成某个操作后才能继续执行(例如,传感器采集任务完成后,数据处理任务才能启动)。
- “步调一致”:多个任务需按照特定顺序协同工作(例如,A任务生成数据→B任务处理数据→C任务存储数据,需严格按A→B→C的顺序执行)。
- “资源有序访问”:当多个任务共享有限资源(如缓冲区、外设)时,通过同步确保资源使用的时序合理性(区别于“互斥”,同步更侧重“顺序”而非“独占”)。
FreeRTOS中常用的同步机制
FreeRTOS提供了多种同步工具,每种工具适用于不同场景:
1. 二进制信号量(Binary Semaphore)
-
原理:类似“开关”,只有两种状态(0或1),通常用于单次事件同步。
-
场景:
- 中断服务程序(ISR)通知任务处理事件(例如,串口接收数据后,ISR释放信号量,任务获取信号量后处理数据)。
- 任务A完成初始化后,释放信号量允许任务B启动。
// 示例:ISR中释放信号量,任务中等待信号量 void UART_ISR(void) {xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken); // ISR释放 }void vDataProcessTask(void *pvParam) {while(1) {xSemaphoreTake(xBinarySemaphore, portMAX_DELAY); // 等待信号量// 处理数据} }
2. 计数信号量(Counting Semaphore)
- 原理:信号量值可在0到最大值之间变化,用于统计可用资源数量或事件次数。
- 场景:
- 管理资源池(如5个缓存区,任务获取信号量使用缓存,释放后信号量值加1)。
- 累计事件次数(如计数按键按下次数,任务批量处理)。
3. 事件标志组(Event Groups)
-
原理:通过一个32位整数的不同位表示不同事件,任务可等待“任意一个事件”或“所有事件”发生。
-
场景:
- 任务等待多个事件中的一个(如“传感器A触发”或“传感器B触发”)。
- 任务等待多个事件同时发生(如“温度达标”且“湿度达标”后启动风扇)。
// 示例:等待两个事件同时发生 EventBits_t xBits = xEventGroupWaitBits(xEventGroup, // 事件组(1 << 0) | (1 << 1), // 等待事件0和事件1pdTRUE, // 获取后清除标志位pdTRUE, // 需两个事件同时发生portMAX_DELAY // 无限等待 ); if( (xBits & ((1 << 0) | (1 << 1))) == ((1 << 0) | (1 << 1)) ) {// 两个事件均发生,执行操作 }
4. 任务通知(Task Notifications)
- 原理:通过直接向任务发送通知(替代信号量/事件组),效率更高(无需创建额外内核对象)。
- 场景:轻量级同步(如替代二进制信号量,减少内存开销)。
5. 消息队列(Message Queues)
- 原理:通过队列传递数据,同时隐含同步功能(发送方和接收方需等待队列有空间/有数据)。
- 场景:同步+数据传递(如任务A发送数据到队列,任务B等待队列有数据后处理)。
同步与互斥的区别
需注意,同步与“互斥(Mutex)”是不同概念:
- 同步:解决“时序协调”问题,确保任务按逻辑顺序执行(如“先生产后消费”)。
- 互斥:解决“资源独占”问题,防止多个任务同时访问共享资源(如用互斥锁保护全局变量)。
例如:两个任务共享打印机时,互斥确保同一时间只有一个任务使用打印机;而同步可能确保“任务A打印完文档1后,任务B才能打印文档2”。
总结
FreeRTOS中的同步是多任务协作的核心机制,通过信号量、事件标志组等工具,确保任务(或中断)按照预期的时序执行,避免因并发导致的逻辑混乱。实际开发中,需根据具体场景(如事件数量、是否传递数据、资源类型)选择合适的同步工具,以保证系统的实时性和可靠性。
1.2 互斥
在FreeRTOS(或更广泛的操作系统领域)中,互斥(Mutual Exclusion,简称“互斥”) 是一种解决多个任务对共享资源竞争访问的机制,核心目标是确保同一时间只有一个任务能访问特定的共享资源,从而避免因并发操作导致的数据错乱、逻辑冲突或系统异常。
互斥的核心问题:资源竞争与数据不一致
在多任务系统中,多个任务可能同时需要访问“共享资源”(如全局变量、硬件外设、缓冲区等)。如果缺乏协调,可能出现以下问题:
- 例如,任务A和任务B同时向同一个串口发送数据,最终输出的内容会变成两者的混合乱码;
- 任务A读取全局变量
count=5并执行count++(计划改为6),但执行中途被任务B抢占,任务B读取count=5并改为6,随后任务A恢复执行,继续将count改为6——最终结果应为7,却错误地变成6。
这种因“同时访问”导致的错误,称为资源竞争(Race Condition)。互斥机制的作用就是通过“独占访问”避免此类问题。
FreeRTOS中互斥的实现:互斥锁(Mutex)
FreeRTOS通过互斥锁(Mutex) 实现互斥功能,其本质是一种特殊的信号量,具有以下特性:
-
独占性:
互斥锁只有“持有”和“释放”两种状态。当一个任务获取(take)互斥锁后,其他任务必须等待(block),直到该任务释放(give)互斥锁,才能有机会获取。// 示例:创建互斥锁并使用 SemaphoreHandle_t xMutex;void vInitTask(void *pvParam) {xMutex = xSemaphoreCreateMutex(); // 创建互斥锁// ... 创建其他任务 }void vTaskA(void *pvParam) {while(1) {// 尝试获取互斥锁,最多等待100msif(xSemaphoreTake(xMutex, 100) == pdTRUE) {// 成功获取锁,安全访问共享资源(如全局变量、串口)shared_data = 123;printf("TaskA: %d\n", shared_data);xSemaphoreGive(xMutex); // 释放锁}vTaskDelay(100);} }void vTaskB(void *pvParam) {while(1) {// 尝试获取互斥锁,无限等待if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {// 安全访问共享资源shared_data = 456;printf("TaskB: %d\n", shared_data);xSemaphoreGive(xMutex); // 释放锁}vTaskDelay(150);} } -
优先级继承(Priority Inheritance):
这是FreeRTOS互斥锁的关键特性,用于解决“优先级反转”问题:- 优先级反转:低优先级任务持有互斥锁时,中等优先级任务抢占CPU,导致高优先级任务(等待该锁)被长时间阻塞,违背实时性要求。
- 优先级继承:当高优先级任务等待低优先级任务持有的互斥锁时,低优先级任务的优先级会临时提升到与高优先级任务相同,直到释放锁,从而避免被中等优先级任务抢占,保证高优先级任务的响应速度。
-
所有权:
互斥锁具有“所有权”概念——只有获取锁的任务才能释放锁(信号量则无此限制)。这避免了其他任务误释放锁导致的混乱。
互斥与同步的核心区别
| 维度 | 互斥(Mutex) | 同步(如信号量、事件组) |
|---|---|---|
| 核心目标 | 解决“资源独占”问题,防止竞争访问 | 解决“时序协调”问题,确保任务按逻辑顺序执行 |
| 关注点 | 资源的“安全性”(数据一致、操作正确) | 任务的“有序性”(先做什么,后做什么) |
| 典型场景 | 多个任务访问同一串口、全局变量、硬件外设等 | 任务A等待任务B完成初始化、中断通知任务处理数据等 |
| 关键特性 | 优先级继承、所有权 | 信号量计数、事件标志位组合等 |
互斥的使用原则
- 最小持有时间:获取互斥锁后,应尽快完成对共享资源的操作并释放锁,减少其他任务的等待时间。
- 避免嵌套死锁:不要在持有一个互斥锁时尝试获取另一个互斥锁(或同一锁),否则可能导致任务相互等待(死锁)。
- 适配场景:仅对“需要独占访问的共享资源”使用互斥锁,无需保护的资源(如只读变量)无需加锁,避免过度消耗系统资源。
总结
互斥是FreeRTOS中保障共享资源安全访问的核心机制,通过互斥锁实现“同一时间仅一个任务访问资源”,并通过优先级继承解决实时性问题。它与同步机制(如信号量)相辅相成:同步确保任务“按顺序执行”,互斥确保资源“被安全访问”,共同支撑多任务系统的稳定运行。
