一、基本概念
1、定义:信号量是一种同步机制,用于控制对共享资源的访问或在任务之间传递信号。【队列发送的是任务的数据,信号量发送的是任务的状态】
2、类型:信号量分为两种:二值信号量、计数型信号量。
3、信号量的传递方法:和队列有点类似,但信号量的释放和获取以内部的计数值为标准。
【注:信号量资源数(计数值),释放信号量(计数值++),获取信号量(计数值--)。信号量的计数值有最大值,当最大值为1时,为二值信号量;当最大值>1时,为计数型信号量。】
4、可以将信号量的传输比喻成停车位:
5、信号量和队列的区别:
二、二值信号量
1、定义:
二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况。二值信号量通常用于互斥访问或任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,二值信号量更适合用于同步!二值信号量主要用于任务的同步!
【互斥访问:互斥访问是指在多任务环境中,确保多个任务不会同时访问某个共享资源的机制。这是为了防止数据竞争和不一致的问题。任务同步:任务同步是指协调多个任务的执行顺序,使得任务按照特定的顺序或在特定的条件下执行。】
2、二值信号量的API函数:(先释放,再获取。初始值为0!)
使用二值信号量的过程:创建二值信号量->释放二值信号量->获取二值信号量
(1)、创建二值信号量:
SemaphoreHandle_t xSemaphoreCreateBinary( void );
(2)、释放二值信号量:
释放信号量,不会发生阻塞,只需要传入对应的信号量句柄
BaseType_t xSemaphoreGive( xSemaphore );
(3)、获取二值信号量:
获取信号量,可能会发生阻塞,需要传入信号量句柄和阻塞时间。当阻塞时间设置成portMAX_DELAY最大延时时,会死等并且任务会被阻塞。
BaseType_t xSemaphoreTake( xSemaphore, xBlockTime );
三、计数值信号量
1、定义:
计数型信号量相当于队列长度大于1 的队列(信号量的上限值),因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。
2、计数值信号量的作用:
计数值信号量有两个作用:①事件计数;②资源管理
①事件计数:计算事件发生后的次数,释放信号量计数+1,获取信号量计数-1。在设置信号量初始值时,要设置为0!!!
②资源管理:表示信号量的有效资源数目,在设置信号量初始值时候,要设置为最大资源数目!!!
2、计数型信号量的API函数:
使用二值信号量的过程:
①事件计数:创建数值型信号量->释放数值型信号量->获取数值型信号量
②多资源管理:创建数值型信号量->获取数值型信号量->释放数值型信号量
【注:计数型信号量的释放和获取和二值信号量一样!】
(1)创建计数型信号量:
xSemaphoreCreateCounting(uxMaxCount, uxInitialCount );
(2)获取信号量当前的计数值:
uxSemaphoreGetCount( xSemaphore );
四、优先级翻转
1、定义:
高优先级的任务反而慢执行,低优先级的任务反而快执行。优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。在使用二值信号量的时候,经常会遇到优先级翻转的问题。
2、图解:
五、优先级继承
1、定义
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
优先级继承并不能完全消除优先级翻转带来的影响,但是可以降低优先级带来的影响。
2、优先级翻转和优先级继承的区别(对任务的实际影响):
二值信号量和互斥信号量的区别就是在于优先级翻转和优先级继承:使用二值信号量时,高优先级的阻塞时间要比互斥信号量的阻塞时间更长!
六、互斥信号量
1、定义:
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中! (优先级继承就是用来解决优先级翻转问题的!)。互斥信号量是对一个资源进行管理!
互斥信号量不可以用于中断服务函数,只能在任务中使用!
2、互斥信号量的API函数:(先获取,再释放。初始值为1!)
使用二值信号量的过程:创建二值信号量->获取二值信号量->释放二值信号量
【注:互斥信号量在创建时,已经释放了一次二值信号量,所以需要先获取,然后又再释放。】
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )