任务创建与管理:TCB、栈配置、优先级与状态迁移
各位同学,今天我们来聊聊任务创建与管理。这是多任务调度的基石,说白了,就是操作系统怎么认识你的任务、怎么给它分配资源、怎么决定谁先跑。
我个人习惯把任务比作一个「独立的小工人」。每个工人都有自己的工具箱(栈)、身份牌(TCB)、级别(优先级),以及当前状态(在干活、在等料、还是被叫停了)。
一、任务控制块(TCB)——任务的身份证
TCB 是什么?它就是操作系统用来管理任务的数据结构。每个任务创建时,内核都会分配一个 TCB。里面存了什么?我列一下关键字段:
- 任务栈指针:指向当前栈顶,保存现场用
- 任务状态:就绪、运行、阻塞、挂起
- 优先级:数值越小优先级越高(常见做法)
- 任务入口函数:任务从哪开始执行
- 任务名称:调试时方便看
- 延时/超时计数器:用于阻塞等待
我在项目中遇到过一个问题:某个任务莫名其妙不跑了。查了半天,发现是 TCB 被意外覆盖了。从那以后,我坚持把 TCB 放在独立的 RAM 段,并且加上保护。
核心要点:TCB 是内核与任务之间的唯一接口。丢了 TCB,任务就丢了。
二、任务栈配置——给工人准备工具箱
每个任务都需要自己的栈空间。为什么?因为函数调用、局部变量、中断嵌套都要用栈。如果栈不够大,任务就会跑飞,而且很难查。
我建议栈大小这样估算:
- 任务函数调用深度 × 每次调用需要的字节数
- 加上中断嵌套的最大深度
- 再加 20% 的安全余量
举个例子,一个简单的 LED 闪烁任务,128 字节可能就够了。但一个带协议栈的通信任务,至少 1024 字节起步。
我的经验:我曾经在一个项目中把栈设得太小,结果任务运行几小时后随机崩溃。后来用栈水印检测,才发现栈溢出。嗯,现在我的每个任务都加栈溢出检测。
三、任务优先级设定——谁说了算?
优先级决定了任务被调度的顺序。实时系统里,优先级就是命根子。
常见的做法:
- 抢占式调度:高优先级任务就绪,立刻抢 CPU
- 时间片轮转:同优先级任务轮流跑
优先级设定有几个原则:
- 时间敏感的任务(如电机控制)给最高优先级
- 通信任务(如 CAN、UART)给中等优先级
- 后台任务(如日志、显示)给最低优先级
注意:优先级反转是个大坑。低优先级任务占着资源,高优先级任务等它释放,中间还有个中优先级任务插队。我曾经被这个问题折腾了两天,最后用优先级继承协议解决了。
四、任务状态迁移——任务的生老病死
任务有四种基本状态:就绪、运行、阻塞、挂起。它们之间的迁移关系,我画了一张图:
这张图你看懂了吗?我解释一下关键路径:
- 就绪 → 运行:调度器选中你,你就跑
- 运行 → 就绪:时间片用完,或者被更高优先级任务抢了
- 运行 → 阻塞:你等某个资源(信号量、队列、延时)
- 阻塞 → 就绪:资源到了,你回到就绪队列排队
- 运行 → 挂起:主动或被动暂停
- 挂起 → 就绪:被恢复后重新排队
避坑指南:我曾经在项目里让一个高优先级任务频繁阻塞,结果低优先级任务永远得不到 CPU。后来我加了看门狗,才意识到问题。记住:阻塞不是休息,是等待资源。
五、代码示例:创建一个任务
下面是一个 FreeRTOS 风格的任务创建示例:
// 任务栈定义
#define TASK_STACK_SIZE 256
StackType_t taskStack[TASK_STACK_SIZE];
// 任务控制块
StaticTask_t taskTCB;
// 任务函数
void vTaskFunction(void *pvParameters) {
while(1) {
// 任务逻辑
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 创建任务
TaskHandle_t xTaskHandle = NULL;
xTaskHandle = xTaskCreateStatic(
vTaskFunction, // 任务函数
"MyTask", // 任务名称
TASK_STACK_SIZE, // 栈大小
NULL, // 参数
2, // 优先级(0最高)
taskStack, // 栈指针
&taskTCB // TCB指针
);
if (xTaskHandle != NULL) {
// 任务创建成功
vTaskStartScheduler();
}
这段代码里,我手动分配了栈和 TCB。为什么不用动态分配?因为工业现场,你想想看,动态分配可能失败,而且碎片化严重。我习惯静态分配,心里踏实。
六、任务状态迁移实战
最后,我分享一个实际场景:
一个数据采集任务,优先级设为 3。它平时在阻塞状态,等待定时器触发。触发后进入就绪,调度器让它运行。运行中它要读取 ADC,如果 ADC 忙,它再次阻塞。读完后发信号量给处理任务,然后继续阻塞等下一次触发。
这个流程里,任务在就绪、运行、阻塞之间来回切换。挂起状态很少用,一般用于调试或低功耗模式。
小技巧:调试时,我经常用挂起/恢复来模拟任务异常,测试看门狗是否生效。嗯,这招挺管用的。
好了,任务创建与管理就讲到这里。记住 TCB 是身份证、栈是工具箱、优先级是级别、状态迁移是生命周期。把这四个点吃透,多任务调度你就入门了。
公众号:蓝海资料掘金营,微信deep3321