FreeRTOS四种信号量详解
FreeRTOS四种信号量详解
前言
上期我们详细介绍了队列的常用操作,本期让我们深入探讨FreeRTOS中的信号量机制。信号量顾名思义是用于传递信号的量,与队列类似,也是实现任务间通信的重要机制,能够有效实现任务同步或临界资源的互斥访问。
同步与互斥概念
在详细介绍信号量前,我们需要先明确同步与互斥的基本概念。
同步
同步类似于生产者与消费者的关系:消费者必须等待生产者生产出数据后才能进行操作。在FreeRTOS中,数据采集和处理通常分为多个程序块,需要我们协调它们的执行顺序。这种协调就是同步,目的是确保多个程序块严格按照预定顺序执行,保证程序逻辑正确。
互斥
互斥则是防止同一资源被多个任务同时访问的机制。例如,任务A正在使用串口发送数据,此时如果高优先级的任务B也要使用同一串口,若没有互斥保护,任务B会中断任务A的发送操作,导致数据错乱。互斥机制确保资源在同一时刻只能被一个任务访问。
FreeRTOS信号量类型
FreeRTOS提供了四种类型的信号量,每种都有其特定用途:
- 计数信号量(Counting Semaphore)
- 二值信号量(Binary Semaphore)
- 互斥信号量(Mutex)
- 递归互斥信号量(Recursive Mutex)
计数信号量与二值信号量
信号量本质上是一种特殊的队列,区别在于:队列可以传递具体数据,而信号量只传递信号。既然信号量也是队列,为何还需要单独设计呢?
原因是:有时我们只需传递状态信息,无需传递具体数据。比如上面串口的例子,两个任务之间的数据没有直接联系,只有串口状态需要共享,因此只要传递状态信号即可。用队列传递状态也可以,但会浪费更多内存空间,所以产生了更轻量的信号量。
计数信号量与二值信号量的区别:
- 计数信号量的计数值没有上限限制
- 二值信号量的计数值仅能为0或1
互斥信号量与递归互斥信号量
这两种信号量主要用于实现互斥访问。那么,既然二值信号量已经可以实现互斥,为什么还需要互斥信号量呢?
这涉及到一个关键问题:优先级反转。
优先级反转问题
假设有三个不同优先级的任务(优先级:任务3 > 任务2 > 任务1),且任务1和任务3都需要使用串口:
- 任务1正在使用串口(已获取信号量)
- 此时任务2抢占了任务1的CPU时间(因优先级更高)
- 随后任务3也抢占了任务2(因优先级最高)
- 但任务3需要串口资源,而串口被任务1占用
- 任务3无法获取串口,只能将CPU控制权还给任务2
- 任务3必须等待任务2执行完毕,任务1释放串口后才能执行
结果:优先级最高的任务3最后才得以执行——这就是优先级反转。
互斥信号量解决方案:优先级继承
互斥信号量通过优先级继承机制解决优先级反转问题:
当任务3发现无法获取被任务1占用的资源时,系统会临时提升任务1的优先级(继承任务3的优先级),使任务1能够立即执行并尽快释放资源。任务1释放资源后,任务3继续执行,任务1恢复原优先级。
需要注意:
- 优先级继承只能缩短优先级反转的时间,无法完全避免
- 互斥信号量本质是带有优先级继承机制的二值信号量
- 互斥信号量不能在中断中使用
递归互斥信号量
递归互斥信号量解决了互斥信号量的两个主要缺陷:
-
误释放问题:互斥信号量可以被任何任务释放,而不仅限于获取该信号量的任务。如果任务A正在使用资源,任务C错误地释放并获取了信号量,会导致资源冲突。
-
死锁问题:同一任务多次获取互斥量会导致死锁。例如,任务首次获取互斥量成功,第二次获取由于未释放而陷入休眠,导致无法释放互斥量而永久阻塞。
递归互斥信号量的特点:
- 只能由获取者释放(谁获取谁释放)
- 同一任务可以多次获取,但需要相同次数的释放
- 具有与互斥信号量相同的优先级继承机制
信号量API简介
这里简单列出FreeRTOS信号量的核心API函数,下期将详细介绍其使用方法:
// 二值信号量
xSemaphoreCreateBinary(); // 创建二值信号量
xSemaphoreTake(xSemaphore, xTime); // 获取信号量
xSemaphoreGive(xSemaphore); // 释放信号量// 计数信号量
xSemaphoreCreateCounting(uxMaxCount, uxInitialCount); // 创建计数信号量// 互斥信号量
xSemaphoreCreateMutex(); // 创建互斥信号量// 递归互斥信号量
xSemaphoreCreateRecursiveMutex(); // 创建递归互斥信号量
xSemaphoreTakeRecursive(xMutex, xBlockTime); // 获取递归互斥信号量
xSemaphoreGiveRecursive(xMutex); // 释放递归互斥信号量
信号量选择指南
在实际开发中,如何选择合适的信号量类型?
- 需要事件计数:使用计数信号量
- 仅需同步,无资源竞争:使用二值信号量
- 需要互斥访问资源,可能有优先级反转:使用互斥信号量
- 需要互斥且同一任务可能多次获取:使用递归互斥信号量
结语
本期文章简要介绍了FreeRTOS中的四种信号量类型,以及同步与互斥的基本概念。下期将详细讲解各种信号量的API函数使用方法和实际案例。
🔥 更多精彩内容:我在GitHub上维护了一个完整的FreeRTOS学习资源库,包含从入门到精通的详细教程、示例代码和项目实战。欢迎star和fork!
参考资料
- FreeRTOS官方文档:FreeRTOS.org
- FreeRTOS学习资源库
如果您觉得这篇文章有帮助,请点赞、收藏、关注!下期将详细介绍信号量API的使用方法和实战案例,敬请期待!