FreeRTOS 调度算法

FreeRTOS调度算法 重要概念

这些知识在前面都提到过了,这里总结一下。

正在运行的任务,被称为”正在使用处理器”,它处于运行状态。在单处理系统中,任何时间里只能有一个任务处于运行状态。

非运行状态的任务,它处于这3中状态之一:阻塞(Blocked)、暂停(Suspended)、就绪(Ready)。就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。

阻塞状态的任务,它在等待”事件”,当事件发生时任务就会进入就绪状态。事件分为两类:时间相关的事件、同步事件。所谓时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。同步事件就是:某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么”发送信息”?方法很多,有:任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。这些方法用来发送同步信息,比如表示某个外设得到了数据。

FreeRTOS调度算法 配置调度算法

所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。

通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。

还有第三个配置项:configUSE_TICKLESS_IDLE,它是一个高级选项,用于关闭Tick中断来实现省电,后续单独讲解。现在我们假设configUSE_TICKLESS_IDLE被设为0,先不使用这个功能。

调度算法的行为主要体现在两方面:高优先级的任务先运行、同优先级的就绪态任务如何被选中。调度算法要确保同优先级的就绪态任务,能”轮流”运行,策略是”轮转调度”(Round Robin Scheduling)。轮转调度并不保证任务的运行时间是公平分配的,我们还可以细化时间的分配方法。

从3个角度统一理解多种调度算法:

  • 可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)
    • 可以:被称作”可抢占调度”(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。
    • 不可以:不能抢就只能协商了,被称作”合作调度模式”(Co-operative Scheduling)
    • 当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出CPU资源。
    • 其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点
  • 可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)
    • 轮流执行:被称为”时间片轮转”(Time Slicing),同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片
    • 不轮流执行:英文为”without Time Slicing”,当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占
  • 在”可抢占”+”时间片轮转”的前提下,进一步细化:空闲任务是否让步于用户任务(配置项:configIDLE_SHOULD_YIELD)
    • 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务
    • 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊

列表如下:

配置项 A B C D E
configUSE_PREEMPTION 1 1 1 1 0
configUSE_TIME_SLICING 1 1 0 0 x
configIDLE_SHOULD_YIELD 1 0 1 0 x
说明 常用 很少用 很少用 很少用 几乎不用

注:

  • A:可抢占+时间片轮转+空闲任务让步
  • B:可抢占+时间片轮转+空闲任务不让步
  • C:可抢占+非时间片轮转+空闲任务让步
  • D:可抢占+非时间片轮转+空闲任务不让步
  • E:合作调度

FreeRTOS调度算法 示例: 调度

本节代码为:FreeRTOS_07_scheduler。后续的实验都是基于这个程序,通过修改配置项来观察效果。

代码里创建了3个任务:Task1、Task2的优先级都是0,跟空闲任务一样,Task3优先级最高为2。程序里定义了4个全局变量,当某个的任务执行时,对应的变量就被设为1,可以通过Keil的逻辑分析仪查看任务切换情况:

static volatile int flagIdleTaskrun = 0;  // 空闲任务运行时flagIdleTaskrun=1
static volatile int flagTask1run = 0;     // 任务1运行时flagTask1run=1
static volatile int flagTask2run = 0;     // 任务2运行时flagTask2run=1
static volatile int flagTask3run = 0;     // 任务3运行时flagTask3run=1

main函数代码如下:

int main( void )
{
    prvSetupHardware();

    xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
    xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
    xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);

    /* 启动调度器 */
    vTaskStartScheduler();

    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}

任务1、任务2代码如下,它们是”连续任务”(continuous task):

void vTask1( void *pvParameters )
{
    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        flagIdleTaskrun = 0;
        flagTask1run = 1;
        flagTask2run = 0;
        flagTask3run = 0;

        /* 打印任务的信息 */
        printf("T1\r\n");               
    }
}

