信号量(二值信号量和计数信号量)和互斥量

信号量

信号量(Semaphore) 是一种实现任务间通信的机制, 可以实现任务之间同步或临界资源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中, 各任务之间需要同步或互斥实现临界资源的保护, 信号量功能可以为用户提供这方面的支持。

抽象的来讲, 信号量是一个非负整数, 所有获取它的任务都会将该整数减一(获取它当然是为了使用资源) , 当该整数值为零时, 所有试图获取它的任务都将处于阻塞状态。 通常一个信号量的计数值用于对应有效的资源数, 表示剩下的可被占用的互斥资源数。 其值的含义分两种情况:
●0: 表示没有积累下来的释放信号量操作, 且有可能有在此信号量上阻塞的任务。
●正值, 表示有一个或多个释放信号量操作。

 信号:起通知作用
 量:还可以用来表示资源的数量
 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
 当"量"只有 0、 1 两个取值时,它就是"二进制信号量"(Binary Semaphores)
 支持的动作: "give"给出资源,计数值加 1; "take"获得资源,计数值减 1

计数:事件产生时"give"信号量,让计数值加 1;处理事件时要先"take"信号量,就是获得信号量,让计数值减 1。
资源管理:要想访问资源需要先"take"信号量,让计数值减 1;用完资源后"give"信号量,让计数值加 1。

二值信号量

二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥信号量(以下使用互斥量表示互斥信号量) 非常相似, 但是有一些细微差别: 互斥量有优先级继承机制, 二值信号量则没有这个机制。 这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步) , 而互斥量更偏向应用于临界资源的访问。用作同步时, 信号量在创建后应被置为空, 任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后, 释放信号量, 于是任务 1 获得信号量得以进入就绪态, 如果任务 1 的优先级是最高的, 那么就会立即切换任务, 从而达到了两个任务间的同步。 同样的, 在中断服务函数中释放信号量, 任务 1 也会得到信号量, 从而达到任务与中断间的同步。

可以将二值信号量看作只有一个消息的队列, 因此这个队列只能为空或满(因此称为二值)我们在运用的时候只需要知道队列中是否有消息即可, 而无需关注消息是什么。

计数信号量

二进制信号量可以被认为是长度为 1 的队列, 而计数信号量则可以被认为长度大于 1 的队列, 信号量使用者依然不必关心存储在队列中的消息, 只需关心队列是否有消息即可。

二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。


 

信号量跟队列的对比


 

 常用信号量 API 函数

创建

使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。


 

删除 

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量

信号量释放函数 (give)

二进制信号量、计数型信号量的 give、 take 操作函数是一样的

 

 

 

 

 信号量获取函数 (take)

 

 

 

互斥量 

互斥量又称互斥信号量(本质是信号量) , 是一种特殊的二值信号量, 它和信号量不同的是, 它支持互斥量所有权、 递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。

互斥量简介

互斥信号量其实就是一个拥有优先级继承的二值信号量, 在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。 互斥信号量适合用于那些需要互斥访问的应用中。 在互斥访问中互斥信号量相当于一个钥匙, 当任务想要使用资源的时候就必须先获得这个钥匙, 当使用完资源以后就必须归还这个钥匙, 这样其他的任务就可以拿着这个钥匙去使用资源。

互斥信号量使用和二值信号量相同的 API 操作函数, 所以互斥信号量也可以设置阻塞时间, 不同于二值信号量的是互斥信号量具有优先级继承的特性。 当一个互斥信号量正在被一个低优先级的任务使用, 而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。 不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级, 这个过程就是优先级继承。 优先级继承尽可能的降低了高优先级任务处于阻塞态的时间, 并且将已经出现的“优先级翻转” 的影响降到最低。

互斥量的优先级继承机制

在 FreeRTOS 操作系统中为了降低优先级翻转问题利用了优先级继承算法
优先级继承算法是指, 暂时提高某个占有某种资源的低优先级任务的优先级, 使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等, 而当这个低优先级任务执行完毕释放该资源时, 优先级重新回到初始设定值。 因此, 继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。

