一、优先级翻转
(1) 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。
(2) 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。
(3) 任务 L 获得信号量并开始使用该共享资源。
(4) 由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。
(5) 任务 H 开始运行。
(6) 任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。
(7) 任务 L 继续运行。
(8) 由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生 后,任务 M 剥夺了任务L 的 CPU 使用权。
(9) 任务 M 处理该处理的事。
(10) 任务 M 执行完毕后,将 CPU 使用权归还给任务 L。
(11) 任务 L 继续运行。
(12) 最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换。
(13) 任务 H 得到该信号量并接着运行。
在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。
二、编程实战
1、实验目的
在使用二值信号量的时候会存在优先级翻转的问题,本实验通过模拟的方式实现优先级翻转,观察优先级翻转对抢占式内核的影响。
2、实验设计
本实验设计四个任务:start_task、high_task 、middle_task ,low_task,这四个任务的任务
功能如下:
start_task:用来创建其他 3 个任务。
high_task :高优先级任务,会获取二值信号量,获取成功以后会进行相应的处理,处理完成以后就会释放二值信号量。
middle_task :中等优先级的任务,一个简单的应用任务。
low_task:低优先级任务,和高优先级任务一样,会获取二值信号量,获取成功以后会进行相应的处理,不过不同之处在于低优先级的任务占用二值信号量的时间要久一点(软件模拟占用)。
实验中创建了一个二值信号量 BinarySemaphore,高优先级和低优先级这两个任务会使用这个二值信号量。
3、实验程序与分析
●任务设置
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define LOW_TASK_PRIO 2 //任务优先级
#define LOW_STK_SIZE 256 //任务堆栈大小
TaskHandle_t LowTask_Handler; //任务句柄
void low_task(void *pvParameters); //任务函数
#define MIDDLE_TASK_PRIO 3 //任务优先级
#define MIDDLE_STK_SIZE 256 //任务堆栈大小
TaskHandle_t MiddleTask_Handler; //任务句柄
void middle_task(void *pvParameters); //任务函数
#define HIGH_TASK_PRIO 4 //任务优先级
#define HIGH_STK_SIZE 256 //任务堆栈大小
TaskHandle_t HighTask_Handler; //任务句柄
void high_task(void *pvParameters); //任务函数
//二值信号量句柄
SemaphoreHandle_t BinarySemaphore;//二值信号量
//LCD 刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW, BROWN,
BRRED, GRAY };
● main()函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
BEEP_Init(); //初始化蜂鸣器
LCD_Init(); //初始化 LCD
my_mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F103/407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 14-3");
LCD_ShowString(30,50,200,16,16,"Priority Overturn");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2016/11/25");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
● 任务函数
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建二值信号量
BinarySemaphore=xSemaphoreCreateBinary(); (1)
//二值信号量创建成功以后要先释放一下
if(BinarySemaphore!=NULL)xSemaphoreGive(BinarySemaphore); (2)
//创建高优先级任务
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&HighTask_Handler);
//创建中等优先级任务
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )MIDDLE_STK_SIZE,
(void* )NULL,
(UBaseType_t )MIDDLE_TASK_PRIO,
(TaskHandle_t* )&MiddleTask_Handler);
//创建低优先级任务
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&LowTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//高优先级任务的任务函数
void high_task(void *pvParameters)
{
u8 num;
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"High Task");
while(1)
{
vTaskDelay(500); //延时 500ms,也就是 500 个时钟节拍
num++;
printf("high task Pend Sem\r\n");
xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取二值信号量 (3)
printf("high task Running!\r\n");
LCD_Fill(6,131,114,313,lcd_discolor[num%14]); //填充区域
LED1=!LED1;
xSemaphoreGive(BinarySemaphore); //释放信号量 (4)
vTaskDelay(500); //延时 500ms,也就是 500 个时钟节拍
}
}
//中等优先级任务的任务函数
void middle_task(void *pvParameters)
{
u8 num;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Middle Task");
while(1)
{
num++;
printf("middle task Running!\r\n");
LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]); //填充区域
LED0=!LED0;
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
//低优先级任务的任务函数
void low_task(void *pvParameters)
{
static u32 times;
while(1)
{
xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取二值信号量 (5)
printf("low task Running!\r\n");
for(times=0;times<20000000;times++) //模拟低优先级任务占用二值信号量 (6)
{
taskYIELD(); //发起任务调度
}
xSemaphoreGive(BinarySemaphore); //释放二值信号量 (7)
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
(1)、调用函数 xSemaphoreCreateBinary()创建二值信号量。
(2)、默认创建的二值信号量是无效的,这里需要先调用函数 xSemaphoreGive()释放一次二值信号量。否则任务 high_task()和 low_task()都会获取不到信号量。
(3)、高优先级任务调用函数 xSemaphoreTake()获取二值信号量。
(4)、使用完以后需要调用函数 xSemaphoreGive()释放二值信号量。
(5)、低优先级任务获取二值信号量 BinarySemaphore。
(6)、低优先级任务模拟长时间占用二值信号量。
(7)、低优先级任务释放二值信号量。
程序运行结果分析
(1)、low_task 任务获取到二值信号量 BinarySemaphore 开始运行。
(2)、high_task 获取信号量 BinarySemaphore,但是此时信号量 BinarySemaphore 被任务low_task 占用着,因此 high_task 就要一直等待,直到 low_task 任务释放信号量 BinarySemaphore。
(3)、由于 high_task 没有获取到信号量 BinarySemaphore,只能一直等待,红色部分代码中high_task 没有运行,而 middle_task 一直在运行,给人的感觉就是 middle_task 的任务优先级高于 high_task。但是事实上 high_task 任务的任务优先级是高于 middle_task 的,这个就是优先级反转!
(4)、high_task 任务因为获取到了信号量 BinarySemaphore 而运行.