news 2026/10/17 2:49:23

freeRTOS信号量(Semaphore)、互斥量(Mutex)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeRTOS信号量(Semaphore)、互斥量(Mutex)

欢迎友友指正🤗🤗🤗,有输出一起进步~

复习一下rtos理论(自己的感悟结合韦东山老师部分讲解)

先说重难点,方便已有基础的同学快速联想(前置知识可以划到下面“前言”部分开始)

互斥量

本质是特殊的二值信号量

由于使用信号量确实也可以实现互斥访问,但是不完美。这是二值信号量做互斥,和 Mutex 互斥量不一样。
(二值信号量实现互斥的缺陷:

1·、没有【所有权】,任何人都可以释放信号量(致命)
——Mutex(互斥量):谁拿锁,只能谁解锁。

——二值信号量:任意任务 / 中断都可以调用 Give 直接把信号量释放。

2、会出现优先级反转,没有优先级继承机制(最经典缺点)
——后文细说;
3、不支持递归锁(可重入),如果同一个任务,已经拿到信号量,再次 Take:直接阻塞卡死自己。
——Mutex 可以支持递归互斥锁(Recursive Mutex),同一个任务多次 take,计数叠加,需要同等次数 give 释放。二值信号量做不到。)

优先级反转

前置知识:

阻塞不是“一个全局状态”。每个内核对象(信号量、队列、任务通知)都有自己独立的等待链表,不属于全局就绪队列,阻塞就是任务在等待一个特定事件,离开全局就绪链表,住在上面这些私有链表其中里。调度器不可见。

全局就绪队列:所有“已经准备好了,只要有CPU就能跑,不等任何特定事件”的任务。调度器每次选任务,只在这里面挑选最高优先级的。

各个对象的私有等待链表:任务“自愿在这里休眠,专门等这个对象的事件”,已经离开就绪队列,调度器看不见它们;

——————————————————————————————————————————

简单说:两个任务,高优先级的因为低优先级的任务持有信号量而被阻塞;
但多数情况是多个任务,拿三任务举例:

(存在一个中优先级任务不依赖那个被占用的信号量/锁)

1、系统刚跑,只有TaskL就绪,TaskH、TaskM(中优先级)都还没触发;
TaskL开始运行,TaskL 成功 xSemaphoreTake(xSem,portMAx_DELAY),拿到信号量,开始操作手保护的共享资源;
此时:xSem=0(资源被占用);
TaskL继续在CPU上跑。TaskH突然因为外部中断/定时器中断/其他任务通知/队列收数据...而就绪,调度器看到就绪列表里的高优先级任务比这个L任务高,立刻抢占了,把低优先级任务踢到就绪队列里,CPU交给TaskH——问题是高优先级拿不到这个信号量take不到,TaskH被踢出了,而不是被抢占踢回就绪队列,是直接踢到阻塞列表,等待信号量;

2、关键来了——此时就绪队列里最高的是TaskM(不需要信号量/锁),在TaskL前进入占据CPU不放,TaskL没办法进去继续运行到释放信号量的时候了,没有机会了。——TaskM,TaskH反而被卡住。

解决办法:
互斥锁(互斥量Mutex)——附带优先级继承效果

所有权:谁 take,只能谁 give(谁上锁,就只能由谁开锁。二值信号量没有这个)

上面的优先级反转情形,如果能提升TaskL任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?等TaskL执行完后释放互斥锁,就恢复优先级——级优先级继承。

创建

