任务与任务控制块:任务的概念、任务状态与TCB结构体设计
各位同学,咱们今天聊聊RTOS里最核心的概念——任务。说白了,任务就是一段独立执行的程序,它有自己的栈、自己的优先级,看起来就像是一个“小CPU”在独自运行。我刚开始接触RTOS时,总觉得任务和函数调用差不多,后来踩了坑才明白,这俩完全是两码事。
任务到底是什么?
任务在RTOS里,是一个无限循环的执行单元。它不像普通函数那样调用完就返回,而是永远在“干活-等待-干活”的循环里打转。举个例子:
void task_led_blink(void *param) {
while(1) {
GPIO_SetBits(LED_PIN);
vTaskDelay(500); // 阻塞500ms
GPIO_ResetBits(LED_PIN);
vTaskDelay(500);
}
}
你看,这个任务永远不会结束。它每次执行完一轮,就主动让出CPU,等时间到了再继续。这就是任务和普通函数的本质区别——任务有生命周期,函数只有调用期。
任务的五种状态
任务在运行过程中,会在不同状态间切换。我习惯把任务状态比作一个人的日常:
- 就绪(Ready):任务已经准备好,就等CPU分配时间片。就像你排队等公交,车来了就能上。
- 运行(Running):任务正在占用CPU执行。相当于你正在开车。
- 阻塞(Blocked):任务在等待某个事件(延时、信号量、消息队列)。就像你在等红灯,车停着但发动机没熄火。
- 挂起(Suspended):任务被强制暂停,不参与调度。相当于你把车停在停车场,钥匙拔了。
- 初始(Dormant/Initial):任务刚创建,还没被调度器激活。就像车刚造好,还没上路。
为什么会这样设计?你想想看,如果所有任务都在抢CPU,系统就乱套了。状态机就是为了让任务有序地共享CPU资源。
状态转换图
下面这张图,是我自己画的任务状态转换关系。你仔细看,箭头上的条件就是触发状态变化的关键:
任务控制块(TCB)结构体设计
TCB是RTOS管理任务的核心数据结构。每个任务都有一个TCB,里面存着任务的所有“户口信息”。我设计TCB时,一般会包含以下几类字段:
| 字段类别 | 具体字段 | 说明 |
|---|---|---|
| 标识信息 | 任务ID、任务名称 | 用于调试和日志输出 |
| 栈管理 | 栈顶指针、栈底指针、栈大小 | 任务切换时保存/恢复上下文 |
| 调度信息 | 优先级、状态、时间片 | 调度器根据这些字段决定谁运行 |
| 同步信息 | 等待事件类型、等待对象指针 | 用于阻塞状态下的唤醒 |
| 链表节点 | 就绪链表、阻塞链表、挂起链表 | 将TCB串入不同状态链表 |
下面是一个典型的TCB结构体定义,我习惯用C语言实现:
typedef struct tcb {
// 标识信息
uint32_t task_id; // 任务ID
char task_name[16]; // 任务名称
// 栈管理
uint32_t *stack_top; // 当前栈顶指针
uint32_t *stack_base; // 栈底指针
uint32_t stack_size; // 栈大小(单位:字)
// 调度信息
uint8_t priority; // 任务优先级(0最高)
uint8_t state; // 任务状态
uint32_t time_slice; // 时间片(tick数)
// 同步信息
uint32_t wait_event; // 等待的事件类型
void *wait_obj; // 等待的对象指针
// 链表节点
struct list_node ready_node; // 就绪链表节点
struct list_node block_node; // 阻塞链表节点
struct list_node suspend_node; // 挂起链表节点
} tcb_t;
TCB的初始化流程
创建一个任务,本质上就是初始化一个TCB,然后把它挂到就绪链表里。流程如下:
- 分配TCB内存:从静态数组或动态堆中分配一个TCB结构体。
- 分配任务栈:为任务分配独立的栈空间。
- 初始化栈帧:在栈中预置任务入口地址、初始寄存器值。
- 设置调度参数:填写优先级、时间片、初始状态(通常为就绪)。
- 插入就绪链表:根据优先级将TCB插入就绪链表的合适位置。
你想想看,如果没有TCB,调度器怎么知道哪个任务该运行?说白了,TCB就是任务的“身份证”,调度器通过它来识别和管理所有任务。
任务状态与TCB的关系
任务状态其实就存储在TCB的state字段里。调度器通过遍历不同状态链表,来决定下一步该运行谁。举个例子:
- 当任务被创建时,TCB的state设为就绪,插入就绪链表。
- 当调度器选中该任务时,state改为运行,从就绪链表移除。
- 当任务调用
vTaskDelay()时,state改为阻塞,插入阻塞链表。 - 当阻塞时间到达,state改回就绪,重新插入就绪链表。
这个机制看起来简单,但实际实现时有很多细节。我记得有一次,任务在阻塞期间被意外删除了,但阻塞链表里还留着它的TCB指针,结果系统在遍历链表时访问了野指针……嗯,从那以后我每次删除任务都会检查它是否还在链表中。
小结
任务和TCB是RTOS的基石。任务定义了“做什么”,TCB定义了“怎么管”。理解任务状态转换和TCB结构,是后续学习调度算法、同步机制的基础。你只要把这张状态转换图记牢,把TCB结构体吃透,后面讲优先级调度、时间片轮转时就会轻松很多。