互斥量与二值信号量最大的不同是: 互斥量具有优先级继承机制, 而信号量没有。 也就是说, 某个临界资源受到一个互斥量保护, 如果这个资源正在被一个低优先级任务使用, 那么此时的互斥量是闭锁状态, 也代表了没有任务能申请到这个互斥量, 如果此时一个高优先级任务想要对这个资源进行访问, 去申请这个互斥量, 那么高优先级任务会因为申请不到互斥量而进入阻塞态, 那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同, 这个优先级提升的过程叫做优先级继承。 这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短, 以及将已经出现的“优先级翻转” 危害降低到最小。

互斥量应用场景

互斥量的使用比较单一, 因为它是信号量的一种, 并且它是以锁的形式存在。
在初始化的时候, 互斥量处于开锁的状态, 而被任务持有的时候则立刻转为闭锁的状态。

互斥量更适合于
 ●可能会引起优先级翻转的情况。
递归互斥量更适用于:
●任务可能会多次获取互斥量的情况下。 这样可以避免同一任务多次递归持有而造成死锁的问题。 

多任务环境下往往存在多个任务竞争同一临界资源的应用场景, 互斥量可被用于对临界资源的保护从而实现独占式访问。 另外, 互斥量可以降低信号量存在的优先级翻转问题带来的影响。 

比如有两个任务需要对串口进行发送数据, 其硬件资源只有一个, 那么两个任务肯定不能同时发送啦, 不然导致数据错误, 那么, 就可以用互斥量对串口资源进行保护, 当一个任务正在使用串口的时候, 另一个任务则无法使用串口, 等到任务使用串口完毕之后, 另外一个任务才能获得串口的使用权。

另外需要注意的是互斥量不能在中断服务函数中使用, 因为其特有的优先级继承机制只在任务起作用, 在中断的上下文环境毫无意义。

常用API函数

互斥量创建函数

xSemaphoreCreateMutex()用于创建一个互斥量, 并返回一个互斥量句柄。

该句柄的原型是一个 void 型的指针, 在使用之前必须先由用户定义一个互斥量句柄。 要想使用该函数必须在 FreeRTOSConfig.h 中configSUPPORT_DYNAMIC_ALLOCATION 定义为 1, 即开启动态内存分配, 其实该宏在 FreeRTOS.h 中默认定义为 1, 即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案, 同时还需在 FreeRTOSConfig.h 中把configUSE_MUTEXES 宏定义打开, 表示使用互斥量。

 

递归互斥量创建函数 

xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量, 不是递归的互斥量由函数 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()创建(我们只讲解动态创建) , 且只能被同一个任务获取一次, 如果同一个任务想再次获取则会失败。 递归信号量则相反, 它可以被同一个任务获取很多次, 获取多少次就需要释放多少次。 递归信号量与互斥量一样, 都实现了优先级继承机制, 可以降低优先级反转的危害。

要想使用该函数必须在 FreeRTOSConfig.h 中把宏configSUPPORT_DYNAMIC_ALLOCATION 和 configUSE_RECURSIVE_MUTEXES 均定义为 1。configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 即表示开启动态内存分配, 其实该宏在 FreeRTOS.h 中默认定义为 1, 即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案


 

互斥量删除函数 

互斥量的本质是信号量, 直接调用 vSemaphoreDelete()函数进行删除即可。

互斥量获取函数 

当互斥量处于开锁的状态, 任务才能获取互斥量成功, 当任务持有了某个互斥量的时候, 其它任务就无法获取这个互斥量, 需要等到持有互斥量的任务进行释放后, 其他任务才能获取成功, 任务通过互斥量获取函数来获取互斥量的所有权。 任务对互斥量的所有权是独占的, 任意时刻互斥量只能被一个任务持有, 如果互斥量处于开锁状态, 那么获取该互斥量的任务将成功获得该互斥
量, 并拥有互斥量的使用权; 如果互斥量处于闭锁状态, 获取该互斥量的任务将无法获得互斥量, 任务将被挂起, 在任务被挂起之前, 会进行优先级继承, 如果当前任务优先级比持有互斥量的任务优先级高, 那么将会临时提升持有互斥量任务的优先级。 互斥量的获取函数是一个宏定义, 实际调用的函数就是xQueueGenericReceive()。

