目录
一、互斥量的概念
二、优先级翻转和优先级继承
1、优先级翻转
2、优先级继承
三、互斥量相关API
四、优先级实操
1、优先级翻转演示
1.1 CubeMX配置
1.2 代码实现
2、使用互斥量优化优先级翻转问题
2.1 CubeMX配置
2.2 代码实现
一、互斥量的概念
在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上二值型信号量用于同步,而互斥型信号量用于资源保护,同时具有“优先级继承”的特性。(注:互斥量不能用于中断服务函数)
我们在学习二值信号量的时候知道,该类型信号量的计数值只有0和1,但是他有个特点是:任何任务都可以生产信号量和拿走信号量。举个不太恰当的例子:假设有一个停车位,现在有一辆车停在车位上,按理来说肯定是停车的人自己把车开走后别人才可以停在这吧?但如果把车位看作二值信号量的话,这时候别的车可以把停在车位的车推走,自己将车位空出来,然后给自己停或给别人停。此时就需要引入互斥量。
互斥量有一个特性,谁拿走这个互斥量只能由他释放,也就是只有进行take的任务才可以give互斥量,别的任务不能give。
互斥量也被称为互斥锁,使用过程如下:
1.在创建一个互斥量时,初始值为1
2.当任务A想访问临界资源,先获得并占有互斥量,然后开始访问
3.如果任务B也想访问临界资源,也要先获得互斥量。但已经被别人占有了,于是只能进行阻塞
4.当任务A使用完毕,释放互斥量。这时任务B被唤醒、得到并占有互斥量,然后开始访问
5.任务B使用完毕,释放互斥量
二、优先级翻转和优先级继承
1、优先级翻转
以上图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L通过信号量机制,共享资源。目前任务L占有资源,锁定了信号量,Task H运行后将被阻塞,直到Task L释放信号量后,Task H才能够退出阻塞状态继续运行。但是Task H在等待Task L释放信号量的过程中,中等优先级任务M抢占了任务L,也就是高优先级任务反而比中等优先级任务更晚执行,从而延迟了信号量的释放时间,导致Task H阻塞了更长时 间,这种现象称为优先级倒置或优先级反转(翻转)。(这里信号量翻转发生于二值信号量,所以后面的实操所创建的信号量应该是二值信号量)
2、优先级继承
当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任 务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。
这样做的目的是:
减少阻塞时间:确保低优先级任务可以快速完成对共享资源的使用,并释放互斥量。
让高优先级任务尽快执行:一旦低优先级任务释放了互斥量,高优先级任务就可以立即执行。
三、互斥量相关API
互斥信号量不能用于中断服务函数中!
函数 | 描述 |
xSemaphoreCreateMutex() | 使用动态方式创建互斥量 |
xSemaphoreCreateMutexStatic() | 使用静态方式创建互斥量 |
xSemaphoreGive() | 释放互斥量 |
xSemaphoreTake() | 获取互斥量 |
SemaphoreHandle_t xSemaphoreCreateMutex( void )
参数:
- 无
返回值:
- 成功,返回对应互斥量的句柄;
- 失败,返回 NULL 。
四、优先级实操
1、优先级翻转演示
1.1 CubeMX配置
这里已经将FreeRTOS移植到STM32F103C8T6,具体操作流程看前面的文章。
创建三个任务
优先级选项从上到下一次升高!
创建信号量
1.2 代码实现
uart.c 重定向printf
#include "stdio.h"
int fputc(int ch,FILE *f)
{
unsigned char temp[1] = {ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
需要打开魔术棒勾上红框内选项实现串口打印
打开freertos.c并添加代码
void StartTaskH(void const * argument)
{
for(;;)
{
xSemaphoreTake(myBinarySemHandle,portMAX_DELAY);
printf("高优先级获得二值信号量,开始工作\r\n");
HAL_Delay(1000);
printf("工作完毕后,释放二值信号量\r\n");
xSemaphoreGive(myBinarySemHandle);
osDelay(1000);
}
}
void StartTaskM(void const * argument)
{
for(;;)
{
printf("占用cpu资源,不工作\r\n");
osDelay(1000);
}
}
void StartTaskL(void const * argument)
{
for(;;)
{
xSemaphoreTake(myBinarySemHandle,portMAX_DELAY);
printf("低优先级获得二值信号量,开始工作\r\n");
HAL_Delay(3000);
printf("工作完毕后,释放二值信号量\r\n");
xSemaphoreGive(mmyBinarySemHandle);
osDelay(1000);
}
}
可见低优先级任务获得信号量时,高优先级想要获得信号量会进入阻塞状态,但此时中优先级会比高优先级先运行,会打断低优先级运行从而使得高优先级阻塞时间增长,当中优先级运行完毕,低优先级才继续运行,当低优先级释放信号量时,高优先级立刻获得信号量;同时当高优先级运行时(获取信号量时),中优先级或者低优先级无法打断。
注意:这里二值信号量的创建用的是CubeMX配置的内置函数,其中已经释放了信号量,所以低优先级任务和高优先级任务可以直接获取信号量再释放。(若是调用二值信号量原本的创建函数,则需要先释放再获取)
2、使用互斥量优化优先级翻转问题
2.1 CubeMX配置
复制上面的文件打开CubeMX配置
删除二值信号量
创建互斥量
2.2 代码实现
void StartTaskH(void const * argument)
{
for(;;)
{
xSemaphoreTake(myMutexHandle,portMAX_DELAY);
printf("高优先级获得互斥量,开始工作\r\n");
HAL_Delay(1000);
printf("工作完毕后,释放互斥量\r\n");
xSemaphoreGive(myMutexHandle);
osDelay(1000);
}
}
void StartTaskM(void const * argument)
{
for(;;)
{
printf("占用cpu资源,不工作\r\n");
osDelay(1000);
}
}
void StartTaskL(void const * argument)
{
for(;;)
{
xSemaphoreTake(myMutexHandle,portMAX_DELAY);
printf("低优先级获得互斥量,开始工作\r\n");
HAL_Delay(3000);
printf("工作完毕后,释放互斥量\r\n");
xSemaphoreGive(myMutexHandle);
osDelay(1000);
}
}
可见低优先级的任务工作后高优先级再进行工作,不会被中优先级打断。
注意:这里是调用CubeMX内置函数,其中已经存在互斥量,所以低优先级和高优先级可以直接获取,不需要先释放再获取。