1、优先级翻转的现象
在使用二值信号量的时候会遇到很常见的一个问题——优先级翻转,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果。
如下图所示,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。
序号 | 说明 |
---|---|
(1) | 任务H和任务M为阻塞状态,等待某一事件发生,此时任务L正在运行 |
(2) | 此时任务L要访问共享资源,因此需要获取信号量 |
(3) | 任务L成功获取信号量,此时信号量已无资源,任务L开始访问共享资源 |
(4) | 此时任务H就绪,抢占任务L运行 |
(5) | 任务H开始运行 |
(6) | 此时任务H要访问共享资源,因此需要获取信号量,但信号量已无资源,因此任务H阻塞等待信号量资源 |
(7) | 任务L继续运行 |
(8) | 此时任务M就绪,抢占任务L运行 |
(9) | 任务M正在运行 |
(10) | 任务M运行完毕,继续阻塞 |
(11) | 任务L继续运行 |
(12) | 此时任务L对共享资源的访问操作完成,释放信号量,任务H因成功获取信号量,解除阻塞并抢占任务L运行 |
(13) | 任务H得以运行 |
2、优先级翻转实验
2.1、实验设计
1、创建一个开始任务,在开始任务里面再新建一二值信号量,task 1(低), task 2(中), task3(高)三个任务后,删除开始任务;
2、task 1间隔 1000ms 获取一次二值信号量,并连续进行任务切换,模拟信号量被占用,随后释放信号量;
3、task 2间隔 1000ms 打印中间优先级的任务正在运行;
4、较task 1 延迟 500ms 获取信号量,此时信号量被task 1 占用,task 3 不能执行,而中优先级的 task 2 一直在执行
2.2、实验代码
//任务1
k_task_handle_t task1_handle; //任务1 句柄
#define TSK1_PRIO 2 //任务1 优先级
#define TASK1_STK_SIZE (1*512) //任务1 分配的堆栈大小
//任务2
k_task_handle_t task2_handle; //任务2 句柄
#define TSK2_PRIO 3 //任务2 优先级
#define TASK2_STK_SIZE (1*512) //任务2 分配的堆栈大小
//任务3
k_task_handle_t task3_handle; //任务3 句柄
#define TSK3_PRIO 4 //任务3 优先级
#define TASK3_STK_SIZE (1*512) //任务3 分配的堆栈大小
//开始任务
k_task_handle_t start_task_handle; //开始任务 句柄
#define START_TSK_PRIO 1 //开始任务 优先级
#define START_TSK_STK_SIZE 1024 //开始任务 分配的堆栈大小
#define TEST_TIME_QUANTA 100
k_sem_handle_t g_usSem; //信号量
void task1(void)
{
uint32_t i = 0;
while(1)
{
csi_kernel_sem_wait(g_usSem, portMAX_DELAY);
my_printf("Low task running!\r\n");
for(i=0;i<2000000;i++)
{
taskYIELD();
}
csi_kernel_sem_post(g_usSem);
csi_kernel_delay_ms(1000);
}
}
void task2(void)
{
while(1)
{
my_printf("Middle task running!\r\n");
csi_kernel_delay_ms(1000);
}
}
void task3(void)
{
while(1)
{
csi_kernel_delay_ms(500);
my_printf("High task Pend Semaphore\r\n");
csi_kernel_sem_wait(g_usSem, portMAX_DELAY);
my_printf("High task running!\r\n");
csi_kernel_sem_post(g_usSem);
csi_kernel_delay_ms(500);
}
}
void start_task(void)
{
//进入临界区
taskENTER_CRITICAL();
//创建二值信号量
g_usSem = csi_kernel_sem_new(1, 1);
if (g_usSem == NULL)
{
printf("fail to create semaphore.\n");
}
//创建task 1
csi_kernel_task_new((k_task_entry_t)task1, "task1", NULL, TSK1_PRIO, TEST_TIME_QUANTA, NULL, TASK1_STK_SIZE, &task1_handle);
if (task1_handle == NULL)
{
csi_kernel_sched_resume(0);
my_printf("Fail to create task 1!\r\n");
}
//创建task 2
csi_kernel_task_new((k_task_entry_t)task2, "task2", NULL, TSK2_PRIO, TEST_TIME_QUANTA, NULL, TASK2_STK_SIZE, &task2_handle);
if (task2_handle == NULL)
{
csi_kernel_sched_resume(0);
my_printf("Fail to create task 2!\r\n");
}
//创建task 3
csi_kernel_task_new((k_task_entry_t)task3, "task3", NULL, TSK3_PRIO, TEST_TIME_QUANTA, NULL, TASK3_STK_SIZE, &task3_handle);
if (task3_handle == NULL)
{
csi_kernel_sched_resume(0);
my_printf("Fail to create task 3!\r\n");
}
//删除开始任务
if(0 != csi_kernel_task_del(csi_kernel_task_get_cur()))
{
my_printf("Fail to delete start_task!\r\n");
}
else
{
my_printf("start_task is deleted!\r\n");
}
//退出临界区
taskEXIT_CRITICAL();
}
void freertos_demo(void)
{
my_printf("\r\n-->this is freertos task test demo!!!\r\n"); //print message
//系统初始化
csi_kernel_init();
//创建开始任务
csi_kernel_task_new((k_task_entry_t)start_task, "start_task", 0, START_TSK_PRIO, 0, 0, START_TSK_STK_SIZE, &start_task_handle);
//任务调度开始
csi_kernel_start();
}
2.3、实验现象
-
Task 3(High task)等待期间,Task 2(Middle task)已经运行了多次。
-
Task 2(Middle task)优先于Task 3(High task)运行,优先级翻转。
-
在实际应用中,我们需要避免优先级翻转这种情况,因为其不符合抢占式内核的特点,所以就有了互斥信号量。