目录
1. 优先级翻转
2. 优先级翻转实验
2.1 实验目的
2.2 实验设计
2.3 实验程序
2.4 现象
在使用二值信号量的时候会遇到一个很常见的问题——优先级翻转。优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果!!!
(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,导致优先级翻转。
在使用二值信号量的时候会存在优先级翻转的问题,本实验通过模拟的方式实现优先级翻转,观察优先级翻转对抢占式内核的影响。
本实验设计了四个任务:start_task、high_task、middle_task、low_task,这四个任务的任务功能如下:
start_task:用来创建其他的三个任务。
high_task:高优先级任务,会获取二值信号量,获取成功以后会进行相应的处理,处理完成以后就会释放二值信号量。
middle_task:中等优先级任务,一个简单的应用任务。
low_task:低优先级任务,和高优先级任务一样,会获取二值信号量,获取成功以后会进行相应的处理,不过不同之处在于低优先级的任务占用二值信号量的时间要久一点(软件模拟占用)。
实验中创建了一个二值信号量 BinarySemaphore,高优先级和低优先级这两个任务会使用这个二值信号量。
#include "stm32f4xx.h"
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h" //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"
//任务优先级
#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 };
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init(168);
uart_init(115200);
LED_Init();
KEY_Init();
BEEP_Init();
LCD_Init();
my_mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR=RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
LCD_ShowString(30,50,200,16,16,"Priority Overturn");
LCD_ShowString(30,70,200,16,16,"ATM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2023/10/08");
//创建开始任务
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(); //进入临界区
//创建二值信号量
//默认创建的二值信号量是无效的,这里需要先调用函数 xSemaphoreGive释放一次二值信号量。
//否则任务high_task和low_task都获取不到信号量
BinarySemaphore=xSemaphoreCreateBinary(); //创建二值信号量函数,返回创建成功的二值信号量句柄
//二值信号量创建成功以后先释放一下,因为默认创建的二值信号量是空的,释放二值信号量,先让这个长度为1的队列有效
if(BinarySemaphore!=NULL) //如果创建成功的二值信号量不为空,那么将二值信号量释放一下其他任务才能使用
{
xSemaphoreGive(BinarySemaphore); //释放二值信号量函数
}
//创建高优先级任务
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 Semaphore\r\n");
xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取二值信号量
//获取二值信号量的阻塞时间设置为无限等待,既然这个任务可以获取二值信号量,那么总有一个时刻可以获取到二值信号量
//否则程序就会卡在这里
printf("high task Running!\r\n"); //获取到二值信号量,高优先级任务开始运行
LCD_Fill(6,131,114,313,lcd_discolor[num%14]); //填充区域
LED1=!LED1;
xSemaphoreGive(BinarySemaphore); //释放二值信号量,当高优先级任务获取二值信号量完成相应的处理之后,就会释放掉信号量
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); //获取二值信号量
printf("low task Running!\r\n");
for(times=0;times<20000000;times++) //模拟低优先级占用二值信号量
{
taskYIELD(); //发起任务调度
//这也就保证了低优先级任务占用二值信号量的时间更长
//因为我一旦发起了任务调度,低优先级抢占的这个二值信号量是不能被高优先级的任务所抢占的
}
xSemaphoreGive(BinarySemaphore); //释放二值信号量
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
LCD ID:5510
middle task Running!
low task Running! (1)
high task Pend Sem (2)
middle task Running! (3)
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
middle task Running!
high task Running! (4)
middle task Running!
high task Pend Sem
(1)low_task 任务获得到二值信号量 BinarySemaphore 开始运行!
(2)high_task 获取信号量 BinarySemaphore,但是此时信号量 BinarySemaphore 被任务 low_task 占用着,因此 high_task 就要一直等待,直到 low_task 任务释放信号量 BinarySemaphore。
(3)由于 high_task 没有获取到信号量 BinarySemaphore,只能一直等待,所以一直在运行 middle task Running! ,给人的感觉是 middle_task 的任务优先级高于 high_task。但是事实上 high_task 任务的任务优先级是高于 middle_task 的,这就是任务优先级翻转!!!
(4)high_task 任务因为获取到了信号量 BinarySemaphore 而运行!!!
当一个低优先级任务和一个高优先级任务同时使用一个信号量时,并且系统中还有其他中等优先级任务时。如果低优先级任务获取到了信号量,那么高优先级任务就会处于等待状态,但是,中等优先级任务可以打断低优先级任务而先于高优先级任务运行!,这就是任务级翻转现象!!!
整个运行过程是这样的!!!
首先高优先级任务会延时500个时钟节拍,所以中优先级任务首先开始运行,这也就是串口助手首先打印 middle task Running!的原因,中等优先级任务while循环一次之后,时间片轮转,低优先级任务会获取二值信号量开始运行(这里解释一下为什么不是中等优先级任务运行完之后为什么不是高优先级任务先运行,这是因为低优先级任务会首先获取二值信号量,所以首先开始运行,高优先级任务还在延迟),串口助手首先打印 low task Running!开启任务调度,此时高优先级任务延时时间到了之后,串口助手首先打印 high task Pend Sem!,但是由于二值信号量还在低优先级任务中,所以无法开始运行,此时中优先级任务抢占CPU的使用权,开始一直打印 middle task Running!等到低优先级任务的循环结束,跳出for循环,释放二值信号量,此时高优先级任务获取二值信号量,高优先级任务开始运行!
既然任务级翻转是个不好的现象,那么如何避免这个现象————此时引出互斥信号量的概念!