第三课:System Viewer 实战:任务调度分析

好,咱们直接进入正题。System Viewer 这个工具,说白了就是 VxWorks 的「黑匣子记录仪」。它能把你系统里每个任务的运行轨迹、切换瞬间、甚至是谁抢了谁的 CPU,全都一帧一帧地录下来。今天我就带你亲手用这个工具,把任务调度、优先级反转、死锁这几个老大难问题,一个一个揪出来。

3.1 任务切换序列:看穿 CPU 的「时间切片」

先说说最基本的——怎么看任务切换。很多新手调优时,总觉得系统卡顿,但说不清卡在哪。System Viewer 的 Timeline 视图,就是你的第一把手术刀。

我个人习惯,在系统启动后先跑一段典型负载,然后抓取一段 5 秒左右的日志。打开 System Viewer,你会看到类似这样的画面:

Timeline 视图示例:
Task_A (pri=100)  ████████░░░░░░░░░░░░░░░░░░░░
Task_B (pri=150)  ░░░░░░░░████████████░░░░░░░░
Task_C (pri=200)  ░░░░░░░░░░░░░░░░░░██████████

每一行是一个任务,横轴是时间。颜色块代表任务正在运行,空白处代表任务被挂起或阻塞。你想想看,如果某个高优先级任务频繁出现「断断续续」的色块,那多半是它在等某个资源。

小技巧: 我建议你重点关注「上下文切换事件」那一栏。双击任意一个切换点,System Viewer 会告诉你:谁切走了谁,切换原因是什么(时间片耗尽?信号量等待?中断触发?)。这比你自己猜要准得多。

3.2 识别优先级反转:那个「低优先级抢跑」的幽灵

优先级反转,这词听着玄乎,其实说白了就是:一个低优先级任务占着茅坑不拉屎,导致高优先级任务干瞪眼。我在项目中遇到过最典型的一次,是一个串口驱动任务(优先级 50)和一个计算任务(优先级 200)共享一个互斥信号量。结果串口任务被中断频繁打断,计算任务反而被活活饿死。

用 System Viewer 怎么抓?看两个关键指标:

  • 任务等待时间(Wait Time):高优先级任务在信号量上等待的累计时间。如果这个值异常大,比如超过 100ms,那基本可以断定有问题。
  • 优先级继承事件(Priority Inheritance):System Viewer 会在事件列表中标注「Priority Inheritance Occurred」。看到这个,就说明系统已经触发了优先级继承机制——但别高兴太早,这恰恰说明反转已经发生了。

举个例子,我抓过一段日志,里面 Task_High(pri=50)在 2.3 秒处开始等待一个二进制信号量,但直到 2.8 秒才拿到。中间这 500ms,Task_Low(pri=200)一直在运行。你猜怎么着?Task_Low 拿着信号量不放,中间还被一个中断任务插了一脚。这就是典型的反转。

避坑指南: 我曾经以为只要用了优先级继承协议就万事大吉。但 System Viewer 告诉我,继承协议本身也有开销——它会临时提升低优先级任务的优先级,导致其他中等优先级任务被意外阻塞。所以,别迷信协议,还是要看实际数据。

3.3 检测死锁:当任务们「互相掐脖子」

死锁,四个字:互相等待。任务 A 拿着锁 1 等锁 2,任务 B 拿着锁 2 等锁 1。两个人都僵在那,谁也动不了。System Viewer 检测死锁,靠的是「资源依赖图」和「任务状态分析」。

具体操作:在 System Viewer 里打开「Resource Dependency」视图。你会看到类似这样的图:

资源依赖图(简化版):
Task_A ──(持有)──> Sem_1
Task_A ──(等待)──> Sem_2
Task_B ──(持有)──> Sem_2
Task_B ──(等待)──> Sem_1

如果这个图里出现了一个环,那就是死锁。System Viewer 会自动用红色高亮这个环,并标注「Deadlock Detected」。嗯,这里要注意:死锁不一定每次都能复现,它往往在特定时序下才触发。所以我的习惯是,让系统跑压力测试,同时用 System Viewer 连续抓取多段日志。

警告: 死锁检测不是万能的。System Viewer 只能检测到「已经发生」的死锁,也就是任务已经永久阻塞的情况。如果死锁只是偶尔出现,比如每 10 分钟卡一次,那你得靠长时间抓取日志,或者结合 Wind River 的 DKM(动态内核模块)来注入检测代码。

3.4 实战案例:一个真实的调度问题排查

我记得有一次,客户反馈说他们的无人机飞控系统偶尔会「丢帧」,导致姿态控制不稳。我远程连上设备,用 System Viewer 抓了 30 秒的日志。打开一看,发现一个叫「GPS_Update」的任务(优先级 120)每隔 1 秒就会卡顿 200ms。

点开 Timeline,发现每次卡顿前,都有一个「Console_Log」任务(优先级 180)在运行。Console_Log 任务在写日志到 Flash 时,占用了 I2C 总线。而 GPS_Update 也需要 I2C 总线来读取数据。结果就是:高优先级的 GPS 任务,被低优先级的日志任务堵住了。

解决方案很简单:把 Console_Log 的优先级降到 200,同时给 I2C 总线加上优先级继承协议。改完之后,再抓一次日志,GPS_Update 的卡顿消失了。你看,System Viewer 就像一面照妖镜,什么妖魔鬼怪都藏不住。

问题类型 System Viewer 关键视图 典型指标 我的经验阈值
任务切换异常 Timeline 上下文切换频率 > 1000次/秒 超过 2000 次/秒就要警惕
优先级反转 事件列表 + Wait Time 高优先级任务等待时间 > 50ms 超过 100ms 基本确认反转
死锁 Resource Dependency 资源依赖图出现环 出现环就是死锁,零容忍

最后说一句:System Viewer 是个好工具,但它不会替你思考。你得学会从数据里读出故事。比如,看到某个任务频繁切换,别急着调优先级,先问问自己:它为什么被切换?是等信号量?还是等消息队列?还是单纯的时间片轮转?搞清楚「为什么」,比「怎么调」更重要。

我的习惯: 每次调优前,先抓一段「正常状态」的日志作为基线。然后改一个参数,再抓一段对比。没有基线,你根本不知道改完是变好了还是变坏了。这招我用了十年,从没失手过。