二、多任务调度基础:任务的定义与属性、调度算法的评价指标、实时系统与非实时系统的区别
好,咱们进入第二章。说实话,多任务调度是整个分拣机控制系统的「大脑」。你硬件再牛,电机再快,调度没做好,分拣照样乱成一锅粥。我见过太多项目,硬件调试得漂漂亮亮,一上多任务就崩——要么丢包,要么卡死。嗯,这章咱们就把基础打牢。
2.1 任务的定义与属性
先问个问题:什么是「任务」?
在工业控制里,任务不是写代码时的一个函数。它是一个可独立调度执行的逻辑单元。说白了,就是CPU要干的一件「正经事」。比如:读一个传感器、发一帧报文、算一个位置。
每个任务都有几个关键属性。我习惯把它们记成「三要素一约束」:
| 属性 | 说明 | 分拣机中的例子 |
|---|---|---|
| 任务ID | 唯一标识,用于调度器索引 | Task_ReadSensor、Task_MotorCtrl |
| 优先级 | 决定抢占顺序,数值越小优先级越高(常见做法) | 急停任务优先级0,日志任务优先级10 |
| 周期/触发条件 | 定时执行或事件触发 | 编码器读取每1ms一次,包裹到位是事件触发 |
| 截止时间 | 最晚完成时间,超时可能导致分拣失败 | 推杆动作必须在包裹到达前50ms内完成 |
这里有个坑,我提醒一下:优先级不是越高越好。我曾经在一个项目里把通信任务优先级设得特别高,结果低优先级的电机控制任务老被抢断,电机跑起来一抖一抖的。后来才明白,优先级要跟任务的「紧急程度」和「执行时间」一起看。
核心概念:任务 = 代码 + 数据 + 调度属性。缺一个都不叫完整的任务。
2.2 调度算法的评价指标
调度算法好不好,不能光凭感觉。我一般用四个指标来衡量。你想想看,选调度器跟选对象差不多——得看综合实力。
- CPU利用率:CPU忙的时间占总时间的比例。理想情况是100%,但实际留20%~30%余量。我习惯留30%,因为分拣机突发情况多。
- 吞吐量:单位时间内完成的任务数。在分拣机上,就是每小时能处理多少包裹。
- 周转时间:从任务就绪到完成的时间。注意,不是执行时间,是「排队+执行」的总时间。
- 截止时间满足率:这是实时系统最关键的指标。说白了,就是「该按时完成的任务,到底按时完成了多少」。
举个例子。我调试过一条分拣线,吞吐量明明达标,但总有包裹被推错位置。查了半天,发现是某个传感器读取任务的截止时间满足率只有85%。也就是说,15%的情况下它读到的数据是「过期」的。嗯,这就是指标没选对的问题——只看吞吐量,没看截止时间。
我的经验:工业现场,优先关注「截止时间满足率」和「CPU利用率」。吞吐量是结果,不是原因。
2.3 实时系统与非实时系统的区别
这个区别,我直接用一句话说清楚:实时系统不是「快」,而是「可预测」。
非实时系统(比如Windows、Linux普通进程)追求的是「平均性能好」。你打开一个软件,有时候快有时候慢,没关系,用户能忍。但分拣机不行——你想想看,如果推杆动作有时候50ms完成,有时候200ms完成,包裹早就飞出去了。
实时系统追求的是「最坏情况下的性能」。它保证:在最恶劣的条件下,任务也能在截止时间前完成。
| 对比项 | 非实时系统 | 实时系统 |
|---|---|---|
| 调度目标 | 平均响应时间短 | 最坏情况响应时间可控 |
| 调度策略 | 时间片轮转、公平调度 | 优先级抢占、EDF(最早截止时间优先) |
| 典型OS | Windows、Linux(非实时内核) | FreeRTOS、VxWorks、RT-Linux |
| 分拣机适用场景 | 日志记录、报表生成 | 电机控制、传感器读取、通信报文处理 |
这里有个常见的误解:有人觉得「我用Linux加高优先级,不就是实时了吗?」不是的。Linux普通内核是非抢占式的——你优先级再高,也得等当前任务主动让出CPU。我曾经踩过这个坑,用普通Linux做运动控制,结果一个打印任务把电机控制卡住了200ms,分拣直接乱套。
避坑指南:分拣机的核心控制任务(读编码器、发脉冲、收报文)必须跑在实时操作系统上。非实时任务(数据上传、界面刷新)可以跑在普通系统上。这叫「混合关键性系统」——我后面会细讲。
2.4 分拣机中的任务模型
好,理论讲完了,咱们看看分拣机里实际有哪些任务。我一般把它们分成三类:
- 周期任务:固定时间间隔执行。比如编码器读取(1ms周期)、位置计算(2ms周期)、通信心跳(10ms周期)。
- 偶发任务:事件触发,但两次触发之间有时间间隔。比如包裹到位信号——包裹不是连续来的,但来了就必须处理。
- 非周期任务:完全随机触发。比如急停按钮、故障报警。这类任务优先级最高,但执行时间极短。
我个人习惯在设计初期就把所有任务列成一张表,标清楚周期、截止时间、最坏执行时间。这样调度分析才有依据。别偷懒,我见过有人凭感觉写调度,最后现场一跑就崩。
// 分拣机任务定义示例(伪代码)
TaskDef taskTable[] = {
{ .id = TASK_ENCODER, .period = 1, .deadline = 1, .wcet = 0.2, .prio = 5 },
{ .id = TASK_POSITION, .period = 2, .deadline = 2, .wcet = 0.5, .prio = 4 },
{ .id = TASK_MOTOR, .period = 2, .deadline = 2, .wcet = 0.8, .prio = 3 },
{ .id = TASK_COMM, .period = 10, .deadline = 10, .wcet = 1.5, .prio = 2 },
{ .id = TASK_ALARM, .period = 0, .deadline = 0.5, .wcet = 0.1, .prio = 0 }, // 事件触发
};
注意看,急停任务(TASK_ALARM)的周期是0,表示事件触发。截止时间0.5ms,优先级最高。这就是实时系统的典型设计——关键任务必须「插队」处理。
总结一下这章:任务有属性,调度有指标,实时非实时有本质区别。下一章咱们就讲具体的调度算法——RMS、EDF这些,到时候拿分拣机的实际数据来算。
嗯,这章就到这儿。内容不多,但都是基础。你把这些搞透了,后面调度算法学起来就顺了。