FreeRTOS 任务创建与删除

在FreeRTOS中,任务就是一个函数,原型如下:

void ATaskFunction( void *pvParameters );

要注意的是:

  • 这个函数不能返回
  • 同一个函数,可以用来创建多个任务;换句话说,多个任务可以运行同一个函数
  • 函数内部,尽量使用局部变量:
    • 每个任务都有自己的栈
    • 每个任务运行这个函数时
    • 任务A的局部变量放在任务A的栈里、任务B的局部变量放在任务B的栈里
    • 不同任务的局部变量,有自己的副本
    • 函数使用全局变量、静态变量的话
    • 只有一个副本:多个任务使用的是同一个副本
    • 要防止冲突(后续会讲)

下面是一个示例:

void ATaskFunction( void *pvParameters )
{
    /* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
    int32_t lVariableExample = 0;

    /* 任务函数通常实现为一个无限循环 */
    for( ;; )
    {
        /* 任务的代码 */
    }

    /* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
     * NULL表示删除的是自己
     */
    vTaskDelete( NULL );

    /* 程序不会执行到这里, 如果执行到这里就出错了 */
}

FreeRTOS 创建任务

创建任务时使用的函数如下:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
                        const char * const pcName, // 任务的名字
                        const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
                        void * const pvParameters, // 调用任务函数时传入的参数
                        UBaseType_t uxPriority,    // 优先级
                        TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

参数说明:

参数 描述
pvTaskCode 函数指针,可以简单地认为任务就是一个C函数。 它稍微特殊一点:永远不退出,或者退出时要调用”vTaskDelete(NULL)”
pcName 任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。 长度为:configMAX_TASK_NAME_LEN
usStackDepth 每个任务都有自己的栈,这里指定栈大小。 单位是word,比如传入100,表示栈大小为100 word,也就是400字节。 最大值为uint16_t的最大值。 怎么确定栈的大小,并不容易,很多时候是估计。 精确的办法是看反汇编码。
pvParameters 调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
uxPriority 优先级范围:0~(configMAX_PRIORITIES – 1) 数值越小优先级越低, 如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1)
pxCreatedTask 用来保存xTaskCreate的输出结果:task handle。 以后如果想操作这个任务,比如修改它的优先级,就需要这个handle。 如果不想使用该handle,可以传入NULL。
返回值 成功:pdPASS; 失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足) 注意:文档里都说失败时返回值是pdFAIL,这不对。 pdFAIL是0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY是-1。

FreeRTOS示例1: 创建任务

代码为:FreeRTOS_01_create_task

使用2个函数分别创建2个任务。

任务1的代码:

void vTask1( void *pvParameters )
{
    const char *pcTaskName = "T1 run\r\n";
    volatile uint32_t ul; /* volatile用来避免被优化掉 */

    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        /* 打印任务1的信息 */
        printf( pcTaskName );

        /* 延迟一会(比较简单粗暴) */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
        }
    }
}

任务2的代码:

void vTask2( void *pvParameters )
{
    const char *pcTaskName = "T2 run\r\n";
    volatile uint32_t ul; /* volatile用来避免被优化掉 */

    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        /* 打印任务1的信息 */
        printf( pcTaskName );

        /* 延迟一会(比较简单粗暴) */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
        }
    }
}

main函数:

int main( void )
{
    prvSetupHardware();

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

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

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

运行结果如下:

FreeRTOS 任务创建与删除

注意:

  • task 2先运行!
  • 要分析xTaskCreate的代码才能知道原因:更高优先级的、或者后面创建的任务先运行。

任务运行图:

  • 在t1:Task2进入运行态,一直运行直到t2
  • 在t2:Task1进入运行态,一直运行直到t3;在t3,Task2重新进入运行态

FreeRTOS 任务创建与删除

FreeRTOS示例2: 使用任务参数

代码为:FreeRTOS_02_create_task_use_params

我们说过,多个任务可以使用同一个函数,怎么体现它们的差别?

  • 栈不同
  • 创建任务时可以传入不同的参数

我们创建2个任务,使用同一个函数,代码如下:

void vTaskFunction( void *pvParameters )
{
    const char *pcTaskText = pvParameters;
    volatile uint32_t ul; /* volatile用来避免被优化掉 */

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

        /* 延迟一会(比较简单粗暴) */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
        }
    }
}

