FreeRTOS 优先级翻转

本文详细描述了在使用二值信号量时可能发生的优先级翻转问题,通过FreeRTOS示例展示了任务优先级如何在抢占式系统中发生变化,以及如何通过互斥信号量避免这种情况。

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)运行,优先级翻转。

  • 在实际应用中,我们需要避免优先级翻转这种情况,因为其不符合抢占式内核的特点,所以就有了互斥信号量。

### FreeRTOS优先级翻转问题及解决方法 #### 优先级翻转现象描述 在抢占式内核环境中,优先级翻转是一个常见问题,在实时操作系统中尤其需要注意。当高优先级任务等待低优先级任务完成资源访问时,如果在此期间另一个中等优先级的任务不断打断低优先级任务的工作,则会发生优先级翻转,这会破坏任务执行的预期顺序并可能引发严重的系统行为异常[^3]。 #### 解决方案一:优先级继承 (Priority Inheritance) 为了应对上述情况,FreeRTOS 提供了基于互斥锁(Mutex)实现的优先级继承机制。具体来说,当一个较高优先级的任务因试图获取已被较低优先级持有者占用的互斥锁而进入阻塞状态时,后者会被赋予前者相同级别的临时权限直至其释放该锁定对象。这种做法可以有效缩短高优级别进程受阻时间,并最大限度减少由于“优先级倒置”所造成的负面影响[^2]。 ```c // 创建带有优先级继承属性的二进制互斥型信号量 SemaphoreHandle_t xMutex; xMutex = xSemaphoreCreateMutex(); if( xMutex != NULL ) { // 尝试获得互斥锁 if( xSemaphoreTake( xMutex, portMAX_DELAY ) == pdTRUE ) { // 访问共享资源... // 使用完毕后立即解锁 xSemaphoreGive( xMutex ); } } ``` #### 解决方案二:优先级天花板 (Priority Ceiling) 另一种防止优先级翻转的方法称为设置优先级上限或称作“优先级天花板”。此策略涉及为每个临界区分配固定的最高允许调度等级;任何尝试进入这段代码区域内的线程都将自动提升至这一水平,从而阻止其他同类型竞争者的干扰。这种方法同样能够很好地规避潜在的风险,但在某些情况下可能导致不必要的上下文切换开销增加[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值