2. RTOS任务感知基础

在裸机调试中,CPU 的执行流是线性的,调试器只要看 PC、SP 和寄存器就能还原现场。但一旦引入 RTOS,情况就完全不同:多个任务在同一个 CPU 上交替运行,每个任务都有自己的栈、自己的上下文、自己的状态。此时如果调试器仍然只认识 CPU,它看到的就只是"当前正在跑的那段代码",而看不到"系统里到底有几个任务、它们分别处于什么状态、谁在等谁"。TRACE32 之所以能做 RTOS 级调试,核心就在于它通过 RTOS 感知(RTOS Awareness) 把操作系统的内部数据结构"翻译"成调试器可以理解的任务视图。

一、TRACE32 感知 RTOS 的机制

TRACE32 本身并不"天生"认识某个 RTOS。它依赖一个关键的中间层——RTOS 感知模块(在 TRACE32 中通常以 .men 或 .rtos 脚本形式提供,例如 rtos_freertos.men、rtos_ucos_iii.men)。这个模块本质上是一组"地址解析规则",它告诉 TRACE32:

  • 任务控制块(TCB)链表的头指针符号叫什么,例如 FreeRTOS 的 pxCurrentTCB 和就绪/阻塞链表;
  • TCB 结构体里,任务名、优先级、状态、栈顶指针、栈基址分别位于结构体的哪个偏移;
  • 任务状态字段的枚举值如何映射为"就绪/运行/阻塞/挂起"等语义。

因此,感知机制可以概括为一条"符号 → 结构体偏移 → 状态语义"的解析链。TRACE32 通过读取目标内存中的这些结构体,动态重建出任务列表。这也解释了一个常见现象:如果 RTOS 感知脚本版本与目标固件里的 RTOS 版本不匹配,任务列表就会显示异常甚至为空——因为偏移对不上了。

感知脚本必须与目标固件使用的 RTOS 版本、编译配置(如是否开启 configUSE_TRACE_FACILITY)一致,否则 TCB 字段偏移会错位。

二、任务列表显示

加载感知脚本后,最直观的入口是 TASK.List 或 TASK.TaskList 命令。它会弹出一个任务列表窗口,典型列包括:任务名、优先级、状态、栈指针、栈使用量、TCB 地址。下面是一个 FreeRTOS 目标上的示例输出(示例数据,用于说明格式):

TASK.List
  Name          Prio  State      StackPtr    StackUse   TCB
  IDLE           0    ready      0x20001F40  112/256    0x20000210
  Tmr Svc        2    blocked    0x20002080   96/512    0x20000340
  AppTask        3    running    0x20002400  388/1024   0x20000470
  CommTask       4    blocked    0x20002800  220/1024   0x200005A0

这张表就是 RTOS 调试的"总览面板"。它把原本隐藏在内存里的调度状态一次性摊开,让工程师能立刻判断:谁在运行、谁被阻塞、谁的栈快满了。

三、任务状态解析

任务状态不是简单的字符串,而是调度器内部状态机的映射。以 FreeRTOS 为例,任务可能处于 Running(运行)、Ready(就绪)、Blocked(阻塞)、Suspended(挂起) 四种基本状态。TRACE32 读取 TCB 中的 uxTCBState 或链表归属(就绪链表、阻塞链表、挂起链表)来判定状态。

理解状态的关键在于:同一时刻只有一个任务处于 Running(单核场景),其余任务要么 Ready 等待调度,要么 Blocked 等待事件(信号量、队列、延时),要么 Suspended 被显式挂起。当你在任务列表里看到某个任务长期处于 Blocked,就要进一步查它阻塞在哪个对象上——这通常需要结合 TASK.STATE 或信号量/队列视图。

四、任务控制块读取

任务列表背后是 TCB。TRACE32 允许直接查看某个任务的 TCB 内容,命令形如 TASK.TCB <任务名> 或直接 Var.View %Hex 。TCB 里保存了任务的全部"身份信息":栈顶指针、栈基址、优先级、状态、事件列表项、任务名指针等。

读取 TCB 的实用价值在于:当任务列表显示异常(例如任务名乱码、栈指针为 0)时,直接看 TCB 原始内存可以判断是感知脚本偏移错误,还是目标内存真的被踩了。例如,若 TCB 中栈顶指针指向了非法区域,往往意味着栈溢出已经破坏了 TCB 本身。

五、任务栈使用查看

栈使用量是 RTOS 调试中最常被关注的指标之一。TRACE32 通过两种方式估算:一是读取 TCB 中记录的栈基址与当前栈指针,计算已用空间;二是利用 RTOS 的栈填充模式(如 FreeRTOS 用 0xA5 填充未使用栈区),扫描未被覆盖的填充字节,得到"高水位线"。

下面这张图把本节的核心对象串起来:从目标内存中的 TCB 链表,到 TRACE32 感知脚本的解析,再到任务列表、状态、栈使用三个视图的呈现关系。

TRACE32 RTOS 任务感知解析链 目标内存(RTOS 内部结构) pxCurrentTCB 当前任务指针 就绪链表 / 阻塞链表 任务归属判定 TCB 结构体 任务名 / 优先级 状态 / 栈顶 / 栈基址 任务栈区 0xA5 填充模式 高水位线扫描 RTOS 感知脚本 rtos_freertos.men 符号 → 结构体偏移 状态枚举 → 语义映射 版本 / 配置匹配校验 读取 任务列表视图 TASK.List 名称 / 优先级 / 状态 / 栈 任务状态解析 Running / Ready Blocked / Suspended 任务栈使用查看 已用 / 总栈 高水位线告警 感知脚本版本与 RTOS 版本不匹配 → TCB 偏移错位 → 任务列表异常或为空

六、把五个环节串成一次实际排查

假设目标板运行 FreeRTOS,调试时发现系统响应变慢。操作顺序可以是:先执行 TASK.List 看全局,发现 CommTask 长期处于 Blocked;再用 TASK.STATE CommTask 确认它阻塞在哪个队列或信号量;接着 TASK.TCB CommTask 读取 TCB,核对栈顶指针是否落在合法栈区;最后看栈使用列,若 388/1024 这类数值接近上限,就要怀疑栈溢出导致调度异常。整个过程从"总览"到"定位"再到"验证",正是 TRACE32 任务感知能力最典型的用法。

任务列表为空时,先检查感知脚本是否加载、RTOS 版本是否匹配,再检查目标是否已进入调度器运行状态(vTaskStartScheduler 之后)。