2. VxWorks任务模型与状态机
VxWorks 的实时性建立在“任务”这一基本调度单位之上。理解任务模型,核心是理解三件事:内核用什么数据结构描述任务、任务在生命周期中如何迁移状态、以及任务栈如何被创建、使用和检测溢出。这三者共同决定了系统能否在确定的时间内完成关键响应。
任务控制块(TCB)结构
每个任务在内核中都对应一个任务控制块(Task Control Block,TCB)。TCB 是任务的“身份证”加“运行档案”,内核调度、切换、统计、异常处理都围绕它展开。典型 TCB 包含以下关键字段:
- 任务标识:任务 ID(TID)与任务名,用于 windTaskId 查找与调试识别。
- 调度信息:优先级、任务选项(如 VX_FP_TASK、VX_UNBREAKABLE)、调度策略相关标志。
- 上下文保存区:CPU 寄存器组、程序计数器、状态字等,任务切换时保存与恢复。
- 栈信息:栈基址、栈顶指针、栈大小、栈边界哨兵值,用于溢出检测。
- 状态与等待信息:当前状态、等待对象(信号量、消息队列、事件)、延迟计数值。
- 统计与钩子:CPU 占用时间、切换次数、任务钩子函数指针。
TCB 由内核在任务创建时从系统内存池分配,删除任务时回收。因此任务数量受内存池大小限制,而不是无限增长。理解 TCB 字段,有助于解释为什么任务优先级、栈大小、选项必须在创建时确定,而不是运行中随意更改。
任务状态转换
VxWorks 任务状态机是理解调度的关键。任务并非只有“运行”和“不运行”两种状态,而是包含就绪(READY)、运行(RUNNING)、阻塞(PEND)、延迟(DELAY)、挂起(SUSPEND)等状态。状态之间由内核 API 和事件驱动迁移。
状态迁移的驱动来源主要有三类:调度器行为(抢占、时间片)、任务自身调用(taskDelay、semTake)、外部干预(taskSuspend、taskResume、taskDelete)。需要特别注意:挂起状态可以叠加在阻塞或延迟之上。一个任务可能同时处于“阻塞+挂起”,此时即使等待的资源可用,它也不会进入就绪队列,必须先执行 taskResume 才能继续。这是调试中常见的“任务不跑”原因之一。
任务创建与删除 API
VxWorks 提供 taskSpawn 和 taskInit/taskActivate 两种创建路径。taskSpawn 一步完成创建与激活,适合大多数场景:
#include <taskLib.h>
int taskId = taskSpawn(
"tSensor", /* 任务名 */
100, /* 优先级:数值越小优先级越高 */
VX_FP_TASK, /* 选项:使用浮点单元 */
4096, /* 栈大小(字节) */
(FUNCPTR)sensorTask,/* 入口函数 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 最多10个参数 */
);
if (taskId == ERROR) {
/* 创建失败:内存不足或参数非法 */
}
taskInit 只创建 TCB 和栈但不激活,随后可用 taskActivate 激活,适合需要先设置任务变量再启动的场景。删除任务使用 taskDelete(taskId),它会释放 TCB 和栈。若任务持有信号量或互斥量,删除前应先释放,否则可能造成资源永久占用。任务入口函数正常 return 也会触发删除,但更推荐显式 taskDelete(0) 删除自身,语义更清晰。
任务栈管理与溢出检测
每个任务拥有独立栈,栈大小在创建时确定。栈用于保存局部变量、函数调用返回地址、中断嵌套上下文。栈太小会导致溢出,破坏相邻内存,表现为随机死机或数据错乱;栈太大则浪费内存,影响可创建任务数量。
VxWorks 提供两种溢出检测机制。第一种是栈哨兵:taskSpawn 时在栈边界写入特定模式(如 0xEEEEEEEE),通过 checkStack 检查哨兵是否被改写。第二种是 MMU 保护:若启用,栈边界可设为不可访问页,溢出立即触发异常,定位更精确。
/* 检查任务栈使用情况 */
STATUS status = checkStack(taskId, 0);
/* 返回 OK 表示哨兵完好;ERROR 表示可能溢出 */
/* 查看栈高水位:剩余未使用字节数 */
int unused = taskId->stackRemaining; /* 调试器或 taskInfoGet 获取 */
实际工程中,栈大小估算可按“局部变量总和 + 调用深度 × 每层开销 + 中断嵌套余量”计算。例如一个任务局部变量约 200 字节,调用深度 8 层,每层约 64 字节,中断嵌套预留 512 字节,则基础需求约 200 + 8×64 + 512 = 1224 字节。考虑对齐和编译器优化,建议取 2 倍余量,即 4096 字节是常见安全起点。若使用浮点、递归或大数组,需单独评估。