递归互斥量获取函数

xSemaphoreTakeRecursive()是一个用于获取递归互斥量的宏, 与互斥量的获取函数一样, xSemaphoreTakeRecursive()也是一个宏定义, 它最终使用现有的队列机制, 实际执行的函数是 xQueueTakeMutexRecursive()。 互斥量之前必须由 xSemaphoreCreateRecursiveMutex()这个函数创建。 要注意的是该函数不能用于获取由函数 xSemaphoreCreateMutex()创建的互斥量。 要想使用该函数必须在头文件 FreeRTOSConfig.h 中把宏 configUSE_RECURSIVE_MUTEXES 定义为1。


 

互斥量释放函数 xSemaphoreGive()

任务想要访问某个资源的时候, 需要先获取互斥量, 然后进行资源访问, 在任务使用完该资源的时候, 必须要及时归还互斥量, 这样别的任务才能对资源进行访问

FreeRTOS 给我们提供了互斥量释放函数 xSemaphoreGive(), 任务可以调用 xSemaphoreGive()函数进行释放互斥量, 表示我已经用完了, 别人可以申请使用, 互斥量的释放函数与信号量的释
放函数一致, 都是调用 xSemaphoreGive()函数, 但是要注意的是, 互斥量的释放只能在任务中, 不允许在中断中释放互斥量。

使用该函数接口时, 只有已持有互斥量所有权的任务才能释放它, 当任务调用 xSemaphoreGive()函数时会将互斥量变为开锁状态, 等待获取该互斥量的任务将被唤醒。 如果任务的优先级被互斥量的优先级翻转机制临时提升, 那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级


 

递归互斥量释放函数 xSemaphoreGiveRecursive() 

xSemaphoreGiveRecursive()是一个用于释放递归互斥量的宏。 要想使用该函数必须在头文件 FreeRTOSConfig.h 把宏 configUSE_RECURSIVE_MUTEXES 定义为 1。


 

xSemaphoreGiveRecursive()函数用于释放一个递归互斥量。 已经获取递归互斥量的任务可以重复获取该递归互斥量。 使用 xSemaphoreTakeRecursive()函数成功获取几次递归互斥量, 就要使用 xSemaphoreGiveRecursive()函数返还几次, 在此之前递归互斥量都处于无效状态, 别的任务就无法获取该递归互斥量。

使用该函数接口时, 只有已持有互斥量所有权的任务才能释放它, 每释放一次该递归互斥量, 它的计数值就减 1。 当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作) , 互斥量则变为开锁状态, 等待在该互斥量上的任务将被唤醒。 如果任务的优先级被互斥量的优先级翻转机制临时提升, 那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级。
 


 

 

 


 


 

