一、信号量简介
前面介绍的消息队列主要用于传输数据:任务与任务之间、任务与中断之间
在有些情况下,不需要传输数据,只需要传递状态即可
• 车开出停车位,你的车可以停进来了
• 课已经录制完成,你可以进行观看了
1.1 信号量的含义
信号量是一种实现任务间通信的机制, 可以实现任务之间的同步或临界资源的互斥访问,可以实现对共享资源的有序访问(用于传递状态)
共享资源的访问:
※ 案列一:汽车驶入或离开停车位,停车位的个数(计数型信号量)
※案列二:公共电话的使用(二值信号量)
任务之间的同步(任务与任务、任务与中断):
在执行中断服务函数的时候,可以通过释放信号量(不做具体的处理,以提高系统的实时性)来通知某个任务所期待的事件发生了。当退出中断后,通过调度器,同步的任务(做出相应的处理)就会执行
信号量用于控制共享资源的访问的场景相当于一个上锁机制,只有获得这把锁的钥匙才可以进行下一步操作
Q:既然队列也可以实现同步与互斥那为什么还要信号量?
答:信号量相比队列更节省空间,因为实现同步与互斥不需要传递数据,所以信号量没有队列后面的环形存储区,信号量主要就是依靠计数值uxMessagesWaiting(在队列中表示队列现有消息个数,在信号量中表示有效信号量个数)。
当计数值 > 0,代表信号量有资源
当释放信号量,信号量计数值(资源数)加一
当获取信号量,信号量计数值(资源数)减一
信号:通知某个对象
量:资源的数量:计数值都有限制
• 限定最大值是1,则是二进制信号量
• 限定最大值不是1,则是计数值信号量
FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量
总结:信号量是用于传递状态
1.2 队列与信号量的对比
|------------------------------------|--------------------------------------------|
| 队列 | 信号量 |
| 可以容纳多个数据; 创建队列有两部分内存:队列结构体+队列项存储空间 | 仅存放 计数值,无法存放其他数据; 创建信号量,只需分配信号量结构体 |
| 写入队列:当队列满时,可阻塞; | 释放信号量:不可阻塞,计数值++, 当计数值为最大值时,返回失败 |
| 读取队列:当队列为空时,可阻塞; | 获取信号量:计数值--, 当没有资源时,可阻塞 |
1.3 二值信号量与计数型信号量的区别
|-------|-------|----------|
| | 二值信号量 | 计数型信号量 |
| 队列长度 | 1 | 大于1 |
| 资源初始化 | 0 | 可以根据情况设定 |
二、二值信号量
二值信号量的本质:一个队列长度为 1 ,队列项大小为0的队列 ,该队列就只有空和满两种情况
二值信号量通常用于互斥访问或任务同步, 与互斥信号量(拥有优先级继承)比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,所以二值信号量更适合用于同步(任务与任务、任务与中断)
同步:所有任务排着队一件件的往后进行,上件事情没有完成,就继续做上件事情,等上件事情完成后才会去做下一件事情
使用二值信号量的过程:创建二值信号量 ------> 释放二值信号量(队满) ------> 获取二值信号量(队空)
注:创建二值信号量时,初始值为0,所以先要释放信号量
2.1 二值信号量相关API函数
头文件:semphr. h
|--------------------------------|---------------|
| 函数 | 描述 |
| xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
| xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
| xSemaphoreGive() | 释放信号量 |
| xSemaphoreGiveFromISR() | 在中断中释放信号量 |
| xSemaphoreTake() | 获取信号量 |
| xSemaphoreTakeFromISR() | 在中断中获取信号量 |
注:二值、计数型、互斥信号量释放与获取均用这两个函数
2.1.1 动态创建二值信号量:
实质:创建一个长度为1、队列项大小为0的队列
2.1.2 释放信号量函数:
※ 对于信号量而言,释放 不允许设置阻塞时间,如果队列已满,则直接返回
入队方式:向后入队
2.1.3 获取信号量函数:
※ 对于信号量而言,获取 允许设置阻塞时间,如果队列为空,则可以选择是否进行等待
可以看出:二值信号量的API函数与队列操作的函数相同,区别只是入口参数不同
三、计数型信号量
计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的
计数型信号量常用于:事件计数、资源管理
注:不同适用场合,信号量创建时的计数值不同
3.1 计数值信号量相关API函数
|----------------------------------|-----------------|
| 函数 | 描述 |
| xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
| xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
| uxSemaphoreGetCount() | 获取信号量的计数值 |
计数型信号量的释放和获取与二值信号量相同 !
3.1.1 动态创建计数值信号量
3.1.2 获取当前计数值
四、互斥信号量(mutex)
4.1 优先级反转:
前面说到,二值信号量用于互斥访问 时,会出现优先级反转的情况
优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行
高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度。但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)
4.2 互斥信号量
互斥信号量:互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
4.2.1 优先级继承
优先级继承:暂时提高 某个占有某种资源的低优先级任务的优先级 ,使之与在所有等待该资源 的任务中优先级最高那个任务的优先级相等 ,而当这个低优先级任务执行完毕释放该资源 时,优先级重新回到初始设定值
当任务L获取信号量后,还没来得及释放,就被高优先级的任务抢占,但没有信号量,高优先级任务H就进入阻塞,此时任务L运行,并将任务L的优先级设置为与任务H同等优先级,即使任务M就绪,也不会得到执行,直到L释放信号量,H获取信号量,任务H开始运行
此时任务H的阻塞时间仅仅是任务L 的执行时间,将优先级翻转的危害降到了最低。优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
总结:
所谓优先级继承,其实就是低优先级任务继承高优先级任务的优先级
发生优先级继承:在高优先级任务获取信号量失败(低优先级任务已经持有信号量) ,进入阻塞态之前,将持有信号量的低优先级任务的优先级提升
解除优先级继承:在持有信号量的低优先级任务释放信号量的时候,将自己的优先级恢复到初始值(因为已经释放了信号量,完成了任务)
4.3 互斥信号量相关API函数
使用流程:创建互斥信号量 ------> (task)获取信号量 ------>(give)释放信号量
注意:创建互斥信号量时,会主动释放一次信号量
使用互斥信号量:首先将宏configUSE_MUTEXES置1
|-------------------------------|----------------|
| 函数 | 描述 |
| xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
| xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
4.3.1 动态创建互斥信号量
互斥信号量的释放和获取函数与二值信号量相同 !
只不过互斥信号量由于涉及到任务优先级继承的性质,而中断不属于任务,没法处理中断由新阿基继承,因此不支持中断中调用
五、递归互斥信号量
5.1 互斥信号的缺点
(1)不能实现由A获取就由A释放
比如:任务A、B互斥访问串口打印资源,假如任务A已经获取,还没释放。此时,任务C偷偷释放,使得资源失去保护,任何任务都可以访问该资源,会发生混乱
(2)死锁
比如:任务A进行两次获取,第二次获取必定失败,导致任务被阻塞,其他任务想要获取,该任务还未释放,导致死锁
5.2 递归互斥信号量
递归互斥信号量是一种特殊的互斥信号量。已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量(即可以嵌套使用),且次数不限。一个任务获取了多少次递归互斥信号量就必须释放多少次,释放之前递归互斥量都处于无效状态,其他任务无法获取 ,只有持有递归信号量的任务才能获取和释放
递归互斥信号量不能在中断函数中使用
5.3 递归互斥信号量相关API函数
5.3.1 动态创建
使用条件:configSUPPORT_DYNAMIC_ALLOCATION 和 configUSE_RECURSIVE_mutexes 都必须在 FreeRTOSConfig.h 中定义为 1
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
返回值:
成功:返回创建的互斥锁的句柄
失败:返回 NULL
SemaphoreHandle_t xMutex;
void vATask( void * pvParameters )
{
Create a recursive mutex.
xMutex = xSemaphoreCreateRecursiveMutex();
if( xMutex != NULL )
{
/* The recursive mutex was created successfully and
can now be used. */
}
}
注:
使用流程:创建递归互斥信号量 ------> (task)获取信号量 ------>(give)释放信号量
注意:创建递归互斥信号量时,会主动释放一次信号量
5.3.2 获取递归互斥信号量
使用条件:configUSE_RECURSIVE_MUTEXES 设置为 1
xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex,
TickType_t xTicksToWait );
SemaphoreHandle_t xMutex = NULL;
// A task that creates a mutex.
void vATask( void * pvParameters )
{
// Create the mutex to guard a shared resource.
xMutex = xSemaphoreCreateRecursiveMutex();
}
// A task that uses the mutex.
void vAnotherTask( void * pvParameters )
{
// ... Do other things.
if( xMutex != NULL )
{
// See if we can obtain the mutex. If the mutex is not available
// wait 10 ticks to see if it becomes free.
if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
{
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
xSemaphoreGiveRecursive( xMutex );
xSemaphoreGiveRecursive( xMutex );
xSemaphoreGiveRecursive( xMutex );
// Now the mutex can be taken by other tasks.
}
else
{
// We could not obtain the mutex and can therefore not access
// the shared resource safely.
}
}
}
5.3.3 释放递归互斥信号量
使用条件:将 configUSE_RECURSIVE_MUTEXES 设置为 1
xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex )