FreeRTOS学习(四)FreeRTOS(动态)任务创建与删除及临界区保护
文章目录
一、FreeRTOS动态任务管理基础
动态管理是由FreeRTOS自己分配内存
1.1 动态创建任务详解
1.1.1 xTaskCreate()函数完整解析
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称
uint16_t usStackDepth, // 任务栈大小(字)
void *pvParameters, // 传递给任务的参数
UBaseType_t uxPriority, // 任务优先级(0-configMAX_PRIORITIES-1)
TaskHandle_t *pxCreatedTask // 任务句柄
);
参数详细说明:
- pvTaskCode
- 任务函数的入口地址
- 必须是永久循环,不能返回
- 也就是不同任务的执行:
void vTaskFunction(void *pvParameters)
{
for(;;)
{
//任务代码
vTaskDelay(xDelay);
}
}
- pcName
- 任务的描述性名称
- 主要用于调试
- 最大长度configMAX_TASK_NAME_LEN
- usStackDepth
- 以字为单位的栈深度
- 需要根据任务实际需求设置
- 常见任务栈大小:50~500字
- pvParameters
- 传递给任务的参数指针
- 可以是任何类型
- 不需要时设为NULL
- uxPriority
- 任务优先级0~(configMAX_PRIORITIES-1)32位的configMAX_PRIORITIES就是32(硬件决定的)
- 数值越大优先级越高
- 相同优先级采用时间片轮转
- pxCreatedTask
- 任务句柄的指针
- 用于后续对任务的操作
- 不需要时可设为NULL
1.1.2 返回值说明
- pdPASS(1): 创建成功
- errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(0): 内存不足
- 其他错误值
1.1.3 创建任务的内部过程
- 申请任务栈空间
- 申请任务控制块(TCB)空间
- 初始化任务栈
- 初始化TCB
- 将任务添加到就绪列表
- 根据优先级可能触发任务调度
1.2 动态删除任务深入解析
1.2.1 vTaskDelete()函数详解
void vTaskDelete(TaskHandle_t xTask);
使用方式:
- 删除自身:
vTaskDelete(NULL);
- 删除其他任务:
vTaskDelete(xTaskHandle);
1.2.2 删除任务的内部过程
- 从所有任务列表中移除
- 从就绪列表移除
- 将任务放入删除队列
- 空闲任务最终释放内存
1.2.3 存在的问题
未加临界区保护存在以下风险:
- 任务创建过程可能被打断,导致任务参数设置不完整
- 任务切换时机不可控,可能造成系统不稳定
- 任务优先级设置可能混乱
- 任务句柄可能无效
二、临界区保护原理与实现
未加的情况
// 第1步: 程序从main函数开始执行
int main(void)
{
/* 创建开始任务 */
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
// 第2步: 调用vTaskStartScheduler()启动调度器
vTaskStartScheduler(); // 启动后FreeRTOS接管系统
// 注意:这里之后的代码正常情况下不会执行到,因为系统已被FreeRTOS接管
while(1);
}
// 第3步: 调度器启动后,会执行优先级最高的就绪任务
// 此时只有start_task这个任务,所以会执行start_task函数
void start_task(void *pvParameters)
{
// 第4步: 创建task1
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
// 第5步: 创建task2
// 注意:在这里可能会被task1抢占,因为没有临界区保护
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
// 第6步: 创建task3
// 同样这里也可能被task1或task2抢占
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
// 第7步: 删除start_task自身
vTaskDelete(NULL);
}
// task1、task2、task3的执行时机取决于任务调度
void task1(void *pvParameters)
{
// 任务1的无限循环
while(1)
{
printf("Task1 is running...\r\n");
vTaskDelay(500); // 延时500ms,任务进入阻塞态
// 延时期间可能执行task2或task3
}
}
void task2(void *pvParameters)
{
while(1)
{
printf("Task2 is running...\r\n");
vTaskDelay(500); // 延时500ms,任务进入阻塞态
// 延时期间可能执行task1或task3
}
}
void task3(void *pvParameters)
{
while(1)
{
printf("Task3 is running...\r\n");
vTaskDelay(500); // 延时500ms,任务进入阻塞态
// 延时期间可能执行task1或task2
}
}
存在的问题和风险:
- 在start_task创建任务的过程中:
// 假设正在创建task2
xTaskCreate((TaskFunction_t )task2,...); // 还未执行完
// 此时可能被task1抢占
printf("Task1 is running...\r\n"); // task1开始执行
// task2的创建过程被中断,可能导致:
- 任务创建参数设置不完整
- 任务句柄未正确赋值
- 任务控制块数据混乱
- 任务切换的不确定性:
// start_task中
xTaskCreate(...task1...); // 创建task1
// task1可能立即得到执行
task1(); // task1抢占执行
// 返回start_task继续创建其他任务
xTaskCreate(...task2...); // 创建task2
- 可能的执行顺序:
start_task开始
↓
创建task1(未完成) → task1抢占执行 → 返回start_task
↓
创建task2(未完成) → task2抢占执行 → 返回start_task
↓
创建task3(未完成) → task3抢占执行 → 返回start_task
2.1 临界区保护的作用
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建任务1
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建任务2
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
//创建任务3
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
- taskENTER_CRITICAL():
- 关闭中断
- 防止任务切换
- 保证代码段完整执行
- 保护范围:
- 任务创建的完整过程
- 参数设置
- 任务控制块初始化
- 任务添加到就绪列表
- taskEXIT_CRITICAL():
- 退出临界区
- 恢复中断
- 允许任务切换
2.2 执行流程对比
未加保护:
加入保护后:
也就是创建完任务再去执行任务!!!
完整代码
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "FreeRTOS.h"
#include "task.h"
/* 任务优先级和堆栈大小定义 */
#define START_TASK_PRIO 1
#define TASK1_PRIO 2
#define TASK2_PRIO 3
#define TASK3_PRIO 4
#define START_TASK_STACK_SIZE 128
#define TASK1_STACK_SIZE 128
#define TASK2_STACK_SIZE 128
#define TASK3_STACK_SIZE 128
/* 任务句柄 */
TaskHandle_t StartTask_Handler;
TaskHandle_t Task1_Handler;
TaskHandle_t Task2_Handler;
TaskHandle_t Task3_Handler;
/* 任务函数声明 */
static void start_task(void *pvParameters);
static void task1(void *pvParameters);
static void task2(void *pvParameters);
static void task3(void *pvParameters);
void freertos_demo(void)
{
/* 创建起始任务 */
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_TASK_STACK_SIZE,/* 任务堆栈大小 */
(void* )NULL, /* 传递给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
/* 开启任务调度 */
vTaskStartScheduler();
}
/* 起始任务函数 */
static void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
/* 创建任务3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
taskEXIT_CRITICAL(); //退出临界区
/* 删除起始任务 */
vTaskDelete(NULL);
}
/* 任务1函数 */
static void task1(void *pvParameters)
{
while(1)
{
printf("task1 is running\r\n");
LED0_TOGGLE();
vTaskDelay(pdMS_TO_TICKS(500)); // 使用pdMS_TO_TICKS转换时间
}
}
/* 任务2函数 */
static void task2(void *pvParameters)
{
while(1)
{
printf("task2 is running\r\n");
LED1_TOGGLE();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/* 任务3函数 */
static void task3(void *pvParameters)
{
uint8_t key;
while(1)
{
key = key_scan(0);
printf("task3 is running\r\n");
if(key == KEY0_PRES)
{
if(Task1_Handler != NULL)
{
vTaskDelete(Task1_Handler);
Task1_Handler = NULL; // 删除后将句柄置空
printf("task1 has been deleted\r\n");
}
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
- 系统启动后创建start_task
- start_task创建task1、task2、task3
- 三个任务交替运行,每500ms打印一次信息
总结
本文详细介绍了FreeRTOS动态任务创建删除的实现方法,以及在多任务环境下必要的临界区保护机制。通过正确使用这些机制,可以确保系统的稳定性和可靠性。