优先级翻转:
高优先级的任务反而慢执行,低优先级的任务反而优先执行
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。
在使用二值信号量的时候,经常会遇到优先级翻转的问题。
高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度。但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)
H(高优先级) M(中优先级) L(低优先级)
优先级翻转实验
实验目的:在使用二值信号量的时候会存在优先级翻转的问题,本实验通过模拟的方式实现优先级翻转,观察优先级翻转对抢占式内核的影响
实验设计:将设计四个任务:start_task、high_task、 middle_task , low_task
四个任务的功能如下:
- start_task:用来创建其他的3个任务
- high_task:高优先级任务,会获取二值信号量,获取成功以后打印提示信息,处理完后释放信号量
- middle_task:中等优先级任务,简单的应用任务
- low_task:低优先级任务,同高优先级一样的操作,不同的是低优先级任务占用信号量的时间久一点
代码 :
SemaphoreHandle_t Semaphore; /* 计数型信号量 */
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建计数型信号量 */
Semaphore = xSemaphoreCreateCounting(1, 1);
/* 创建任务1 */
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务3 */
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
void high_task(void *pvParameters)
{
vTaskDelay(1000);
while (1)
{
printf("high_task 获取信号量\r\n");
xSemaphoreTake(Semaphore, portMAX_DELAY); /* 获取计数型信号量 */
printf("high_task 获取成功\r\n");
printf("high_task 正在运行\r\n");
printf("high_task 释放信号量\r\n");
xSemaphoreGive(Semaphore); /* 释放计数型信号量 */
vTaskDelay(10000);
}
}
void middle_task(void *pvParameters)
{
uint32_t middle_task_num = 0;
vTaskDelay(1000);
while (1)
{
for (middle_task_num=0; middle_task_num<5; middle_task_num++)
{
printf("middle_task 正在运行\r\n");
delay_ms(1000); /* 模拟运行,不触发任务调度 */
}
vTaskDelay(10000);
}
}
void low_task(void *pvParameters)
{
uint32_t low_task_num = 0;
while (1)
{
printf("low_task 获取信号量\r\n");
xSemaphoreTake(Semaphore, portMAX_DELAY); /* 获取计数型信号量 */
printf("low_task 获取成功\r\n");
for (low_task_num=0; low_task_num<5; low_task_num++)
{
printf("low_task 正在运行\r\n");
delay_ms(1000); /* 模拟运行,不触发任务调度 */
}
printf("low_task 释放信号量\r\n");
xSemaphoreGive(Semaphore); /* 释放计数型信号量 */
vTaskDelay(1000);
}
}
实验结果:
实验注意:延迟时间,确保有足够的延迟时间使中优先级任务和低优先级任务运行
互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
优先级继承:
当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
此时任务H的阻塞时间仅仅是任务L 的执行时间,将优先级翻转的危害降到了最低。
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
注意:互斥信号量不能用于中断服务函数中,原因如下:
- 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
互斥信号量相关API函数
使用互斥信号量:首先将 宏 configUSE_MUTEXES 置一
使用流程:创建互斥信号量(task)获取信号量 (give)释放信号量
创建互斥信号量函数:
函数 | 描述 |
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
返回值 | 描述 |
NULL | 创建失败 |
其他值 | 创建成功返回互斥信号量的句柄 |
此函数用于创建互斥信号量
#define xSemaphoreCreateMutex ( ) \
xQueueCreateMutex (queueQUEUE_TYPE_MUTEX )
互斥信号量的释放和获取函数与二值信号量相同 !只不过互斥信号量不支持中断中调用
注意:创建互斥信号量时,会主动释放一次信号量
互斥信号量实验
实验目的:在优先级翻转实验的基础,加入互斥信号量,解决优先级翻转问题
实验设计:将优先级翻转所用到的信号量函数,修改成互斥信号量即可,通过串口打印提示信息
代码:
整体和上份没有太大区别,仅是使用了 互斥信号量
SemaphoreHandle_t MutexSemaphore; /* 互斥信号量 */
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建互斥信号量 */
MutexSemaphore = xSemaphoreCreateMutex();
/* 创建任务1 */
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务3 */
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
void high_task(void *pvParameters)
{
vTaskDelay(1000);
while (1)
{
printf("high_task 获取信号量\r\n");
xSemaphoreTake(MutexSemaphore, portMAX_DELAY); /* 获取计数型信号量 */
printf("high_task 获取成功\r\n");
printf("high_task 正在运行\r\n");
printf("high_task 释放信号量\r\n");
xSemaphoreGive(MutexSemaphore); /* 释放计数型信号量 */
vTaskDelay(10000);
}
}
void middle_task(void *pvParameters)
{
uint32_t middle_task_num = 0;
vTaskDelay(1000);
while (1)
{
for (middle_task_num=0; middle_task_num<5; middle_task_num++)
{
printf("middle_task 正在运行\r\n");
delay_ms(1000); /* 模拟运行,不触发任务调度 */
}
vTaskDelay(10000);
}
}
void low_task(void *pvParameters)
{
uint32_t low_task_num = 0;
while (1)
{
printf("low_task 获取信号量\r\n");
xSemaphoreTake(MutexSemaphore, portMAX_DELAY); /* 获取计数型信号量 */
printf("low_task 获取成功\r\n");
for (low_task_num=0; low_task_num<5; low_task_num++)
{
printf("low_task 正在运行\r\n");
delay_ms(1000); /* 模拟运行,不触发任务调度 */
}
printf("low_task 释放信号量\r\n");
xSemaphoreGive(MutexSemaphore); /* 释放计数型信号量 */
vTaskDelay(1000);
}
}
实验结果 :
总结来说
FreeRTOS中的互斥信号量通过优先级继承机制有效地减少了优先级翻转的影响,从而提高了系统的实时性和响应性。这一机制使得低优先级任务在持有关键资源时,其优先级会被临时提升,以便尽快释放资源给高优先级任务,从而保证高优先级任务能尽快获得资源,减少不必要的延迟。