明珠的个人博客

是谁告诉你,你是赤裸的?

0%

FreeRTOS之二-初步认识任务创建和删除

FreeRTOS任务创建和删除

简介

任务创建和删除的本质就是调用FreeRTOS的API函数,和运用数学公式同理,证明过程已经有大神帮我们铺好路了,我们要做的就是应用即可。先从这3个开启第一步

动态创建任务
任务的任务控制块以及任务的栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配。优点:动态创建使用起来相对简单。在实际的应用中,动态方式创建任务是比较常用的,除非有特殊的需求,一般都会使用动态方式创建任务。

1
2
3
4
5
6
7
8
9
BaseType_t xTaskCreate
(
TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */
const char * const pcName, /* 任务名字,最大长度 configMAX_TASK_NAME_LEN */
const configSTACK_DEPTH_TYPE usStackDepth, /* 任务堆栈大小,注意字为单位 */
void * const pvParameters, /* 传递给任务函数的参数 */
UBaseType_t uxPriority, /* 任务优先级,范围: 0 ~ configMAX_PRIORITIES - 1 */
TaskHandle_t * const pxCreatedTask /* 任务句柄,就是任务的任务控制块 */
)

静态创建任务
任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供。优点:静态创建可将任务堆栈放置在特定的内存位置,并且无需关心对内存分配失败的处理。

1
2
3
4
5
6
7
8
9
10
TaskHandle_t xTaskCreateStatic
(
TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */
const char * const pcName, /* 任务函数名 */
const uint32_t ulStackDepth, /* 任务堆栈大小注意字为单位 */
void * const pvParameters, /* 传递的任务函数参数 */
UBaseType_t uxPriority, /* 任务优先级 */
StackType_t * const puxStackBuffer, /* 任务堆栈,一般为数组,由用户分配 */
StaticTask_t * const pxTaskBuffer /* 任务控制块指针,由用户分配 */
);

创建任务时任务堆栈所存内容

寄存器下PSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令

寄存器PC被初始化为任务函数指针(任务A,即我们写的任务函数的地址),这样当某次任务切换后,任务A获得CPU控制权,任务函数(任务A)被出栈到PC寄存器,之后会执行任务A的代码

LR寄存器初始化为函数指针prvTaskExitError,这个函数是FreeRTOS提供的,是一个出错处理函数

子函数的调用通过寄存器R0~R3传递参数,创建任务时,我们传入的参数被保存到R0中,用来向任务传递参数。

任务控制块结构体

1
2
3
4
5
6
7
8
9
10
11
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 任务栈栈顶,必须为 TCB 的第一个成员*/
ListItem_t xStateListItem; /* 任务状态列表项 */
ListItem_t xEventListItem; /* 任务事件列表项 */
UBaseType_t uxPriority; /* 任务优先级,数值越大,优先级越大 */
StackType_t *pxStack; /* 任务栈起始地址 */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名字 */

省略很多条件编译的成员
} tskTCB;

任务栈栈顶,在任务切换时的任务上下文保存、任务恢复息息相关。
每个任务都有属于自己的任务控制块,类似身份证。

实现动态创建任务流程

使用方法:

  1. 将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1

  2. 定义函数入口参数

  3. 编写任务函数

此函数创建的任务会立刻进入就绪态,由任务调度器调度运行

函数内部实现

  1. 申请堆栈内存 & 任务控制块内存

  2. TCB 结构体成员赋值

  3. 添加新任务到就绪列表中

实现静态创建任务流程

使用方法

  1. 需将宏configSUPPORT_STATIC_ALLOCATION配置为1

  2. 定义空闲任务 & 定时器任务的任务堆栈及TCB

  3. 实现两个接口函数:

vApplicationGetIdleTaskMemory( )
vApplicationGetTimerTaskMemory( )

  1. 定义函数入口参数

  2. 编写任务函数

此函数创建的任务会立刻进入就绪态,由任务调度器调度运行

函数内部实现:

  1. TCB 结构体成员赋值

  2. 添加新任务到就绪列表中

注意,堆栈申请时有可能会造成预留空间过多无法满足所有任务的堆栈需求,因此用函数 uxTaskGetStackHighWaterMark()函数可以更准确的查询任务所需堆栈大小

每个任务都有自己的堆栈,堆栈的总大小在创建任务的时候就确定了,此函数用于检查任务从创建好到现在的历史剩余最小值,这个值越小说明任务堆栈溢出的可能性就越大!
FreeRTOS 把这个历史剩余最小值叫做“高水位线”。此函数相对来说会多耗费一点时间,所以在代码调试阶段可以使用,产品发布的时候最好不要使用。要使用此函数的话宏INCLUDE_uxTaskGetStackHighWaterMark 必须为 1,

此函数原型如下:
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
参数:
xTask: 要查询的任务的任务句柄,当这个参数为 NULL 的话说明查询自身任务(即调用函数 uxTaskGetStackHighWaterMark()的任务)的“高水位线”

返回值: 任务堆栈的“高水位线”值,也就是堆栈的历史剩余最小值

任务删除函数

用于删除已被创建的任务

1
void vTaskDelete(TaskHandle_t xTaskToDelete);

当传入的参数为 NULL ,则代表删除任务自身(当前正在运行的任务)
将被删除任务移除所在列表 — 将该任务所在列表中移除,包括:就绪、阻塞、挂起、事件等列表(列表使用long32位数据通过与或位操作判断状态)
空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存, 则需要由用户在任务被删除前提前释放,否则将导致内存泄露

使用方法:

  1. 使用删除任务函数,将FreeRTOSConfig.h文件中宏INCLUDE_vTaskDelete配置为1

  2. 入口参数输入需要删除的任务句柄(NULL代表删除本身)

函数内部实现:

  1. 获取所要删除的任务控制块 — 通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身

  2. 将被删除任务移除所在列表 — 将该任务所在列表中移除,包括:就绪、阻塞、挂起、事件等列表

  3. 判断所需要删除的任务

  • 删除任务自身,需要先添加到等待删除列表,内存释放将在空闲任务执行。

  • 删除其他任务,释放内存,任务数量

  1. 更新下个任务的阻塞时间 — 更新下一个任务的阻塞超时时间,以防止被删除的任务就是下一个阻塞超时的任务