【FreeRTOS】RTOS任务的同步与互斥:(三)互斥量
同步与互斥是学习FreeRTOS的重点内容,同步与互斥的相关的内容可参考:
同步与互斥的概念:
-
同步: A等待B做完某件事
-
互斥: 某一资源同一时间仅能有一个用户访问
-
RTOS同步与互斥的方式: 队列(queue)、信号量(semaphoe)、互斥量(mutex)、事件组(event group)、任务通知(task notification)等
互斥量相关理论
互斥量的概念
在RTOS中,互斥量(Mutex)是实现互斥的一种常见机制。互斥量是一种特殊的变量,它有两个状态:锁定和解锁。只能拥有互斥量的任务可以对共享资源进行操作,其他任务需要等待互斥量被解锁才能访问资源。任务可以通过申请互斥量来获取对共享资源的独占访问权,一旦任务使用完共享资源,它可以释放互斥量,让其他任务可以使用资源。
在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上二值型信号量用于同步,而互斥型信号量用于资源保护。互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转现象。
优先级翻转
以下图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L通过信号量机制,共享资源。目前任务L占有资源,锁定了信号量,Task H运行后将被阻塞,直到Task L释放信号量后,Task H才能够退出阻塞状态继续运行。但是Task H在等待Task L释放信号量的过程中,中等优先级任务M抢占了任务L,从而延迟了信号量的释放时间,导致Task H阻塞了更长时间,这种现象称为优先级倒置或反转。
优先级继承: 当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
互斥量相关 API 函数
互斥信号量不能用于中断服务函数中!!!
互斥量的创建
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量 |
函数原型
SemaphoreHandle_t xSemaphoreCreateMutex( void )
返回值:
- 成功,返回对应互斥量的句柄;
- 失败,返回 NULL 。
互斥量的释放和获取与二值信号量完全相同 !!!
释放互斥量
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )
参数:
xSemaphore
:要释放的信号量句柄
返回值:
- 成功,返回
pdTRUE
; - 失败,返回 errQUEUE_FULL
获取互斥量
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
参数:
- xSemaphore:要获取的信号量句柄
- xTicksToWait:超时时间,
0
表示不超时,portMAX_DELAY
表示卡死等待;
返回值:
- 成功,返回
pdTRUE
; - 失败,返回 errQUEUE_FULL 。
互斥量实验设计
CubeMX工程的建立与移植建立在基础工程上,FreeRTOS工程的创建详情请参考:使用CubeMX快速移植FreeRTOS工程到蓝桥杯开发板
1. 优先级翻转实验缺陷
CubeMX配置
创建三个优先级不同的任务,分别表示TashH,TaskM以及TaskL
创建一个二值信号量
软件程序设计
-
任务H、任务L:获取信号量,并进行一定的资源消耗(这里采用HAL_Dealy延时模拟,),执行完毕后释放信号量
-
任务M:任务M参与对信号量的争夺,只做每隔1秒消耗CPU资源
void startTaskH(void const * argument)
{
while(1)
{
if(xSemaphoreTake(binarySem1Handle,portMAX_DELAY) == pdTRUE)
{
printf("TaskH 获取信号量成功,占用CPU中\r\n\r\n");
HAL_Delay(1000);
printf("TaskH 释放信号量\r\n\r\n");
xSemaphoreGive(binarySem1Handle);
}
else
printf("TaskH 获取信号量失败\r\n\r\n");
osDelay(1000);
}
}
void startTaskM(void const * argument)
{
while(1)
{
printf("TaskM 运行, 仅占用CPU资源\r\n\r\n");
osDelay(1000);
}
}
void startTaskL(void const * argument)
{
while(1)
{
if(xSemaphoreTake(binarySem1Handle,portMAX_DELAY) == pdTRUE)
{
printf("TaskL 获取信号量成功,占用CPU中\r\n\r\n");
HAL_Delay(3000);
printf("TaskL 释放信号量\r\n\r\n");
xSemaphoreGive(binarySem1Handle);
}
else
printf("TaskH 获取信号量失败\r\n\r\n");
osDelay(1000);
}
}
实验测试结果
对比优先级翻转与串口调试信息进行对比分析,其中红框部分则为优先级翻转的实际运行效果
为了解决这一问题 ,保证某个任务的连续执行,不被任何任务所打断,需要使用互斥量进行对临界资源的保护,只需将这里的信号量更换成互斥量即可。
2. 使用互斥量优化优先级翻转
CubeMX配置
创建三个优先级不同的任务,分别表示TashH,TaskM以及TaskL
创建一个互斥量,用于对临界资源的保护
软件程序设计
程序设计与上述一致,只需将信号量句柄改成互斥量句柄即可。
void startTaskH(void const * argument)
{
while(1)
{
if(xSemaphoreTake(MutexHandle,portMAX_DELAY) == pdTRUE)
{
printf("TaskH 获取互斥量成功,占用CPU中\r\n\r\n");
HAL_Delay(1000);
printf("TaskH 释放互斥量\r\n\r\n");
xSemaphoreGive(MutexHandle);
}
else
printf("TaskH 获取互斥量失败\r\n\r\n");
osDelay(1000);
}
}
void startTaskM(void const * argument)
{
while(1)
{
printf("TaskM 运行, 仅占用CPU资源\r\n\r\n");
osDelay(1000);
}
}
void startTaskL(void const * argument)
{
while(1)
{
if(xSemaphoreTake(MutexHandle,portMAX_DELAY) == pdTRUE)
{
printf("TaskL 获取互斥量成功,占用CPU中\r\n\r\n");
HAL_Delay(3000);
printf("TaskL 释放互斥量\r\n\r\n");
xSemaphoreGive(MutexHandle);
}
else
printf("TaskH 获取互斥量失败\r\n\r\n");
osDelay(1000);
}
}
实验测试结果
通过互斥量很好的对某个任务或代码段进行临界资源的保护,避免出现优先级翻转的现象