FreeRTOS系列第9篇---FreeRTOS任务概述基础篇

时间:2022-07-22
本文章向大家介绍FreeRTOS系列第9篇---FreeRTOS任务概述基础篇,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

ID:技术让梦想更伟大

作者:李肖遥

1. 任务和协程(Co-routines)

应用程序可以使用任务也可以使用协程,或者两者混合使用,但是任务和协程使用不同的API函数,因此在任务和协程之间不能使用同一个队列或信号量传递数据。

通常情况下,协程仅用在资源非常少的微处理器中,特别是RAM非常稀缺的情况下。

目前协程很少被使用到,因此对于协程FreeRTOS作者既没有把它删除也没有进一步开发。

所以本系列文章以后不会对协程过多描述,包括其API函数。

1.1任务的特性

「简而言之:」

使用RTOS的实时应用程序可认为是一系列独立任务的集合。每个任务在自己的环境中运行,不依赖于系统中的其它任务或者RTOS调度器。

在任何时刻,只有一个任务得到运行,RTOS调度器决定运行哪个任务。调度器会不断的启动、停止每一个任务,宏观看上去就像整个应用程序都在执行。

作为任务,不需要对调度器的活动有所了解,在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。

为了实现这点,每个任务都需要有自己的堆栈。当任务切出时,它的执行环境会被保存在该任务的堆栈中,这样当再次运行时,就能从堆栈中正确的恢复上次的运行环境。

1.2任务概要

  • 简单
  • 没有使用限制
  • 支持完全抢占
  • 支持优先级
  • 每个任务都有自己的堆栈,消耗RAM较多
  • 如果使用抢占,必须小心的考虑可重入问题

2. 任务状态

「一个任务可为下面中的一个:」

  1. 「运行」:如果一个任务正在执行,那么说这个任务处于运行状态。此时它占用处理器。
  2. 「就绪」:就绪的任务已经具备执行的能力(不同于阻塞和挂起),但是因为有一个同优先级或者更高优先级的任务处于运行状态而还没有真正执行。
  3. 「阻塞」:如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态。比如一个任务调用vTaskDelay()后会阻塞到延时周期到为止。任务也可能阻塞在队列或信号量的事件上。进入阻塞状态的任务通常有一个“超时”周期,当事件超时后解除阻塞。
  4. 「挂起」:处于挂起状态的任务同样对调度器无效。仅当明确的分别调用vTaskSuspend()xTaskResume() API函数后,任务才会进入或退出挂起状态。不可以指定超时周期事件(不可以通过设定超时事件而退出挂起状态)

3.任务优先级

每个任务都要被指定一个优先级,从0~configMAX_PRIORITIESconfigMAX_PRIORITIES定义在FreeRTOSConfig.h中。

如果某架构硬件支持CLZ(或类似)指令(计算前导零的数目,Cortex-M3是支持该指令的,从ARMv6T2才支持这个指令),并且打算在移植层使用这个特性来优化任务调度机制,需要有一些步骤。

首先将FreeRTOSConfig.h中configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1,并且最大优先级数目configMAX_PRIORITIES不能大于32。

除此之外,configMAX_PRIORITIES可以设置为任意值,但是考虑到configMAX_PRIORITIES设置越大,RAM消耗也越大,一般设置为满足使用的最小值。

低优先级数值代表低优先级。空闲任务(idle task)的优先级为0(tskIDLE_PRIORITY)。

FreeRTOS调度器确保处于最高优先级的就绪或运行态任务获取处理器,换句话说,处于运行状态的任务,只有其中的最高优先级任务才会运行。

任何数量的任务可以共享同一个优先级。如果宏configUSE_TIME_SLICING未定义或者宏configUSE_TIME_SLICING定义为1,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。

4.实现一个任务

「一个任务具有以下结构:」

void vATaskFunction( void *pvParameters )
{
    for( ;; )
    {
        /*-- 应用程序代码放在这里. --*/
    }
 
    /* 任务不可以从这个函数返回或退出。在较新的FreeRTOS移植包中,如果
    试图从一个任务中返回,将会调用configASSERT()(如果定义的话)。
    如果一个任务确实要退出函数,那么这个任务应调用vTaskDelete(NULL)
    函数,以便处理一些清理工作。*/
    vTaskDelete( NULL );
}

任务函数返回为void,参数只有一个void类型指针。所有的任务函数都应该是这样。void类型指针可以向任务传递任意类型信息。

任务函数决不应该返回,因此通常任务函数都是一个死循环。

任务由xTaskCreate()函数创建,由vTaskDelete()函数删除。

5.空闲任务和空闲任务钩子(idle task和Idle Task hook)

5.1空闲任务

空闲任务是启动RTOS调度器时由内核自动创建的任务,这样可以确保至少有一个任务在运行。

空闲任务具有最低任务优先级,这样如果有其它更高优先级的任务进入就绪态就可以立刻让出CPU。

删除任务后,空闲任务用来释放RTOS分配给被删除任务的内存。因此,在应用中使用vTaskDelete()函数后确保空闲任务能获得处理器时间就很重要了。

