嵌入式|RTOS教学——FreeRTOS基础4:信号量
在嵌入式实时操作系统(如 FreeRTOS、RT-Thread 等)中,信号量(Semaphore) 是一种用于任务同步和资源管理的核心机制,本质是一个 “带计数的标志”,通过对计数器的 “获取” 和 “释放” 操作,实现多任务间的协作与冲突控制。
简单来说,信号量可以理解为:
- 一个 “通行证”:任务需要获得通行证才能执行某个操作(如访问共享资源);
- 一个 “通知器”:任务完成后释放通行证,通知其他等待的任务可以开始执行。
一、信号量的核心组成
信号量的核心是一个计数器和一个等待队列:
- 计数器(count):记录可用的 “资源数量” 或 “通知次数”,取值范围由具体 RTOS 定义(通常为非负整数)。
- 等待队列(wait queue):当信号量计数器为 0 时,试图获取信号量的任务会进入这个队列,进入 “阻塞态” 等待,直到有其他任务释放信号量。
二、信号量的两种核心类型
根据计数器的特性,信号量主要分为两类,适用场景不同:
1. 二进制信号量(Binary Semaphore)
特性:计数器只能为 0 或 1(类似 “开关”)。
核心作用:任务同步(通知事件发生)。
工作流程:
- 初始状态:计数器为 0(信号量 “未释放”)。
- 任务 A 完成某个操作后,调用 “释放信号量” 接口,计数器变为 1(发出 “事件完成” 通知)。
- 任务 B 调用 “获取信号量” 接口,若计数器为 1,则获取成功(计数器变回 0),开始执行后续操作;若计数器为 0,则任务 B 进入阻塞态,等待任务 A 释放信号量。
示例场景:
传感器采集任务(A)完成一次数据采集后,释放二进制信号量;数据处理任务(B)一直等待该信号量,获取成功后立即处理新采集的数据(实现 “采集完成→处理” 的同步)。
2. 计数信号量(Counting Semaphore)
特性:计数器可以是 0 到 N 之间的整数(N 为最大计数,创建时指定)。
核心作用:资源管理(控制多个任务对有限资源的访问)。
工作流程:
- 初始状态:计数器为 N(表示有 N 个资源可用)。
- 任务获取信号量:计数器减 1(占用一个资源);若计数器为 0,任务进入阻塞态等待。
- 任务释放信号量:计数器加 1(释放一个资源);若有任务在等待队列中,唤醒其中一个任务(通常是优先级最高的)。
示例场景:
系统有 2 个串口资源,创建计数信号量时初始计数设为 2。3 个任务需要使用串口时:前 2 个任务能成功获取信号量(计数器变为 0),第 3 个任务进入阻塞态;当任意一个任务用完串口并释放信号量(计数器变为 1),第 3 个任务被唤醒,获取信号量后使用串口。
三、信号量的核心操作(以 FreeRTOS 为例)
信号量的使用围绕 “创建→获取→释放” 三个核心操作,FreeRTOS 提供了简洁的接口:
操作 | 函数接口(FreeRTOS) | 功能说明 |
---|---|---|
创建 | xSemaphoreCreateBinary() | 创建二进制信号量(初始计数为 0)。 |
xSemaphoreCreateCounting() | 创建计数信号量(需指定最大计数和初始计数,如 xSemaphoreCreateCounting(5, 3) 表示最大 5 个资源,初始 3 个可用)。 | |
获取 | xSemaphoreTake() | 任务中获取信号量:计数器减 1,若计数器为 0 则阻塞(阻塞时间可设置);成功返回 pdPASS 。 |
xSemaphoreTakeFromISR() | 中断中获取信号量(不阻塞,仅用于特殊场景,通常不推荐在中断中获取)。 | |
释放 | xSemaphoreGive() | 任务中释放信号量:计数器加 1,若有等待任务则唤醒;成功返回 pdPASS 。 |
xSemaphoreGiveFromISR() | 中断中释放信号量(中断安全版本)。 |
四、信号量 vs 消息队列:核心区别
信号量和消息队列都能实现任务间通信,但定位不同:
- 信号量:更侧重 “事件同步” 或 “资源计数”,不传递具体数据(仅传递 “是否可用” 的状态)。
例:用信号量通知 “按键被按下”,但不传递 “按下的是哪个键” 的信息。 - 消息队列:更侧重 “数据传递”,可以携带具体数据(如温度值、命令字)。
例:用队列传递 “按键值 = KEY1”,接收方知道具体是哪个键被按下。
五、典型应用场景
中断与任务同步:
外部中断(如按键触发)发生时,在中断服务函数中用xSemaphoreGiveFromISR()
释放二进制信号量;任务中用xSemaphoreTake()
等待该信号量,获取成功后处理中断事件(避免在中断中执行复杂逻辑)。限制共享资源访问:
多个任务需要访问同一块 LCD 屏(唯一资源),用二进制信号量控制:任务使用 LCD 前获取信号量(确保独占),用完后释放,避免多个任务同时刷新 LCD 导致显示混乱。任务间顺序控制:
系统启动时,初始化任务(A)需要先初始化外设,完成后释放信号量;其他任务(B、C、D)必须等待该信号量才能运行(确保外设就绪后再操作)。限流控制:
某传感器最大支持每秒被读取 10 次,用计数信号量(初始计数 10,最大 10):每次读取前获取信号量(计数减 1),1 秒后定时任务释放 10 个信号量(恢复计数),避免读取频率过高损坏硬件。
六、使用注意事项
避免死锁:
若任务 A 持有信号量 S1 并等待信号量 S2,任务 B 持有 S2 并等待 S1,会导致两者永久阻塞(死锁)。需统一任务获取信号量的顺序(如按信号量地址从小到大)。中断中谨慎操作:
中断中只能释放信号量(用xSemaphoreGiveFromISR()
),禁止获取信号量(可能阻塞,违反中断 “快速执行” 原则)。合理设置阻塞时间:
获取信号量时,阻塞时间过短可能导致任务频繁失败;过长可能影响实时性。根据业务需求设置(如非关键任务设短时间,关键任务设portMAX_DELAY
永久等待)。二进制信号量 vs 互斥锁:
二进制信号量用于 “同步”,互斥锁(Mutex)用于 “独占资源”(带优先级继承机制,避免优先级反转)。若要保护共享资源,优先用互斥锁而非二进制信号量。
总结
信号量是 RTOS 中实现任务协作与资源控制的 “轻量级工具”,通过简单的计数机制,解决了多任务环境下的同步、互斥和限流问题。核心是理解:
- 二进制信号量:“0 或 1” 的开关,用于事件通知;
- 计数信号量:“0 到 N” 的计数器,用于资源管理。
结合具体场景选择合适的信号量类型,并注意避免死锁和中断使用禁忌,就能充分发挥其作用。