3. 任务创建与删除:动态创建任务、静态创建任务、任务删除函数、任务句柄的使用
好,咱们今天聊聊任务创建与删除。这块儿是RTOS里最基础、也最常用的操作。说白了,你写嵌入式程序,本质上就是在跟任务打交道。任务怎么生出来?怎么销毁?这里头门道不少。
我个人习惯把任务创建分成两种:动态创建和静态创建。很多初学者一开始搞不清区别,其实你想想看,这就像你家里请客吃饭——动态创建是叫外卖,静态创建是自己下厨。各有各的适用场景。
3.1 动态创建任务
动态创建,就是让RTOS内核帮你分配任务控制块(TCB)和任务栈。你只需要调用一个API函数,剩下的内存分配工作,系统替你搞定。
在FreeRTOS里,动态创建用的是 xTaskCreate() 这个函数。我贴一段代码,咱们边看边聊:
TaskHandle_t myTaskHandle = NULL;
void vTaskFunction(void *pvParameters)
{
// 任务主体代码
for(;;)
{
// 做点啥
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void main(void)
{
BaseType_t xReturned;
xReturned = xTaskCreate(
vTaskFunction, // 任务函数
"MyTask", // 任务名字(调试用)
256, // 栈深度,单位是字(Word)
NULL, // 参数
1, // 优先级
&myTaskHandle // 任务句柄
);
if (xReturned == pdPASS)
{
// 创建成功
printf("任务创建成功!\n");
}
vTaskStartScheduler();
}
这里有个关键点——任务句柄。它就像任务的身份证。你拿着这个句柄,后面才能对任务做操作,比如删除、挂起、恢复等等。我在项目中遇到过有人创建任务时不保存句柄,结果后面想删除任务时傻眼了——找不着对象了。
3.2 静态创建任务
静态创建就不同了。你得自己准备好任务控制块和栈的内存空间。说白了,内存归你管,系统只管用。
为什么要用静态创建?嗯,这里有个现实问题。在一些安全关键系统里(比如医疗设备、航空航天),动态内存分配是不被允许的。为什么?因为malloc可能失败,你没法保证每次都能分到内存。静态创建就没有这个问题——内存是编译时就定好的。
静态创建的API是 xTaskCreateStatic():
// 自己准备栈和TCB
StackType_t xStack[256];
StaticTask_t xTaskBuffer;
TaskHandle_t xHandle = NULL;
void vTaskFunction(void *pvParameters)
{
for(;;)
{
// 任务代码
}
}
void main(void)
{
xHandle = xTaskCreateStatic(
vTaskFunction,
"StaticTask",
256,
NULL,
1,
xStack,
&xTaskBuffer
);
if (xHandle != NULL)
{
// 创建成功
}
vTaskStartScheduler();
}
xTaskCreate,静态创建用 xTaskCreateStatic。动态的省心,静态的可靠。选哪个?看你的项目需求。
3.3 任务删除函数
任务创建了,总有不需要的时候。比如某个初始化任务,干完活就该退场了。这时候就用 vTaskDelete()。
这个函数有个特点——它只能删除别人,或者删除自己。删除自己时,传参传NULL就行:
void vTaskFunction(void *pvParameters)
{
// 做初始化工作
InitHardware();
InitPeripherals();
// 干完活了,删除自己
vTaskDelete(NULL);
}
删除其他任务时,你得传那个任务的句柄:
// 假设有一个全局句柄
extern TaskHandle_t xTaskToDelete;
void vControlTask(void *pvParameters)
{
vTaskDelay(pdMS_TO_TICKS(5000)); // 等5秒
vTaskDelete(xTaskToDelete); // 删除另一个任务
}
3.4 任务句柄的使用
任务句柄,说白了就是一个指向任务控制块的指针。它的类型是 TaskHandle_t。你拿着它,能干很多事情:
| API函数 | 功能 | 需要句柄? |
|---|---|---|
| vTaskDelete() | 删除任务 | 是(删除自己时传NULL) |
| vTaskSuspend() | 挂起任务 | 是 |
| vTaskResume() | 恢复任务 | 是 |
| uxTaskPriorityGet() | 获取任务优先级 | 是 |
| vTaskPrioritySet() | 设置任务优先级 | 是 |
我个人的习惯是,在创建任务时就把句柄存到一个结构体里,方便统一管理:
typedef struct {
TaskHandle_t xTaskHandle;
const char *pcTaskName;
UBaseType_t uxPriority;
} TaskInfo_t;
TaskInfo_t xTaskList[] = {
{NULL, "Task1", 1},
{NULL, "Task2", 2},
{NULL, "Task3", 3}
};
void CreateAllTasks(void)
{
for (int i = 0; i < 3; i++)
{
xTaskCreate(
vGenericTask,
xTaskList[i].pcTaskName,
256,
(void*)&xTaskList[i],
xTaskList[i].uxPriority,
&xTaskList[i].xTaskHandle
);
}
}
这样设计的好处是,你随时可以通过数组索引找到某个任务的句柄,做删除、挂起等操作时特别方便。我在做多传感器数据采集项目时就是这么干的,管理十几个任务,井井有条。
uxTaskGetNumberOfTasks() 查看当前系统里有多少个任务在跑。如果发现任务数异常增多,八成是创建了没删除,或者删除不彻底。
最后说一句,任务创建和删除看似简单,但用好了能解决很多实际问题。比如你可以设计一个动态任务池,按需创建临时任务来处理突发事件,处理完就删除。这样既节省内存,又灵活。但切记——别在中断服务函数里创建或删除任务,那是找死。中断里只适合发信号,具体操作交给任务去做。