4、任务创建与删除:任务创建API、任务删除API、动态任务管理、静态任务配置
好,咱们今天聊聊任务创建和删除。这部分内容,说实在的,是每个RTOS开发者每天都要打交道的。你想想看,一个系统里跑着十几个甚至几十个任务,怎么把它们生出来,又怎么让它们“寿终正寝”,这里面的门道可不少。
我个人习惯把任务管理分成两块:动态管理和静态配置。动态管理就像是在运行时“生孩子”,而静态配置则像是提前画好“族谱”。两种方式各有千秋,咱们一个一个说。
4.1 任务创建API
任务创建,说白了就是告诉内核:“嘿,我要跑一段代码,你帮我安排个线程。” 不同的RTOS,API名字可能不一样,但核心参数都差不多。
我拿最常用的FreeRTOS举个例子:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名字(调试用)
configSTACK_DEPTH_TYPE usStackDepth, // 栈深度
void *pvParameters, // 传给任务的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t *pxCreatedTask // 返回的任务句柄
);
这里有个坑,我当年刚入行时就踩过。栈深度这个参数,很多人随便填个值,结果系统跑着跑着就崩了。为什么会这样?因为每个任务都有自己的栈空间,如果栈不够用,就会溢出,覆盖掉其他任务的数据。
再看一个uC/OS-III的例子:
OS_TCB MyTaskTCB;
CPU_STK MyTaskStk[128];
void AppTaskStart (void *p_arg)
{
OS_ERR err;
OSTaskCreate((OS_TCB *)&MyTaskTCB,
(CPU_CHAR *)"My Task",
(OS_TASK_PTR )MyTask,
(void *)0,
(OS_PRIO )5,
(CPU_STK *)&MyTaskStk[0],
(CPU_STK_SIZE)128 / 10,
(CPU_STK_SIZE)128,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void *)0,
(OS_OPT )OS_OPT_TASK_NONE,
(OS_ERR *)&err);
}
你看,uC/OS需要你提前准备好任务控制块(TCB)和栈空间。这种方式更“静态”一些,但好处是内存分配在编译时就确定了,运行时不会因为动态分配失败而出问题。
4.2 任务删除API
有创建就有删除。任务删除,就是告诉内核:“这个任务我不需要了,把资源还回来。”
FreeRTOS里是这样:
void vTaskDelete(TaskHandle_t xTaskToDelete);
参数传NULL,就是删除自己。传其他任务的句柄,就是删除别人。
我遇到过最典型的问题:一个任务里循环malloc,但忘了free。任务被删除后,那些内存就成了“孤儿”,永远无法释放。时间一长,系统内存就耗尽了。
uC/OS的删除API长这样:
void OSTaskDel(OS_TCB *p_tcb, OS_ERR *p_err);
嗯,这里要注意,uC/OS不允许删除空闲任务和中断服务任务。如果你试图这么做,它会返回一个错误码。
4.3 动态任务管理
动态任务管理,就是运行时创建和删除任务。这种方式很灵活,但风险也大。
优点很明显:
- 内存按需分配,不浪费
- 可以应对不确定的工作负载
- 适合插件式架构
缺点也不少:
- 内存碎片化问题
- 创建/删除操作耗时不确定
- 容易忘记释放资源
我个人的建议是:能不动态创建,就别动态创建。尤其是在安全关键系统里,动态内存分配往往是禁止的。你想想看,如果系统运行到一半,malloc失败了,任务创建不出来,整个逻辑就乱了。
4.4 静态任务配置
静态配置,就是所有任务在编译时就定义好了。系统启动时,内核一次性把它们都创建好,之后就不再变动。
这种方式在汽车电子、工业控制等领域非常常见。为什么?因为确定性。所有任务的内存、优先级、栈大小都是固定的,系统行为可以预测。
FreeRTOS的静态创建API:
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pvTaskCode,
const char * const pcName,
uint32_t ulStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
StackType_t *puxStackBuffer,
StaticTask_t *pxTaskBuffer
);
你看,栈空间和TCB都是你提供的,内核只管用,不管分配。这样就不会有内存分配失败的问题。
uC/OS的静态配置就更典型了。它有一个os_cfg_app.h文件,里面定义了所有任务:
#define OS_CFG_APP_TASK_START_PRIO 2u
#define OS_CFG_APP_TASK_START_STK_SIZE 128u
#define OS_CFG_APP_TASK1_PRIO 5u
#define OS_CFG_APP_TASK1_STK_SIZE 256u
#define OS_CFG_APP_TASK2_PRIO 6u
#define OS_CFG_APP_TASK2_STK_SIZE 256u
所有任务在系统启动时一次性创建。运行期间,任务数量是固定的,不会多也不会少。
| 对比项 | 动态管理 | 静态配置 |
|---|---|---|
| 内存分配时机 | 运行时 | 编译时 |
| 灵活性 | 高 | 低 |
| 确定性 | 低 | 高 |
| 内存碎片 | 有 | 无 |
| 适用场景 | 消费电子、原型开发 | 汽车、工业、航天 |
好了,任务创建与删除就讲这么多。总结一下:动态管理图方便,静态配置图安心。选哪种,取决于你的项目需求。我个人更倾向于静态配置,尤其是做产品级开发时。毕竟,系统稳定比什么都重要。