4. 任务状态监控与诊断

VxWorks 的任务是内核调度的基本单位,其状态变化贯穿从创建到删除的完整生命周期。运行状态诊断的核心,不是等到系统卡死才去查,而是在任务状态迁移的每个关键节点上留下可观测的痕迹。本节围绕任务创建、就绪/运行、挂起、删除四个阶段,说明如何用 WindSh 命令与内核 API 跟踪状态,并识别栈溢出、优先级反转、死锁与饥饿四类典型问题。

一、任务状态模型与迁移条件

VxWorks 任务主要有就绪(READY)、运行(RUNNING)、阻塞(PEND)、延迟(DELAY)、挂起(SUSPEND)和停止(STOP)等状态。状态迁移由内核原语触发:taskSpawn 创建后进入就绪队列;taskDelay 进入延迟;semTake 等待信号量时进入阻塞;taskSuspend 强制挂起;taskDelete 释放任务控制块(TCB)与栈空间。诊断的关键是判断“任务当前处于哪个状态、由谁触发、是否可恢复”。

VxWorks 任务状态迁移与诊断观测点 就绪 READY 在就绪队列排队 运行 RUNNING 占用 CPU 执行 阻塞 PEND 等待信号量/消息 延迟 DELAY taskDelay 计时中 挂起 SUSPEND taskSuspend 冻结 删除 DELETE TCB/栈已释放 调度选中 semTake 失败 taskDelete taskDelay taskSuspend taskResume semGive 唤醒 诊断观测点: ① i / taskShow 查看状态与优先级 ② ti / taskIdList 遍历任务 ③ sp / taskStackShow 检查栈余量 ④ semShow 查看信号量持有者 ⑤ 看门狗/心跳任务检测长时间 RUNNING 或 PEND 异常

二、任务创建与栈使用监控

任务创建时最容易埋下隐患的是栈空间估算不足。VxWorks 任务栈需容纳函数调用帧、局部变量、中断嵌套保存的上下文以及浮点运算的额外开销。经验上,普通任务栈建议不低于 4KB,涉及递归、大数组或浮点运算的任务应放大到 8KB~16KB。创建示例:

#define TASK_PRIO   120
#define TASK_STACK  8192
#define TASK_OPT    VX_FP_TASK | VX_SUPERVISOR_MODE

TASK_ID gTaskId = taskSpawn("tDiag",
                            TASK_PRIO,
                            TASK_OPT,
                            TASK_STACK,
                            (FUNCPTR)diagTask,
                            0,0,0,0,0,0,0,0,0,0);

创建后应立即用 taskStackShow 或 WindSh 的 sp 命令查看栈的高水位标记。VxWorks 在栈初始化时写入固定填充模式,运行时通过扫描未被覆盖的填充字节估算剩余栈空间。若剩余量长期低于 20%,说明栈偏小,应增大 TASK_STACK 或减少局部变量。栈溢出通常表现为任务异常挂起、数据被踩踏或系统复位,因此栈监控应作为周期性巡检项,而不是只在故障后检查。

三、优先级反转的识别与处理

优先级反转发生在高优先级任务因等待低优先级任务持有的互斥资源而被阻塞,而中优先级任务又抢占了低优先级任务,导致高优先级任务被间接延迟。典型场景是:高优先级任务 H 等待信号量 S,S 被低优先级任务 L 持有,此时中优先级任务 M 就绪并抢占 L,H 只能继续等待。识别方法是观察 semShow 输出中信号量的持有者与等待队列,若发现高优先级任务长时间处于 PEND 且持有者是低优先级任务,即可判定。

VxWorks 提供优先级继承机制:使用 semMCreate 创建互斥信号量并设置 SEM_INVERSION_SAFE 选项,当高优先级任务等待时,持有者的优先级会被临时提升到等待者中的最高优先级,释放后恢复。示例:

SEM_ID mutex = semMCreate(SEM_Q_PRIORITY |
                          SEM_INVERSION_SAFE |
                          SEM_DELETE_SAFE);

注意:优先级继承只对互斥信号量有效,二进制信号量不提供该保护。若系统中存在多个互斥资源,应统一使用互斥信号量并开启反转保护,避免混用。

四、死锁与饥饿的区分

死锁是多个任务互相等待对方持有的资源,形成循环等待,任何一方都无法推进。例如任务 A 持有信号量 S1 等待 S2,任务 B 持有 S2 等待 S1。诊断时可用 semShow 逐个查看信号量持有者,若出现 A 等 B、B 等 A 的闭环,即为死锁。预防手段包括:统一资源申请顺序、使用带超时的 semTake(sem, timeout)、避免在持有锁时调用可能阻塞的接口。

饥饿则是某任务长期得不到调度机会,通常由优先级设计不当或资源被高优先级任务持续占用引起。与死锁不同,饥饿任务本身并未形成循环等待,只是永远排在队尾。识别方法是统计各任务在就绪队列中的等待时间,若低优先级任务长时间未获得 CPU,而高优先级任务持续运行,则存在饥饿。可通过优先级老化、时间片轮转或调整任务优先级缓解。

诊断顺序建议:先用 i 查看所有任务状态,锁定异常任务;再用 ti 查看其 TCB 与优先级;接着用 semShow 检查其等待的信号量;最后结合 sp 判断是否栈溢出。四类问题中,栈溢出多表现为任务消失或复位,优先级反转与死锁表现为 PEND 不返回,饥饿表现为 READY 但长期不运行。

五、删除与资源回收

任务删除必须确保其持有的信号量、打开的文件描述符、分配的内存等资源被释放。直接 taskDelete 会强制回收 TCB 和栈,但不会自动释放任务持有的信号量,可能造成其他任务永久阻塞。推荐做法是设置删除钩子或使用 taskDeleteHookAdd,在删除前执行清理逻辑。删除后应确认任务 ID 不再出现在 taskIdList 中,且相关信号量计数恢复正常。

问题类型典型现象关键诊断命令处理方向
栈溢出任务异常消失、系统复位sp / taskStackShow增大栈空间,减少局部变量
优先级反转高优先级任务长时间 PENDsemShow改用互斥信号量并开启继承
死锁多任务互相等待,均不推进semShow + i统一加锁顺序,使用超时
饥饿低优先级任务长期 READY 不运行i / taskShow优先级老化或时间片轮转

综合来看,任务状态监控应形成“创建时记录、运行时巡检、异常时定位、删除时确认”的闭环。把状态迁移、栈余量、信号量持有关系三类信息结合起来,才能在问题扩大前完成诊断与处置。