FreeRTOS中的互斥量(Mutex)是一种特殊的二值信号量,它支持互斥量所有权、递归访问以及防止优先级翻转的特性。在FreeRTOS中,互斥量用于实现对临界资源的独占式处理。
互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。
此外,持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问。在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。这是互斥量与一般的信号量的一个重要区别。
互斥量的典型应用场景:优先级反转
实验目的:实现优先级反转现象
优先级反转是指在某些操作系统中,由于任务调度和优先级分配的原因,高优先级的任务可能无法获得足够的CPU时间,而低优先级的任务反而占用大量CPU时间
具体实现:
创建3个Task(Task1、Task2、Task3)、1个二进制信号量、3个全局变量(cnt1、cnt2、cnt3)初始值为0
Task1:
Task2:
Task3:
实现代码:
#define mainDELAY_LOOP_COUNT 1000000
uint32_t cnt1 = 0;
uint32_t cnt2 = 0;
uint32_t cnt3 = 0;
uint8_t taskFlag = 0;
SemaphoreHandle_t SemaphoreHandle;
void vTask1( void *pvParameters )
{
/* 打印任务1的信息 */
printf( "Task1 start!\r\n");
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 表示Task1在运行 */
taskFlag = 1;
cnt1++;
printf( "Task1 Take!\r\n");
/* 获取二进制信号量 */
xSemaphoreTake(SemaphoreHandle, portMAX_DELAY);
for(cnt1 = 0; cnt1 < mainDELAY_LOOP_COUNT; cnt1++)
{
}
printf( "Task1 Give!\r\n");
/* 释放二进制信号量 */
xSemaphoreGive(SemaphoreHandle);
}
}
void vTask2( void *pvParameters )
{
vTaskDelay(150);
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 表示Task2在运行 */
taskFlag = 2;
printf( "Task2 Tack!\r\n");
}
}
void vTask3( void *pvParameters )
{
vTaskDelay(300);
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 表示Task3在运行 */
taskFlag = 3;
printf( "Task3 Tack!\r\n");
/* 获取二进制信号量 */
xSemaphoreTake(SemaphoreHandle, portMAX_DELAY);
cnt2++;
for(cnt3 = 0; cnt3 < mainDELAY_LOOP_COUNT; cnt3++)
{
}
printf( "Task3 Give!\r\n");
/* 释放二进制信号量 */
xSemaphoreGive(SemaphoreHandle);
}
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 100, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 100, NULL, 2, NULL);
xTaskCreate(vTask3, "Task 2", 100, NULL, 3, NULL);
/* 创建二进制信号量 */
SemaphoreHandle = xSemaphoreCreateBinary();
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
运行结果:
从测试结果来看:Task1运行后获取信号量,未来得及释放信号量时更高优先级的Task2开始运行,之后Task1没有机会释放信号量,Task3再运行时由于无法获取信号量一直处于阻塞状态,而优先级低于Task3的Task2一直处于运行状态,实现了优先级反转。
实验目的:使用互斥量解决优先级反转问题
原理分析:
1、Task1获取互斥量
2、Task1还未释放互斥量Task2开始运行,并一直运行,Task1没有机会释放互斥量
3、Task3运行,获取信号量时发现获取不到,使获取了互斥量的Task1继承自己的优先级(高于Task2)
4、Task1继续运行直到释放互斥量
5、Task3获取信号量,最高优先级的Task3得到运行
具体实现:
创建3个Task(Task1、Task2、Task3)、1个互斥量、3个全局变量(cnt1、cnt2、cnt3)初始值为0
Task1:
Task2:
Task3:
实现代码:
#define mainDELAY_LOOP_COUNT 1000000
uint32_t cnt1 = 0;
uint32_t cnt2 = 0;
uint32_t cnt3 = 0;
uint8_t taskFlag = 0;
SemaphoreHandle_t SemaphoreValue;
//SemaphoreHandle_t SemaphoreHandle;
void vTask1( void *pvParameters )
{
/* 打印任务1的信息 */
printf( "Task1 start!\r\n");
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 表示Task1在运行 */
taskFlag = 1;
cnt1++;
printf( "Task1 Take!\r\n");
/* 获取互斥量 */
xSemaphoreTake(SemaphoreValue, portMAX_DELAY);
/* 获取二进制信号量 */
//xSemaphoreTake(SemaphoreHandle, portMAX_DELAY);
for(cnt1 = 0; cnt1 < mainDELAY_LOOP_COUNT; cnt1++)
{
}
printf( "Task1 Give!\r\n");
/* 释放互斥量 */
xSemaphoreGive(SemaphoreValue);
/* 释放二进制信号量 */
//xSemaphoreGive(SemaphoreHandle);
}
}
void vTask2( void *pvParameters )
{
vTaskDelay(150);
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 表示Task2在运行 */
taskFlag = 2;
printf( "Task2 Tack!\r\n");
}
}
void vTask3( void *pvParameters )
{
vTaskDelay(300);
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 表示Task3在运行 */
taskFlag = 3;
printf( "Task3 Tack!\r\n");
/* 获取互斥量 */
xSemaphoreTake(SemaphoreValue, portMAX_DELAY);
/* 获取二进制信号量 */
//xSemaphoreTake(SemaphoreHandle, portMAX_DELAY);
cnt2++;
for(cnt3 = 0; cnt3 < mainDELAY_LOOP_COUNT; cnt3++)
{
}
printf( "Task3 Give!\r\n");
/* 释放互斥量 */
xSemaphoreGive(SemaphoreValue);
/* 释放二进制信号量 */
//xSemaphoreGive(SemaphoreHandle);
}
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 100, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 100, NULL, 2, NULL);
xTaskCreate(vTask3, "Task 2", 100, NULL, 3, NULL);
/* 创建互斥量 */
SemaphoreValue = xSemaphoreCreateMutex();
/* 创建二进制信号量 */
//SemaphoreHandle = xSemaphoreCreateBinary();
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
运行结果:
从运行结果看,将信号量换成互斥量能够解决优先级反转问题。