3. 任务控制块(TCB):任务调度的核心数据结构

任务控制块(Task Control Block, TCB)是 RTOS 中用于描述和管理任务的核心数据结构。每个任务在系统中都有一个唯一的 TCB,它包含了任务的所有关键信息,如栈指针、优先级、状态标志以及链表管理节点。TCB 是任务调度器进行上下文切换和任务状态迁移的依据,其设计直接影响系统的实时性和可扩展性。

核心概念: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;
设计要点:TCB 中的 pxTopOfStack 是上下文切换的关键。每次任务被切换出去时,CPU 寄存器会被压入当前任务栈,然后 pxTopOfStack 更新为新的栈顶位置;切换回来时,调度器从 pxTopOfStack 指向的位置恢复寄存器。

3.2 栈指针(pxTopOfStack)

栈指针是 TCB 中最重要的字段之一,它直接关联到任务的执行上下文。在 RTOS 中,每个任务拥有独立的栈空间,用于存储局部变量、函数调用参数以及中断发生时的寄存器现场。

  • 初始创建:任务创建时,系统会分配一块连续的内存作为任务栈,并将初始的栈指针设置为栈顶(高地址)。同时,会将任务入口函数地址、初始寄存器值等预置到栈中,模拟一个“刚被中断”的现场。
  • 上下文切换:当调度器决定切换任务时,当前任务的 CPU 寄存器(如 R0-R12、LR、PC、PSP 等)会被压入当前任务栈,然后 pxTopOfStack 更新为最新的栈顶地址。接着,调度器从下一个任务的 TCB 中取出 pxTopOfStack,并从该地址恢复寄存器。
  • 栈溢出检测:通过 pxStack(栈起始地址)和 usStackDepth(栈深度),系统可以在每次上下文切换时检查栈指针是否越界,从而触发栈溢出钩子函数。
常见陷阱:栈指针损坏是 RTOS 中最难调试的问题之一。通常由栈溢出、错误的指针操作或中断嵌套过深导致。建议在 TCB 中保留栈起始地址和深度信息,并启用硬件栈溢出检测功能。

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):当任务等待信号量、队列、互斥量等内核对象时,挂接到对应对象的事件等待列表。
链表管理优势:使用双向链表可以实现 O(1) 时间复杂度的插入和删除操作,这对于实时系统至关重要。调度器在每次调度时,只需从就绪列表中取出最高优先级的链表头节点即可。

以下是一个简化的链表节点结构:

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 如何通过链表节点挂接到不同的系统链表中,以及调度器如何通过链表进行任务管理。

TCB 链表管理结构图 就绪列表(按优先级) 优先级 0 链表头 TCB_A (pxTopOfStack) TCB_B (pxTopOfStack) 优先级 1 链表头 TCB_C (pxTopOfStack) 阻塞列表(按超时时间) 阻塞链表头 TCB_D (超时: 50ms) TCB_E (超时: 100ms) 挂起列表 挂起链表头 TCB_F (挂起中) 事件等待列表(信号量) 信号量等待链表头 TCB_G (等待信号量) TCB 链表管理关键点 • 每个 TCB 嵌入 ListItem_t 节点 • 调度器通过链表头快速访问任务 调度器操作 • 从就绪列表取最高优先级节点 • 从阻塞列表检查超时任务

3.7 TCB 生命周期管理

TCB 从创建到销毁经历以下阶段:

  1. 创建:系统分配 TCB 内存(静态或动态),初始化栈指针、优先级、状态标志为就绪,将 TCB 挂接到就绪列表。
  2. 就绪:TCB 在就绪列表中等待调度。调度器根据优先级选择最高优先级的 TCB 投入运行。
  3. 运行:TCB 状态标志更新为运行,调度器从 pxTopOfStack 恢复上下文。
  4. 阻塞/挂起:当任务等待事件或被挂起时,TCB 从就绪列表移除,挂接到阻塞列表或挂起列表。
  5. 恢复:事件到达或超时后,TCB 从阻塞列表移除,重新挂接到就绪列表。
  6. 删除:任务执行完毕或被显式删除,TCB 从所有链表移除,内存被回收。
优化建议:在双缓冲机制中,TCB 的链表操作(插入/删除)需要保证原子性。建议使用关中断或自旋锁保护链表操作,防止在中断服务程序(ISR)中操作 TCB 链表时发生竞态条件。

3.8 本章小结

任务控制块(TCB)是 RTOS 任务管理的基石。本章详细介绍了 TCB 的核心字段:栈指针(pxTopOfStack)用于上下文切换,优先级(uxPriority)决定调度顺序,状态标志(ucCurrentStatus)标识任务生命周期,链表节点(ListItem_t)实现高效的任务组织。理解 TCB 的结构和链表管理机制,是后续学习任务调度算法和双缓冲协同优化的前提。