/* 创建一个互斥量,返回它的句柄。 * 此函数内部会分配互斥量结构体 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateMutex( void ); /* 创建一个互斥量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

#define configUSE_MUTEXES 1

注意:二级制信号量初始值是0,创建后需要Give一次;互斥量初始值是1,创建后不需要Give一次。

要注意的是,互斥量不能在ISR中使用。

递归锁

1.死锁的概念

什么情况下会造成死锁,例如,线程 A 持有锁 L1 并请求锁 L2,而线程 B 持有锁 L2 并请求锁 L1,这种情况可能导致死锁。所以我们编写程序时要注意,不要写成死锁的情况,等以后写一些大型的代码,需要同时操控几个互斥量时,可能会犯这种错误。

假设有2个互斥量M1、M2,2个任务A、B:

A获得了互斥量M1

B获得了互斥量M2

A还要获得互斥量M2才能运行,结果A阻塞

B还要获得互斥量M1才能运行,结果B阻塞

A、B都阻塞,再无法释放它们持有的互斥量

死锁发生!

2.自我死锁

假设这样的场景:

任务A获得了互斥锁M

它调用一个库函数

库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!

死锁发生!

一个任务申请锁后调用其他函数时申请同一个锁,导致要释放锁的任务阻塞,从而无法释放锁。

——

怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:

递归锁实现了:谁上锁就由谁解锁。

  • 任务A获得递归锁M后,它还可以多次去获得这个锁

  • "take"了N次,要"give"N次,这个锁才会被释放

递归锁的函数根一般互斥量的函数名不一样,参数类型一样,列表如下:

递归锁一般互斥量
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
获得xSemaphoreTakeRecursivexSemaphoreTake
释放xSemaphoreGiveRecursivexSemaphoreGive

注意:要使用递归锁,需要定义配置项configUSE_RECURSIVE_MUTEXES!!!

——————————————————————————————————————

什么时候用二值信号量做互斥,什么时候用 Mutex:
✅ 保护共享资源、临界区 → 优先用Mutex互斥量
✅ 任务间事件通知(比如中断通知任务收到数据)→ 用二值信号量

=======================================================================

前言

队列(queue)可以用于传输数据:在任务之间、任务和中断之间。

有时候我们只需要传递状态,并不需要传递具体的信息,比如:
我的事做完了,通知一下你
卖包子了、卖包子了,做好了1个包子!做好了2个包子!做好了3个包子!
这个停车位我占了,你们只能等着

这种情况下我们只需要维护一个数值而且不需要复制数据,使用信号量效率更高、它更节省内存。是可以用来保证两个或多个关键代 码段不被并发调用。——信号量也是队列。
当我们的量只有0和1的时候,它就可以被称作二值信号量,只有两个状 态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。

信号量函数

创建

使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:

创建二进制信号量的函数原型如下:

/* 创建一个二进制信号量,返回它的句柄。 * 此函数内部会分配信号量结构体 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateBinary( void ); /* 创建一个二进制信号量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

创建计数型信号量的函数原型如下:

/* 创建一个计数型信号量,返回它的句柄。 * 此函数内部会分配信号量结构体 * uxMaxCount: 最大计数值 * uxInitialCount: 初始计数值 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount); /* 创建一个计数型信号量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * uxMaxCount: 最大计数值 * uxInitialCount: 初始计数值 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );

删除

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

通用:

/* * xSemaphore: 信号量句柄,你要删除哪个信号量 */ void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

获得与释放信号量

二进制信号量、计数型信号量的give、take操作函数是一样的。这些函数也分为2个版本:给任务使 用,给ISR使用。列表如下:

参数

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 2:48:53

科研绘图的5个常见误区,科迅捷AI帮你一次纠正

研究生阶段,论文配图的质量直接影响审稿人的第一印象。很多同学实验做得扎实,却栽在图上:图太丑、图不规范、图与正文对不上,最后被审稿意见反复点名。科研绘图到底有哪些常见误区? 误区一:图是“配图”&a…

作者头像 李华
网站建设 2026/10/17 2:47:31

系统读Spring官网:容器、AOP、事务与Bean生命周期的知识地图

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 2:47:16

mybatis进阶使用(上)

补充:1.在开发中遇到数据表的字段和实体的属性名不匹配时的处理方法a.查询的时候给查询条件取别名 例:User selectUser(Param("userName") String name, Param("userAge") Integer age);param("xxx"&#xff0…

作者头像 李华
网站建设 2026/10/17 2:46:57

GPU服务器租用性能验收:CPU线程与NUMA亲和性排查实战

在云端GPU租赁场景中,显卡型号一致并不代表训练速度一致。大模型训练或深度学习任务如果长期“GPU吃不满”,问题可能不在CUDA,而在CPU线程、内存带宽和NUMA访问路径。本文给出一套可复现的验收流程,帮助开发者在正式跑任务前定位主…

作者头像 李华
网站建设 2026/10/17 2:46:55

【环境方向 SCI期刊推荐】环境 SCI 捡漏!扩刊红利期黑马刊 + 经典顶刊,3 区到 1 区全覆盖,好投又稳妥 | 环境领域黑马 SCI 发文量翻倍,附4本经典稳刊,毕业结题首选,适配各阶段

环境科学领域的科研人投稿,常陷入两难困境:冲顶刊则审稿周期长、拒稿率高,耗时耗力容易耽误毕业、结题节点;选保底刊又容易踩中预警、自引率超标的水刊,最终成果不被认可。 当前不少环境类SCI 期刊正处于扩刊红利期&am…

作者头像 李华