第4章:优先级调度算法
调度算法,说白了就是系统决定「下一个该谁跑」的规则。VxWorks 在这方面做得相当灵活,三种调度策略各有各的脾气。我最早接触 VxWorks 时,以为调度就是比优先级,后来踩了不少坑才明白——同优先级任务的调度,才是真正考验设计功底的地方。
4.1 基于优先级的抢占式调度
这是 VxWorks 默认的调度方式。每个任务有个优先级,数值越小优先级越高(0 最高,255 最低)。高优先级任务一旦就绪,立马抢走 CPU。
举个例子:
/* 任务A:优先级 50 */
void taskA(void) {
while (1) {
printf("A running\n");
taskDelay(100); /* 主动让出CPU */
}
}
/* 任务B:优先级 100 */
void taskB(void) {
while (1) {
printf("B running\n");
taskDelay(100);
}
}
任务A 优先级更高,所以只要 A 就绪,B 就靠边站。我在项目中遇到过一个问题:一个低优先级的通信任务,因为高优先级任务一直占着 CPU,导致丢包。后来加了合理的延时和信号量,才解决。
核心要点:抢占式调度下,高优先级任务可以随时打断低优先级任务。这是实时性的保障,但也容易引发优先级反转问题。
4.2 时间片轮转调度
同优先级任务之间,默认是不轮转的。一个任务不主动让出 CPU,同优先级的其他任务就永远没机会跑。这其实是个设计陷阱。
我习惯的做法是:在任务创建时显式开启时间片轮转。
/* 开启时间片轮转,时间片长度 5 个 tick */
kernelTimeSlice(5);
/* 创建两个同优先级任务 */
taskSpawn("tTask1", 100, 0, 4096, (FUNCPTR)task1, 0,0,0,0,0,0,0,0,0,0);
taskSpawn("tTask2", 100, 0, 4096, (FUNCPTR)task2, 0,0,0,0,0,0,0,0,0,0);
时间片轮转的好处是公平。每个任务都能分到 CPU 时间,不会饿死。但要注意:时间片太短会导致频繁切换,浪费 CPU;太长又失去了轮转的意义。我个人建议 5-10 个 tick 比较合适。
实战技巧:如果任务之间有明确的优先级关系,就别用时间片轮转。轮转会引入不确定性,影响实时性。
4.3 同优先级任务调度策略
这里有个容易忽略的点:VxWorks 对同优先级任务默认采用 FIFO 调度。什么意思?就是谁先就绪谁先跑,跑完才轮到下一个。
我曾经在一个数据采集项目中,三个同优先级的采集任务共享一个缓冲区。因为 FIFO 调度,第一个任务一直占着 CPU,后面两个任务迟迟得不到执行,缓冲区数据都过期了。后来改成时间片轮转,问题才解决。
同优先级任务的调度策略,总结起来就三种:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| FIFO(默认) | 先就绪先执行,直到任务主动让出 | 任务执行时间短,且互不依赖 |
| 时间片轮转 | 每个任务分到固定时间片,轮流执行 | 任务执行时间长,需要公平调度 |
| 优先级继承 | 临时提升低优先级任务的优先级 | 解决优先级反转问题 |
避坑指南:我曾经在项目中同时使用 FIFO 和信号量,结果出现了死锁。原因是两个同优先级任务互相等待对方释放资源,而 FIFO 调度下谁也不让谁。解决方案是改用时间片轮转,或者引入优先级继承协议。
4.4 如何选择合适的调度策略
这个问题没有标准答案,但我可以分享一些经验:
- 硬实时任务:用抢占式调度,优先级要明确区分。比如中断处理任务优先级设为 50,控制任务设为 80,日志任务设为 150。
- 软实时任务:同优先级任务用时间片轮转。比如多个传感器数据采集任务,优先级相同,轮流采集。
- 后台任务:优先级设低,用 FIFO 就行。比如系统监控、日志写入等。
你想想看,如果所有任务都用抢占式调度,高优先级任务一多,低优先级任务可能永远得不到 CPU。反过来,如果都用时间片轮转,实时性又没法保证。所以关键是「分而治之」——不同类别的任务用不同的策略。
我的建议:设计初期就明确任务的优先级和调度策略。别等到系统跑起来才发现调度有问题,那时候改起来成本很高。
4.5 实战中的调度陷阱
嗯,这里要重点说说我踩过的坑。
陷阱一:优先级反转
高优先级任务等待低优先级任务释放资源,而低优先级任务又被中等优先级任务抢占。结果高优先级任务反而跑不过中等优先级任务。解决方法是使用优先级继承协议,或者干脆避免共享资源。
陷阱二:时间片设置不合理
时间片太短,系统频繁切换,CPU 都花在上下文切换上了。时间片太长,轮转失去意义。我一般先设 5 个 tick,然后通过性能监控工具观察,再微调。
陷阱三:忘记开启时间片轮转
这个错误我犯过不止一次。创建了同优先级任务,以为会自动轮转,结果一个任务跑死,其他任务干瞪眼。记住:VxWorks 默认是 FIFO,要轮转必须显式调用 kernelTimeSlice()。
调试技巧:用 i 命令查看任务状态。如果某个任务一直处于 READY 状态但没得到 CPU,多半是优先级或调度策略的问题。
调度算法是 VxWorks 实时性的核心。选对了,系统稳如泰山;选错了,各种诡异问题层出不穷。我个人建议:多花点时间在设计阶段,把任务的优先级和调度策略想清楚。别等到系统跑起来再改,那时候代价就大了。