3. 任务控制块(TCB):任务调度的核心数据结构
任务控制块(Task Control Block, TCB)是 RTOS 中用于描述和管理任务的核心数据结构。每个任务在系统中都有一个唯一的 TCB,它包含了任务的所有关键信息,如栈指针、优先级、状态标志以及链表管理节点。TCB 是任务调度器进行上下文切换和任务状态迁移的依据,其设计直接影响系统的实时性和可扩展性。
3.1 TCB 数据结构定义
一个典型的 TCB 结构体包含以下关键字段。不同 RTOS 的实现细节可能有所差异,但核心要素基本一致。
| 字段名称 | 数据类型 | 描述 |
|---|---|---|
pxTopOfStack |
StackType_t * |
栈指针,指向当前任务栈顶,用于上下文切换时保存/恢复寄存器 |
uxPriority |
UBaseType_t |
任务优先级,数值越小优先级越高(或反之,取决于实现) |
ucCurrentStatus |
uint8_t |
任务当前状态标志,如就绪、运行、阻塞、挂起等 |
xStateListItem |
ListItem_t |
状态链表节点,用于将任务挂接到就绪列表、阻塞列表或挂起列表 |
xEventListItem |
ListItem_t |
事件链表节点,用于任务在等待事件(如信号量、队列)时挂接到事件列表 |
pcTaskName |
char * |
任务名称,用于调试和日志输出 |
usStackDepth |
uint16_t |
任务栈深度(以字为单位),用于栈溢出检测 |
以下是一个简化的 TCB 结构体定义示例(基于 FreeRTOS 风格):
typedef struct tskTaskControlBlock {
volatile StackType_t *pxTopOfStack; /* 栈指针 */
ListItem_t xStateListItem; /* 状态链表节点 */
ListItem_t xEventListItem; /* 事件链表节点 */
UBaseType_t uxPriority; /* 任务优先级 */
StackType_t *pxStack; /* 栈起始地址 */
char pcTaskName[configMAX_TASK_NAME_LEN]; /* 任务名称 */
uint8_t ucCurrentStatus; /* 任务状态标志 */
uint16_t usStackDepth; /* 栈深度 */
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /* TCB 编号,用于跟踪 */
#endif
} tskTCB;
pxTopOfStack 是上下文切换的关键。每次任务被切换出去时,CPU 寄存器会被压入当前任务栈,然后 pxTopOfStack 更新为新的栈顶位置;切换回来时,调度器从 pxTopOfStack 指向的位置恢复寄存器。
3.2 栈指针(pxTopOfStack)
栈指针是 TCB 中最重要的字段之一,它直接关联到任务的执行上下文。在 RTOS 中,每个任务拥有独立的栈空间,用于存储局部变量、函数调用参数以及中断发生时的寄存器现场。
- 初始创建:任务创建时,系统会分配一块连续的内存作为任务栈,并将初始的栈指针设置为栈顶(高地址)。同时,会将任务入口函数地址、初始寄存器值等预置到栈中,模拟一个“刚被中断”的现场。
- 上下文切换:当调度器决定切换任务时,当前任务的 CPU 寄存器(如 R0-R12、LR、PC、PSP 等)会被压入当前任务栈,然后
pxTopOfStack更新为最新的栈顶地址。接着,调度器从下一个任务的 TCB 中取出pxTopOfStack,并从该地址恢复寄存器。 - 栈溢出检测:通过
pxStack(栈起始地址)和usStackDepth(栈深度),系统可以在每次上下文切换时检查栈指针是否越界,从而触发栈溢出钩子函数。
3.3 优先级(uxPriority)
优先级决定了任务在就绪队列中的调度顺序。在抢占式调度中,高优先级任务会立即抢占正在运行的低优先级任务。
| 优先级模型 | 说明 | 典型 RTOS |
|---|---|---|
| 数值越小优先级越高 | 优先级 0 为最高,数值越大优先级越低 | FreeRTOS, uC/OS-II |
| 数值越大优先级越高 | 优先级 255 为最高,数值越小优先级越低 | RT-Thread, Zephyr |
| 位图优先级 | 使用位图表示优先级,支持快速查找最高优先级就绪任务 | uC/OS-III, 部分商业 RTOS |
优先级相关的调度策略:
- 抢占式调度:当高优先级任务就绪时,立即抢占当前运行的低优先级任务。
- 时间片轮转:同优先级任务之间按时间片轮流执行,防止某个任务长时间独占 CPU。
- 优先级继承:用于解决优先级反转问题,临时提升持有资源的低优先级任务的优先级。
3.4 状态标志(ucCurrentStatus)
任务状态标志用于标识任务当前所处的生命周期阶段。RTOS 通常定义以下基本状态:
| 状态 | 标志值 | 描述 |
|---|---|---|
| 就绪(Ready) | 0x01 |
任务已准备好运行,等待调度器分配 CPU |
| 运行(Running) | 0x02 |
任务当前正在占用 CPU 执行 |
| 阻塞(Blocked) | 0x04 |
任务因等待某个事件(如延时、信号量、队列)而暂停 |
| 挂起(Suspended) | 0x08 |
任务被显式挂起,需要其他任务或中断恢复 |
| 删除(Deleted) | 0x10 |
任务已被删除,TCB 等待系统回收 |
状态标志通常使用位掩码实现,允许一个任务同时处于多种状态(例如,阻塞且挂起)。调度器通过检查状态标志来决定任务是否可以被调度。
/* 状态标志位定义 */
#define TASK_STATUS_READY ( ( uint8_t ) 0x01 )
#define TASK_STATUS_RUNNING ( ( uint8_t ) 0x02 )
#define TASK_STATUS_BLOCKED ( ( uint8_t ) 0x04 )
#define TASK_STATUS_SUSPENDED ( ( uint8_t ) 0x08 )
#define TASK_STATUS_DELETED ( ( uint8_t ) 0x10 )
/* 检查任务是否处于就绪状态 */
#define TASK_IS_READY( pxTCB ) ( ( pxTCB->ucCurrentStatus & TASK_STATUS_READY ) != 0 )
3.5 链表管理:TCB 的组织方式
RTOS 使用双向链表来管理所有 TCB,实现高效的任务插入、删除和遍历。每个 TCB 中嵌入 ListItem_t 结构体,作为链表节点。
典型的链表管理结构:
- 就绪列表(Ready List):按优先级组织的多个链表,每个优先级对应一个链表头。所有就绪状态的任务挂接到对应优先级的链表中。
- 阻塞列表(Blocked List):所有因等待事件而阻塞的任务挂接到此列表,通常按超时时间排序。
- 挂起列表(Suspended List):所有被显式挂起的任务挂接到此列表。
- 事件等待列表(Event List):当任务等待信号量、队列、互斥量等内核对象时,挂接到对应对象的事件等待列表。
以下是一个简化的链表节点结构:
typedef struct xLIST_ITEM {
TickType_t xItemValue; /* 用于排序的值(如优先级、超时时间) */
struct xLIST_ITEM *pxNext; /* 指向下一个节点 */
struct xLIST_ITEM *pxPrevious; /* 指向前一个节点 */
void *pvOwner; /* 指向所属的 TCB */
void *pvContainer; /* 指向所属的链表 */
} ListItem_t;
3.6 TCB 与链表的关系图
下图展示了 TCB 如何通过链表节点挂接到不同的系统链表中,以及调度器如何通过链表进行任务管理。
3.7 TCB 生命周期管理
TCB 从创建到销毁经历以下阶段:
- 创建:系统分配 TCB 内存(静态或动态),初始化栈指针、优先级、状态标志为就绪,将 TCB 挂接到就绪列表。
- 就绪:TCB 在就绪列表中等待调度。调度器根据优先级选择最高优先级的 TCB 投入运行。
- 运行:TCB 状态标志更新为运行,调度器从
pxTopOfStack恢复上下文。 - 阻塞/挂起:当任务等待事件或被挂起时,TCB 从就绪列表移除,挂接到阻塞列表或挂起列表。
- 恢复:事件到达或超时后,TCB 从阻塞列表移除,重新挂接到就绪列表。
- 删除:任务执行完毕或被显式删除,TCB 从所有链表移除,内存被回收。
3.8 本章小结
任务控制块(TCB)是 RTOS 任务管理的基石。本章详细介绍了 TCB 的核心字段:栈指针(pxTopOfStack)用于上下文切换,优先级(uxPriority)决定调度顺序,状态标志(ucCurrentStatus)标识任务生命周期,链表节点(ListItem_t)实现高效的任务组织。理解 TCB 的结构和链表管理机制,是后续学习任务调度算法和双缓冲协同优化的前提。