1.信号量的简介
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。
-
信号量:用于传递状态(区别于队列传递消息)
-
信号量的计数值都有限制:限定最大值。
-
如果最大值被限定为1,那么它就是二值信号量 ;如果最大值不是1,它就是计数型信号量 。
-
当计数值大于0,代表有信号量资源
-
当释放信号量,信号量计数值(资源数)加一
-
当获取信号量,信号量计数值(资源数)减一
2.二值信号量
二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况,这就是二值 。二值信号量通常用于互斥访问或任务同步 , 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,所以二值信号量更适合用于同步
使用二值信号量的过程:创建二值信号量->释放二值信号量->获取二值信号量
- xSemaphoreCreateBinary():使用动态方式创建二值信号量
- xSemaphoreCreateBinaryStatic():使用静态方式创建二值信号量
- xSemaphoreGive():释放信号量
- xSemaphoreGiveFromlSR():在中断中释放信号量
- xSemaphoreTake():获取信号量
- xSemaphoreTakeFromISR():在中断中获取信号量
- uxSemaphoreGetCount():获取信号量的计数值
2.1.创建二值信号量(动态)xSemaphoreCreateBinary()
cpp
SemaphoreHandle_t xSemaphoreCreateBinary(void)
#define xSemaphoreCreateBinary(void)
xQueueGenericCreate(1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量 */
- 形参:无
- 返回值:NULL,创建失败;其他值,创建成功返回二值信号量的句柄
2.2.释放信号量(通用)xSemaphoreGive()
cpp
BaseType_t xSemaphoreGive(xSemaphore)
#define xSemaphoreGive(xSemaphore)
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK)
#define semGIVE_BLOCK_TIME ( ( TickType_t ) 0U )
- 形参xSemaphore:要释放的信号量句柄
- 返回值:pdPASS,释放信号量成功;errQUEUE_FULL,释放信号量失败
- 注意:释放信号量相当于写队列,但是当计数最大时无法阻塞,返回错误
2.3.获取信号量(通用)xSemaphoreTake()
cpp
BaseType_t xSemaphoreTake( xSemaphore, xBlockTime )
#define xSemaphoreTake( xSemaphore, xBlockTime )
xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
TickType_t xTicksToWait )
- 形参xSemaphore:要获取的信号量句柄
- 形参xBlockTime:阻塞时间
- 返回值:pdTRUE,获取信号量成功;pdFALSE,超时,获取信号量失败
- 注意:获取信号量相当于读队列,同样可以阻塞
2.4.获取信号量的计数值(通用)uxSemaphoreGetCount()
cpp
UBaseType_t uxSemaphoreGetCount(xSemaphore)
#define uxSemaphoreGetCount(xSemaphore)
uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
- 形参xSemaphore:信号量句柄
- 返回值:整数,当前信号量计数值的大小
2.5.二值信号量实验
- 实验目的:学习 FreeRTOS 的二值信号量相关API函数的使用
- 实验设计:将设计三个任务:start_task、task1、task2
start_task-用来创建task1和task2任务
task1-用于按键扫描,当检测到按键KEY0被按下时,释放二值信号量
task2-获取二值信号量,当成功获取后打印提示信息
3.计数型信号量
计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的计数型信号量适用场合:
- 事件计数 :当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其他任务
会获取计数型信号量(计数值-1) ,这种场合一般在创建时将初始计数值设置为 0 - 资源管理 :信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1 )才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目
API函数:
- xSemaphoreCreateCounting() :使用动态方式创建计数信号量
- xSemaphoreCreateCountingStatic() :使用静态方式创建计数信号量
- xSemaphoreGive():释放信号量
- xSemaphoreGiveFromlSR():在中断中释放信号量
- xSemaphoreTake():获取信号量
- xSemaphoreTakeFromISR():在中断中获取信号量
- uxSemaphoreGetCount():获取信号量的计数值
3.1.创建计数信号量(动态)xSemaphoreCreateCounting()
cpp
#define xSemaphoreCreateCounting( uxMaxCount , uxInitialCount )
xQueueCreateCountingSemaphore( ( uxMaxCount ) , ( uxInitialCount ) )
- 形参uxMaxCount:计数值的最大值限定
- 形参uxInitialCount:计数值的初始值
- 返回值:NULL,创建失败;其他值,创建成功返回计数型信号量的句柄
3.2.计数型信号量实验
- 实验目的:学习 FreeRTOS 的计数型信号量相关API函数的使用
- 实验设计:将设计三个任务:start_task、task1、task2
start_task-用来创建task1和task2任务
task1-用于按键扫描,当检测到按键KEY0被按下时,释放计数型信号量
task2-每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值
4.优先级翻转简介
优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。 在使用二值信号量的时候,经常会遇到优先级翻转的问题。
解释:高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度。但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)
5.互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
优先级继承 :当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
此时任务H的阻塞时间仅仅是任务L 的执行时间,将优先级翻转的危害降到了最低(注意没有消除)
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响 注意,互斥信号量不能用于中断服务函数中,原因如下:
- 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
API函数:
- xSemaphoreCreateMutex() :使用动态方式创建互斥信号量
- xSemaphoreCreateMutexStatic() :使用静态方式创建互斥信号量
- xSemaphoreGive():释放信号量
- xSemaphoreTake():获取信号量
- uxSemaphoreGetCount():获取信号量的计数值
【注意:使用互斥信号量:首先将宏configUSE_MUTEXES置一】
5.1.使用动态方式创建互斥信号量xSemaphoreCreateMutex()
cpp
#define xSemaphoreCreateMutex(void) xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
- 返回值:NULL,创建失败;其他值,创建成功返回计数型信号量的句柄
- 注意:创建互斥信号量时,会主动释放一次信号量
6.递归互斥信号量
递归互斥信号量可以看作是特殊的互斥信号量,与互斥信号量不同的是,递归互斥信号量在被获取后,可以被其持有者重复获取,当然递归互斥信号量的持有者需要释放递归互斥信号量与之获取递归互斥信号量相同的次数,递归互斥信号量才算被释放。
递归互斥信号量与互斥信号量一样,也具备优先级继承机制,因此也不能在中断服务函数中使用递归互斥信号量。
7.总结对比:队列、二值信号量、数值信号量、互斥信号量
二值信号量 | 数值信号量 | 互斥信号量 | 总结 | |
---|---|---|---|---|
创建 | xSemaphoreCreateBinary() xSemaphoreCreateBinaryStatic() | xSemaphoreCreateCounting() xSemaphoreCreateCountingStatic() | xSemaphoreCreateMutex() xSemaphoreCreateMutexStatic() | 创建函数不同 底层均为队列创建函数 |
释放 | xSemaphoreGive() xSemaphoreCreateBinaryStatic() | xSemaphoreGive() xSemaphoreCreateBinaryStatic() | xSemaphoreGive() | 释放函数相同 互斥信号量无中断释放 |
获取 | xSemaphoreTake() xSemaphoreTakeFromISR() | xSemaphoreTake() xSemaphoreTakeFromISR() | xSemaphoreTake() | 获取函数相同 互斥信号量无中断获取 |
取值 | uxSemaphoreGetCount() | uxSemaphoreGetCount() | uxSemaphoreGetCount() | 获取值函数相同 |
- 二值信号量、计数信号量、互斥信号量的创建函数不同,但是释放和获取均相同,此外注意,互斥信号量在中断中无法使用,所有没有中断中释放/获取信号量
- 二值信号量、计数信号量、互斥信号量的创建函数不同,但是底层调用的都是同一个API(队列创建函数),只是内部机制不同、某些参数不同,创建的信号量储存结构只有结构体,没有队列项,依靠变量uxMessagesWaiting储存信号量信息
- 二值信号量、计数信号量、互斥信号量的释放信号量通用,底层调用与队列写入函数相同,只是参数不同
- 二值信号量、计数信号量、互斥信号量的获取信号量通用,底层调用与队列写入函数不相同(类似),无buf参数