2. 任务调度优化:优先级调度策略
好,咱们进入第二个核心话题——任务调度。说实话,调度策略选对了,电梯控制系统就成功了一半。我见过太多项目,硬件选型没问题,代码写得也漂亮,结果一跑起来就卡顿,最后发现是调度策略没选对。
电梯控制系统里,任务五花八门。有门控任务、速度曲线计算、楼层检测、通讯处理、故障监控……每个任务对时间的要求都不一样。怎么让它们井井有条地跑?这就是调度策略要解决的问题。
2.1 固定优先级调度
这是最直观的策略。每个任务分配一个固定的优先级,高优先级的任务先跑。说白了,就是「官大一级压死人」。
我在一个老项目里用过这种策略。当时电梯主控芯片是STM32F4,跑FreeRTOS。我把故障监控任务的优先级设得最高,因为安全第一。门控任务次之,通讯任务最低。跑起来很稳定,没出过问题。
但固定优先级有个坑——低优先级任务可能永远得不到CPU。你想想看,如果高优先级任务一直就绪,低优先级任务就饿死了。电梯里如果通讯任务一直抢不到CPU,那楼层显示就卡住了,乘客会投诉的。
2.2 EDF调度(最早截止时间优先)
EDF是个好东西。它不看优先级,看截止时间。哪个任务快到期了,就先跑哪个。动态的,很灵活。
我记得有一次做高速电梯项目,速度曲线计算任务必须在5ms内完成,否则电梯会抖动。用固定优先级的话,万一有个高优先级任务占用了CPU,速度曲线就超时了。换成EDF后,系统自动把快到期的任务优先处理,问题就解决了。
EDF的优点是CPU利用率高,理论上可以达到100%。但缺点也很明显——实现复杂,而且一旦过载,所有任务都可能错过截止时间。嗯,这就像高速公路堵车,所有车都走不动。
| 调度策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定优先级 | 实现简单,可预测性强 | 低优先级任务可能饿死 | 任务少、优先级明确 |
| EDF | CPU利用率高,动态适应 | 过载时所有任务都可能失败 | 实时性要求高、任务动态变化 |
| RMS | 理论成熟,可分析性强 | 对周期任务依赖大 | 周期任务为主的系统 |
2.3 RMS调度(速率单调调度)
RMS是固定优先级的一种特例。它的规则很简单:周期越短的任务,优先级越高。为什么?因为周期短的任务更紧急,你想想看,1ms跑一次的任务和100ms跑一次的任务,哪个更怕被耽误?
RMS有个著名的可调度性判定公式:
U = Σ(Ci/Ti) ≤ n(2^(1/n) - 1)
其中:
U = CPU利用率
Ci = 任务i的最坏执行时间
Ti = 任务i的周期
n = 任务数量
当n很大时,这个上限趋近于ln2 ≈ 69.3%。也就是说,用RMS调度,CPU利用率超过69.3%就可能出问题。我刚开始做电梯项目时,总想把CPU跑满,结果经常出问题。后来老老实实留了30%的余量,世界就清净了。
2.4 任务优先级分配原则
优先级怎么分?我总结了几条原则,都是血泪教训换来的:
- 安全第一:故障检测、急停处理这些任务,优先级必须最高。人命关天的事,不能含糊。
- 实时性要求高的优先:速度曲线计算、门控信号这些,错过时间就会出问题。
- 周期短的优先:参考RMS的思路,周期短的任务更紧急。
- 关键路径上的任务优先:比如电梯的启动、停止、换速这些关键动作,优先级要高。
- 非实时任务放最低:日志记录、数据显示这些,优先级最低,有空再跑。
我曾经在一个项目里,把通讯任务的优先级设得比门控任务还高。结果电梯到了楼层,门半天打不开,因为通讯任务抢了CPU。乘客投诉说电梯反应慢,后来我赶紧调了优先级,问题就解决了。
2.5 避免优先级反转
优先级反转是个经典问题。简单说就是:高优先级任务等着低优先级任务释放资源,但低优先级任务被中等优先级任务抢占了CPU,结果高优先级任务反而被中等优先级任务「卡住」了。
我遇到过真实案例。电梯的故障检测任务(高优先级)和门控任务(低优先级)共享一个I2C总线。故障检测任务要读传感器数据,但门控任务正占着总线。这时候来了个通讯任务(中等优先级),把门控任务抢占了。结果故障检测任务干等着,电梯出了故障都检测不到。
解决方案有三种:
- 优先级继承:低优先级任务在持有资源时,临时提升到高优先级任务的优先级。这样中等优先级任务就抢不了它了。
- 优先级天花板:访问共享资源的任务,优先级直接提到资源对应的最高优先级。
- 关中断:访问共享资源时关中断,简单粗暴,但会影响实时性。
我个人推荐优先级继承。FreeRTOS和uC/OS都支持这个机制。我那个I2C总线的案例,开了优先级继承后,问题就解决了。
好了,任务调度这块就聊到这儿。记住一句话:调度策略没有银弹,要根据实际场景选。电梯控制系统里,我一般用固定优先级为主,配合优先级继承机制。如果实时性要求特别高,可以考虑EDF。RMS适合做理论分析,实际项目中用得不多。