上述代码中的pcTaskText来自参数pvParameterspvParameters来自哪里?创建任务时传入的。

代码如下:

  • 使用xTaskCreate创建2个任务时,第4个参数就是pvParameters
  • 不同的任务,pvParameters不一样
static const char *pcTextForTask1 = "T1 run\r\n";
static const char *pcTextForTask2 = "T2 run\r\n";

int main( void )
{
    prvSetupHardware();

    xTaskCreate(vTaskFunction, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);
    xTaskCreate(vTaskFunction, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);

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

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

FreeRTOS任务的删除

删除任务时使用的函数如下:

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数说明:

参数 描述
pvTaskCode 任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。 也可传入NULL,这表示删除自己。
怎么删除任务?举个不好的例子:
  • 自杀:vTaskDelete(NULL)
  • 被杀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄
  • 杀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄

FreeRTOS示例3: 删除任务

代码为:FreeRTOS_03_delete_task

本节代码会涉及优先级的知识,可以只看vTaskDelete的用法,忽略优先级的讲解。

我们要做这些事情:

  • 创建任务1:任务1的大循环里,创建任务2,然后休眠一段时间
  • 任务2:打印一句话,然后就删除自己

任务1的代码如下:

void vTask1( void *pvParameters )
{
    const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );      
    BaseType_t ret;

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

        ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
        if (ret != pdPASS)
            printf("Create Task2 Failed\r\n");

        // 如果不休眠的话, Idle任务无法得到执行
        // Idel任务会清理任务2使用的内存
        // 如果不休眠则Idle任务无法执行, 最后内存耗尽
        vTaskDelay( xDelay100ms );
    }

任务2的代码如下:

void vTask2( void *pvParameters )
{   
    /* 打印任务的信息 */
    printf("Task2 is running and about to delete itself\r\n");

    // 可以直接传入参数NULL, 这里只是为了演示函数用法
    vTaskDelete(xTask2Handle);
}

main函数代码如下:

int main( void )
{
    prvSetupHardware();

    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);

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

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

运行结果如下:

FreeRTOS 任务创建与删除

任务运行图:

  • main函数中创建任务1,优先级为1。任务1运行时,它创建任务2,任务2的优先级是2。
  • 任务2的优先级最高,它马上执行。
  • 任务2打印一句话后,就删除了自己。
  • 任务2被删除后,任务1的优先级最高,轮到任务1继续运行,它调用vTaskDelay()进入Block状态
  • 任务1 Block期间,轮到Idle任务执行:它释放任务2的内存(TCB、栈)
  • 时间到后,任务1变为最高优先级的任务继续执行。
  • 如此循环。

FreeRTOS 任务创建与删除

在任务1的函数中,如果不调用vTaskDelay,则Idle任务用于没有机会执行,它就无法释放创建任务2是分配的内存。

而任务1在不断地创建任务,不断地消耗内存,最终内存耗尽再也无法创建新的任务。

现象如下:

FreeRTOS 任务创建与删除

任务1的代码中,需要注意的是:xTaskCreate的返回值。

  • 很多手册里说它失败时返回值是pdFAIL,这个宏是0
  • 其实失败时返回值是errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,这个宏是-1
  • 为了避免混淆,我们使用返回值跟pdPASS来比较,这个宏是1
赞(0)
未经允许不得转载:极客笔记 » FreeRTOS 任务创建与删除
分享到: 更多 (0)

评论 抢沙发

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