FreeRTOS任务管理与相关API函数详解
FreeRTOS任务管理与相关API函数详解
FreeRTOS是广泛应用于嵌入式系统的实时操作系统内核,其任务管理机制是系统的核心组成部分。本文将详细介绍FreeRTOS中任务管理相关的API函数,包括任务创建、删除、优先级管理、暂停与恢复、延时函数以及空闲任务等关键概念。通过本文的学习,读者将能够掌握FreeRTOS任务管理的基本原理和具体实现方法。
5.1 创建任务
创建任务时使用的函数如下:
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内部不使用。pcName
的长度为configMAX_TASK_NAME_LEN
。usStackDepth
:每个任务都有自己的栈,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)。如果以后需要对该任务进行操作,如修改优先级,则需要使用此句柄。如果不需要使用该句柄,可以传入NULL
。
返回值:成功时返回pdPASS
,失败时返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
(失败原因是内存不足)。请注意,文档中提到的失败返回值是pdFAIL
是不正确的。pdFAIL
的值为0,而errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
的值为-1。
使用静态分配内存的函数如下:
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer
StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);
相比于使用动态分配内存创建任务的函数,最后2个参数不一样:
puxStackBuffer
:静态分配的栈内存,比如可以传入一个数组,它的大小是usStackDepth*4
。pxTaskBuffer
:静态分配的StaticTask_t
结构体的指针
返回值:成功:返回任务句柄;失败:NULL
5.2 删除任务
删除任务时使用的函数如下:
void vTaskDelete(TaskHandle_t xTaskToDelete);
参数说明:
pvTaskCode
:任务句柄,使用xTaskCreate
创建任务时可以得到一个句柄。也可传入NULL
,这表示删除自己。
5.3 任务优先级
5.3.1 介绍
优先级的取值范围是: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的任务优先级高低与其对应的优先级数值,是成正比的,也就是说任务优先级数值为0的任务优先级是最低的任务优先级,任务优先级数值为(configMAX_PRIORITIES-1)
的任务优先级是最高的任务优先级。FreeRTOS的任务优先级高低与其对应数值的逻辑关系正好与STM32的中断优先级高低与其对应数值的逻辑关系相反,如下图所示。
5.3.2 任务优先级的相关API函数:
使用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)`。
5.4 暂停任务与恢复任务
FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend
函数。函数原型如下:
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
参数xTaskToSuspend
表示要暂停的任务,如果为NULL
,表示暂停自己。
要退出暂停状态,只能由别人来操作:
- 别的任务调用:
vTaskResume
- 中断程序调用:
xTaskResumeFromISR
实际开发中,暂停状态用得不多。
5.5 两个Delay函数
这2个函数原型如下:
void vTaskDelay(const TickType_t xTicksToDelay);
:按给定的tick数延迟任务,任务进入阻塞状态,即在延迟时间内不参与任务的调度。任务保持阻塞的实际时间取决于tick频率。
参数:xTicksToDelay
调用任务应阻塞的tick周期数。void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement);
参数:
pxPreviousWakeTime
:指向一个变量的指针,该变量用于保存任务最后一次解除阻塞的时间。该变量在第一次使用前必须用当前时间进行初始化。在这之后,该变量会在vTaskDelayUntil()
中自动更新。xTimeIncrement
:周期时间段。
两者的区别:后者比前者更精确,使用的绝对时间片段,不非前者相对时间片段。
5.6 空闲任务及其钩子函数
5.6.1 介绍
空闲任务(Idle任务)的作用之一:释放被删除的任务的内存。
除了上述目的之外,为什么必须要有空闲任务?一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用vTaskStartScheduler()
函数来创建、启动调度器时,这个函数内部会创建空闲任务:
- 空闲任务优先级为0:它不能阻碍用户任务运行
- 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞
空闲任务的优先级为0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。
要注意的是:如果使用vTaskDelete()
来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。
我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:
- 执行一些低优先级的、后台的、需要连续执行的函数
- 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
- 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。
空闲任务的钩子函数的限制:
- 不能导致空闲任务进入阻塞状态、暂停状态
- 如果你会使用
vTaskDelete()
来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。
5.6.2 使用钩子函数的前提
在FreeRTOS\Source\tasks.c中,可以看到如下代码,所以前提就是:
- 把这个宏定义为1:
configUSE_IDLE_HOOK
- 实现
vApplicationIdleHook
函数
5.6.3 CubeMX使用钩子函数
我们也可以在CubeMX中设置
生成代码之后我们可以在freertos.c中找到自动生成的钩子函数,在其中添加我们自己的功能就行了。