<think>好的,我现在需要回答用户关于二值信号量互斥信号量计数信号量的概念及区别的问题。首先,我得仔细理解用户的问题,明确他们想知道这三者的定义、区别以及应用场景。 首先,根据用户提供的引用内容,特别是引用[1][5],二值信号量的值只能是0或1,用于简单的同步或互斥。而互斥信号量则是一种特殊的二值信号量,但可能有所有权机制,防止优先级反转。引用[5]还提到信号量在资源长时间占用时更适用,而自旋锁适合短时间的情况,这可能帮助区分不同信号量的使用场景。 接下来,计数信号量,引用[1][4]提到,计数信号量可以管理多个资源实例,其值代表可用资源数。例如,当资源有多个时,每次申请信号量会减少计数,释放则增加。这需要解释清楚其工作原理,比如初始化时的最大值当前可用数。 然后,用户的问题具体问到了二值信号量互斥信号量的区别。根据引用[3],二值信号量可能在创建时就带有初始值,而互斥信号量可能有更复杂的机制,比如所有权,防止多次释放。需要强调互斥信号量用于互斥访问,而二值信号量可能更多用于同步。引用[5]中的例子,比如厕所的使用,可能帮助解释两者的不同应用。 关于计数信号量的工作原理,需要结合引用[4],说明它如何跟踪资源数,并处理多个线程同时访问的情况。比如,当计数为0时线程阻塞,直到有资源释放。 最后,应用场景方面,引用[5]提到信号量适合资源占用较久的场合,而中断中不能使用。需要指出二值信号量用于任务同步或简单互斥互斥信号量用于临界区保护,计数信号量用于资源池管理,比如连接池。 在整理这些点时,要注意结构清晰,分点说明每个信号量的定义、区别工作原理。同时,引用相关的引用内容来支持解释,比如提到二值信号量初始化时的不同情况引用[3],以及互斥信号量的所有权引用[5]。 可能还需要检查是否有混淆的地方,比如互斥信号量是否总是二值的,或者是否有其他特性。确保引用的资料准确,比如引用[5]中的例子是否适用于互斥信号量的解释。 现在需要将这些点组织成逻辑连贯的回答,确保每个部分都有对应的引用支持,并且语言通俗易懂,符合用户要求的中文回答。同时,生成的相关问题需要覆盖概念、区别应用,比如如何选择不同类型的信号量,或者实际中的使用注意事项。</think>### 二值信号量互斥信号量计数信号量的概念及区别 #### 1. 二值信号量(Binary Semaphore) - **定义**:二值信号量的值只能是 $0$ 或 $1$,通常用于简单的任务同步或资源互斥访问。例如,任务A完成任务后释放信号量,任务B等待该信号量以继续执行[^1]。 - **特点**: - 初始化时可能已包含一个信号量(如通过特定函数创建后直接可用)[^3]。 - 适用于简单的同步场景(如任务间通知)或单一资源的互斥访问。 - 无“所有权”概念,任何任务均可释放信号量。 #### 2. 互斥信号量(Mutex) - **定义**:互斥信号量二值信号量的特殊形式,专用于资源互斥访问,且具有“所有权”机制。例如,只有持有信号量的任务才能释放它[^5]。 - **特点**: - 解决优先级反转问题(通过优先级继承机制)。 - 严格保证资源独占性,避免重复释放导致的逻辑错误。 - 适用于临界区保护,如共享变的读写操作。 #### 3. 计数信号量(Counting Semaphore) - **定义**:计数信号量的值是非负整数,表示可用资源的数。例如,管理一个包含 $N$ 个资源的池(如数据库连接池)[^1][^4]。 - **工作原理**: - **申请资源**:调用 `sem_wait` 时,若值 $>0$ 则减 $1$;若 $=0$ 则阻塞等待[^2]。 - **释放资源**:调用 `sem_post` 时,值加 $1$,唤醒等待线程。 - 适用于多实例资源管理,如缓冲区空闲槽位计数。 --- ### 核心区别 | 特性 | 二值信号量 | 互斥信号量 | 计数信号量 | |--------------------|---------------------|----------------------|---------------------| | **取值范围** | $0$ 或 $1$ | $0$ 或 $1$ | 非负整数 | | **所有权** | 无 | 有(仅持有者可释放) | 无 | | **适用场景** | 同步或简单互斥 | 严格互斥 | 多资源池管理 | | **优先级继承** | 不支持 | 支持 | 不支持 | | **初始化值** | 可能为 $1$(预置) | 通常为 $1$ | 自定义(如 $N$) | --- ### 信号量在并发控制中的应用 1. **二值信号量**: - 任务同步:如任务A完成数据采集后通知任务B处理。 - 简单互斥:保护单例资源(如共享外设)。 2. **互斥信号量**: - 临界区保护:确保共享数据结构(如链表)的原子操作。 3. **计数信号量**: - 资源池管理:如线程池、内存池中空闲资源的分配[^4]。 --- ### 典型问题示例 1. **二值信号量初始化时为何可能已包含信号量?** - 某些实现(如CubeMX)在创建二值信号量时会自动释放一次,需注意与标准API(如 `xSemaphoreCreateBinary`)的区别。 2. **为何互斥信号量需要所有权?** - 防止非持有者误释放信号量,确保资源访问的严格串行化。 3. **计数信号量如何避免资源竞争?** - 通过原子操作增减计数值,保证资源分配的线程安全性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值