除此之外,空闲任务没有其它有效功能,所以可以被合理的剥夺处理器时间,并且它的优先级也是最低的。

应用程序任务共享空闲任务优先级(tskIDLE_PRIORITY)也是可能的。这种情况如何配置可以参考configIDLE_SHOULE_YIELD配置参数类获取更多信息。

5.2空闲任务钩子

空闲任务钩子是一个函数,每一个空闲任务周期被调用一次。如果你想将任务程序功能运行在空闲优先级上,可以有两种选择:

  1. 在一个空闲任务钩子中实现这个功能:因为FreeRTOS必须至少有一个任务处于就绪或运行状态,因此钩子函数不可以调用可能引起空闲任务阻塞的API函数(比如vTaskDelay()或者带有超时事件的队列或信号量函数)
  2. 创建一个具有空闲优先级的任务去实现这个功能:这是个更灵活的解决方案,但是会带来更多RAM开销。

「创建一个空闲钩子步骤如下」

  • FreeRTOSConfig.h头文件中设置configUSE_IDLE_HOOK为1;
  • 定义一个函数,名字和参数原型如下所示:
void vApplicationIdleHook( void );

通常,使用这个空闲钩子函数设置CPU进入低功耗模式。

6.任务创建

任务创建和删除API函数位于文件task.c中,需要包含task.h头文件。

6.1 函数描述

BaseType_t xTaskCreate(
        TaskFunction_t pvTaskCode,
        const char * const pcName,
        unsigned short usStackDepth,
        void *pvParameters,
        UBaseType_t uxPriority,
        TaskHandle_t * pvCreatedTask
      );

创建新的任务并加入任务就绪列表。

如果使用FreeRTOS-MPU(在官方下载包中,为Cortex-M3内核写了两个移植方案,一个是普通的FreeRTOS移植层,还有一个是FreeRTOS-MPU移植层。后者包含完整的内存保护),那么推荐使用函数xTaskCreateRestricted()来代替xTaskCreate()

在使用FreeRTOS-MPU的情况下,使用xTaskCreate()函数可以创建运行在特权模式或用户模式(见下面对函数参数uxPriority的描述)的任务。

当运行在特权模式下,任务可以访问整个内存映射;当处于用户模式下,任务仅能访问自己的堆栈。

无论在何种模式下,MPU都不会自动捕获堆栈溢出,因此标准的FreeRTOS堆栈溢出检测机制仍然会被用到。xTaskCreateRestricted()函数具有更大的灵活性。

6.2参数描述

  • 「pvTaskCode」:指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型TaskFunction_t定义在文件projdefs.h中,定义为:typedefvoid (*TaskFunction_t)( void * )
  • 「pcName」:任务描述。主要用于调试。字符串的最大长度由宏configMAX_TASK_NAME_LEN指定,该宏位于FreeRTOSConfig.h文件中。
  • 「usStackDepth」:指定任务堆栈大小,能够支持的堆栈变量数量,而不是字节数。比如,在16位宽度的堆栈下,usStackDepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示的最大值是65535。
  • 「pvParameters」:指针,当任务创建时,作为一个参数传递给任务。
  • uxPriority:任务的优先级。具有MPU支持的系统,可以通过置位优先级参数的portPRIVILEGE_BIT位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxPriority可以设置为( 2 | portPRIVILEGE_BIT )。
  • 「pvCreatedTask」:用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。

6.3返回值

如果任务成功创建并加入就绪列表函数返回pdPASS,否则函数返回错误码,具体参见projdefs.h

6.4用法举例

/* 创建任务. */
void vTaskCode( void * pvParameters )
{
    for( ;; )
    {
       /* 任务代码放在这里 */
    }
}
 
/* 创建任务函数 */
void vOtherFunction( void )
{
    static unsigned char ucParameterToPass;
    xTaskHandlexHandle;
 
     /* 创建任务,存储句柄。注:传递的参数ucParameterToPass必须和任务具有相同的生存周期,
        因此这里定义为静态变量。如果它只是一个自动变量,可能不会有太长的生存周期,因为
                中断和高优先级任务可能会用到它。 */
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE,&ucParameterToPass, tskIDLE_PRIORITY, &xHandle );
 
     /* 使用句柄删除任务. */
    if( xHandle !=NULL )
    {
        vTaskDelete( xHandle );
    }
}

7.任务删除

7.1 任务描述

voidvTaskDelete( TaskHandle_t xTask );

从RTOS内核管理器中删除一个任务。任务删除后将会从就绪、阻塞、暂停和事件列表中移除。在文件FreeRTOSConfig.h中,必须定义宏INCLUDE_vTaskDelete 为1,本函数才有效。

「注意:」

被删除的任务,其在任务创建时由内核分配的存储空间,会由空闲任务释放。

如果有应用程序调用xTaskDelete(),必须保证空闲任务获取一定的微控制器处理时间。

任务代码自己分配的内存是不会自动释放的,因此删除任务前,应该将这些内存释放。

7.2参数描述

  • 「xTask」:被删除任务的句柄。为NULL表示删除当前任务。