二、多任务调度基础:任务的定义与属性、调度算法的评价指标、实时系统与非实时系统的区别

好,咱们进入第二章。说实话,多任务调度是整个分拣机控制系统的「大脑」。你硬件再牛,电机再快,调度没做好,分拣照样乱成一锅粥。我见过太多项目,硬件调试得漂漂亮亮,一上多任务就崩——要么丢包,要么卡死。嗯,这章咱们就把基础打牢。

2.1 任务的定义与属性

先问个问题:什么是「任务」?

在工业控制里,任务不是写代码时的一个函数。它是一个可独立调度执行的逻辑单元。说白了,就是CPU要干的一件「正经事」。比如:读一个传感器、发一帧报文、算一个位置。

每个任务都有几个关键属性。我习惯把它们记成「三要素一约束」:

属性 说明 分拣机中的例子
任务ID 唯一标识,用于调度器索引 Task_ReadSensor、Task_MotorCtrl
优先级 决定抢占顺序,数值越小优先级越高(常见做法) 急停任务优先级0,日志任务优先级10
周期/触发条件 定时执行或事件触发 编码器读取每1ms一次,包裹到位是事件触发
截止时间 最晚完成时间,超时可能导致分拣失败 推杆动作必须在包裹到达前50ms内完成

这里有个坑,我提醒一下:优先级不是越高越好。我曾经在一个项目里把通信任务优先级设得特别高,结果低优先级的电机控制任务老被抢断,电机跑起来一抖一抖的。后来才明白,优先级要跟任务的「紧急程度」和「执行时间」一起看。

核心概念:任务 = 代码 + 数据 + 调度属性。缺一个都不叫完整的任务。

2.2 调度算法的评价指标

调度算法好不好,不能光凭感觉。我一般用四个指标来衡量。你想想看,选调度器跟选对象差不多——得看综合实力。

  1. CPU利用率:CPU忙的时间占总时间的比例。理想情况是100%,但实际留20%~30%余量。我习惯留30%,因为分拣机突发情况多。
  2. 吞吐量:单位时间内完成的任务数。在分拣机上,就是每小时能处理多少包裹。
  3. 周转时间:从任务就绪到完成的时间。注意,不是执行时间,是「排队+执行」的总时间。
  4. 截止时间满足率:这是实时系统最关键的指标。说白了,就是「该按时完成的任务,到底按时完成了多少」。

举个例子。我调试过一条分拣线,吞吐量明明达标,但总有包裹被推错位置。查了半天,发现是某个传感器读取任务的截止时间满足率只有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这些,到时候拿分拣机的实际数据来算。

嗯,这章就到这儿。内容不多,但都是基础。你把这些搞透了,后面调度算法学起来就顺了。