void vTask2( void *pvParameters )
{   
    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        flagIdleTaskrun = 0;
        flagTask1run = 0;
        flagTask2run = 1;
        flagTask3run = 0;

        /* 打印任务的信息 */
        printf("T2\r\n");               
    }
}

任务3代码如下,它会调用vTaskDelay,这样别的任务才可以运行:

void vTask3( void *pvParameters )
{   
    const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );      

    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        flagIdleTaskrun = 0;
        flagTask1run = 0;
        flagTask2run = 0;
        flagTask3run = 1;

        /* 打印任务的信息 */
        printf("T3\r\n");               

        // 如果不休眠的话, 其他任务无法得到执行
        vTaskDelay( xDelay5ms );
    }
}

提供了一个空闲任务的钩子函数:

void vApplicationIdleHook(void)
{
    flagIdleTaskrun = 1;
    flagTask1run = 0;
    flagTask2run = 0;
    flagTask3run = 0;   

    /* 故意加入打印让flagIdleTaskrun变为1的时间维持长一点 */
    printf("Id\r\n");               
}

FreeRTOS调度算法 对比效果: 抢占与否

FreeRTOSConfig.h中,定义这样的宏,对比逻辑分析仪的效果:

// 实验1:抢占
#define configUSE_PREEMPTION       1
#define configUSE_TIME_SLICING      1
#define configIDLE_SHOULD_YIELD        1

// 实验2:不抢占
#define configUSE_PREEMPTION       0
#define configUSE_TIME_SLICING      1
#define configIDLE_SHOULD_YIELD        1

从下面的对比图可以知道:

  • 抢占时:高优先级任务就绪时,就可以马上执行
  • 不抢占时:优先级失去意义了,既然不能抢占就只能协商了,图中任务1一直在运行(一点都没有协商精神),其他任务都无法执行。即使任务3的vTaskDelay已经超时、即使它的优先级更高,都没办法执行。

FreeRTOS调度算法 对比效果: 抢占与否

FreeRTOS调度算法 对比效果: 时间片轮转与否

FreeRTOSConfig.h中,定义这样的宏,对比逻辑分析仪的效果:

// 实验1:时间片轮转
#define configUSE_PREEMPTION       1
#define configUSE_TIME_SLICING      1
#define configIDLE_SHOULD_YIELD        1

// 实验2:时间片不轮转
#define configUSE_PREEMPTION       1
#define configUSE_TIME_SLICING      0
#define configIDLE_SHOULD_YIELD        1

从下面的对比图可以知道:

  • 时间片轮转:在Tick中断中会引起任务切换
  • 时间片不轮转:高优先级任务就绪时会引起任务切换,高优先级任务不再运行时也会引起任务切换。可以看到任务3就绪后可以马上执行,它运行完毕后导致任务切换。其他时间没有任务切换,可以看到任务1、任务2都运行了很长时间。

FreeRTOS调度算法 对比效果: 时间片轮转与否

FreeRTOS调度算法 对比效果: 空闲任务让步

FreeRTOSConfig.h中,定义这样的宏,对比逻辑分析仪的效果:

// 实验1:空闲任务让步
#define configUSE_PREEMPTION       1
#define configUSE_TIME_SLICING      1
#define configIDLE_SHOULD_YIELD        1

// 实验2:空闲任务不让步
#define configUSE_PREEMPTION       1
#define configUSE_TIME_SLICING      1
#define configIDLE_SHOULD_YIELD        0

从下面的对比图可以知道:

  • 让步时:在空闲任务的每个循环中,会主动让出处理器,从图中可以看到flagIdelTaskrun的波形很小
  • 不让步时:空闲任务跟任务1、任务2同等待遇,它们的波形宽度是差不多的

FreeRTOS调度算法 对比效果: 空闲任务让步

赞(0)
未经允许不得转载:极客笔记 » FreeRTOS 调度算法
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址