FreeRTOS 任务优先级和Tick

FreeRTOS 任务优先级

在上个示例中我们体验过优先级的使用:高优先级的任务先运行。

优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。

FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES 的取值有所不同。

  • 通用方法 使用C函数实现,对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。 configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时,使用此方法。
  • 架构相关的优化的方法 架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。 使用这种方法时,configMAX_PRIORITIES的取值不能超过32。 configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用此方法。

在学习调度方法之前,你只要初略地知道:

  • FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
  • 对于相同优先级的、可运行的任务,轮流执行

这无需记忆,就像我们举的例子:

  • 厨房着火了,当然优先灭火
  • 喂饭、回复信息同样重要,轮流做

FreeRTOS Tick

对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。

“一会”怎么定义?

人有心跳,心跳间隔基本恒定。

FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。

如下图:

  • 假设t1、t2、t3发生时钟中断
  • 两次中断之间的时间被称为时间片(time slice、tick period)
  • 时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms

FreeRTOS Tick

相同优先级的任务怎么切换呢?请看下图:

  • 任务2从t1执行到t2
  • 在t2发生tick中断,进入tick中断处理函数:
    • 选择下一个要运行的任务
    • 执行完中断处理函数后,切换到新的任务:任务1
  • 任务1从t2执行到t3
  • 从图中可以看出,任务运行的时间并不是严格从t1,t2,t3哪里开始

FreeRTOS Tick

有了Tick的概念后,我们就可以使用Tick来衡量时间了,比如:

vTaskDelay(2);  // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100));  // 等待100ms

注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。

如下图:

FreeRTOS Tick

使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。

这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。

FreeRTOS 示例: 优先级实验

代码为:FreeRTOS_04_task_priority

本程序会创建3个任务:

  • 任务1、任务2:优先级相同,都是1
  • 任务3:优先级最高,是2

任务1、2代码如下:

void vTask1( void *pvParameters )
{
    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        /* 打印任务的信息 */
        printf("T1\r\n");               
    }
}

void vTask2( void *pvParameters )
{   
    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        /* 打印任务的信息 */
        printf("T2\r\n");               
    }
}

任务3代码如下:

void vTask3( void *pvParameters )
{   
    const TickType_t xDelay3000ms = pdMS_TO_TICKS( 3000UL );        

    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        /* 打印任务的信息 */
        printf("T3\r\n");               

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

main函数代码如下:

{
    prvSetupHardware();

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

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

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

运行情况如下图所示:

  • 任务3优先执行,直到它调用vTaskDelay主动放弃运行
  • 任务1、任务2:轮流执行

FreeRTOS 示例: 优先级实验

调度情况如下图所示:

FreeRTOS 示例: 优先级实验

FreeRTOS 示例: 修改优先级

本节代码为:FreeRTOS_05_change_priority

使用uxTaskPriorityGet来获得任务的优先级:

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。

使用vTaskPrioritySet 来设置任务的优先级:

void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority );

使用参数xTask来指定任务,设置为NULL表示设置自己的优先级; 参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。

main函数的代码如下,它创建了2个任务:任务1的优先级更高,它先执行:

int main( void )
{
    prvSetupHardware();

    /* Task1的优先级更高, Task1先执行 */
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );

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

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

任务1的代码如下:

void vTask1( void *pvParameters )
{
    UBaseType_t uxPriority;

    /* Task1,Task2都不会进入阻塞或者暂停状态
     * 根据优先级决定谁能运行
     */

    /* 得到Task1自己的优先级 */
    uxPriority = uxTaskPriorityGet( NULL );

    for( ;; )
    {
        printf( "Task 1 is running\r\n" );

        printf("About to raise the Task 2 priority\r\n" );

        /* 提升Task2的优先级高于Task1
         * Task2会即刻执行
         */
        vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );

        /* 如果Task1能运行到这里,表示它的优先级比Task2高
        * 那就表示Task2肯定把自己的优先级降低了
         */
    }
}

任务2的代码如下:

void vTask2( void *pvParameters )
{
    UBaseType_t uxPriority;

    /* Task1,Task2都不会进入阻塞或者暂停状态
     * 根据优先级决定谁能运行
     */

    /* 得到Task2自己的优先级 */
    uxPriority = uxTaskPriorityGet( NULL );

    for( ;; )
    {
        /* 能运行到这里表示Task2的优先级高于Task1
         * Task1提高了Task2的优先级
         */
        printf( "Task 2 is running\r\n" );

        printf( "About to lower the Task 2 priority\r\n" );

        /* 降低Task2自己的优先级,让它小于Task1
         * Task1得以运行
         */
        vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
    }
}

调度情况如下图所示:

  • 1:一开始Task1优先级最高,它先执行。它提升了Task2的优先级。
  • 2:Task2的优先级最高,它执行。它把自己的优先级降低了。
  • 3:Task1的优先级最高,再次执行。它提升了Task2的优先级。
  • 如此循环。
  • 注意:Task1的优先级一直是2,Task2的优先级是3或1,都大于0。所以Idel任务没有机会执行。

FreeRTOS 示例: 修改优先级

赞(0)
未经允许不得转载:极客笔记 » FreeRTOS 任务优先级和Tick
分享到: 更多 (0)

评论 抢沙发

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