本节摘要:实时系统的本质不是跑得快,而是每次都在截止时间前完成——确定性比峰值性能更值钱。本节从前后台系统的失效机理讲起,厘清硬实时、软实时、抖动、确定性四组概念,算一笔最坏响应时间的账,并给出判断一个系统"够不够实时"的可操作标准。这是全册的概念地基,后续所有关于调度、中断与优化的判断都从这里出发。
嵌入式圈有句行话,夸一个系统好,说它"稳得像块表"。可"像表"到底指什么?指的不是主频高、跑分好,而是每一件事都在该发生的时刻发生:按键按下到背光点亮之间的间隔,第一百次和第一次一样长;电机过流到封锁PWM之间的延迟,永远不越过一个上限。这个"每次都一样"的性质,学名确定性。它恰恰是裸机前后台系统最先丢掉的东西,也是实时操作系统存在的全部理由。本节先把这层概念账算清楚——它是整册书的坐标系,后面每解析一张故障工单,最终都要回到这里对账。
前后台系统(fore-background system)是每个嵌入式工程师的起点:一个死循环构成"后台",中断服务程序构成"前台",中断里改个标志位,主循环轮询标志位再干重活。任务少的时候它简单直接,但它的响应能力有一个天然的数学上限——主循环一圈的耗时。
假设主循环依次处理按键扫描、屏幕刷新、传感器滤波、通信协议四件事,最坏路径走完一圈要 8 毫秒。那么"按键事件发生"到"按键被处理"的最坏延迟是多少?不是中断延迟(那只有微秒级),而是 8 毫秒——事件恰好在你刚开始处理第一件事时到来,就得等整圈。更要命的是,这个 8 毫秒不是常数:屏幕这一帧刷得多、协议这回合到大数据包,循环就变长。于是同一件事的响应延迟在 1 到 8 毫秒之间游走,这就是抖动。
抖动本身还不是致命的,致命的是失控的耦合。看看这段典型代码的毛病:
volatile uint8_t key_flag = 0; void KEY_IRQHandler(void) /* 前台:中断里只置标志 */ { key_flag = 1; } int main(void) { for (;;) { if (key_flag) { key_flag = 0; do_key_work(); /* 干活,耗时不确定 */ } refresh_screen(); /* 无条件执行,耗时不确定 */ sensor_filter(); protocol_poll(); } }
三个问题叠在一起。其一,优先级倒置于无形:按键在业务上最紧急,但在循环里排在第一位也未必最快被处理,因为 refresh_screen 的耗时不受它控制。其二,加功能必改循环:每加一个功能都要动 main 函数,循环变长,所有旧功能的响应时间一起退化——退化发生在时间维度上,功能测试根本测不出来。其三,中断和主循环共享 key_flag 这类变量,一旦业务复杂到需要中断里传多字节数据,竞态条件就进场了。
解法的历史演化很有方向感:先给循环里的每件事加时间片,再给时间片加优先级,再给高优先级的事情做独立的栈——把这三步做完,你等于重新发明了一个抢占式 RTOS。与其手工演化,不如直接站到内核的肩膀上。
硬实时:错过截止时间就是系统失效,没有商量。电机换相窗口、安全气囊点火判定、工业安全回路的急停响应,晚一毫秒可能就是炸管子或伤人。硬实时不等于快——气囊判定可以在 20 毫秒内完成,这个数字并不惊人,惊人的是它"从不"超过 20 毫秒。
软实时:偶尔迟到不致命,但持续迟到会肉眼可见地劣化体验。音频播放的缓冲区填充、视频帧率、界面刷新都属此类——迟到的帧会被丢弃或跳变,用户感知到卡顿,但系统仍完成使命。
确定性:同样输入、同样负载下,行为可重复、延迟有界。注意它和"平均性能"是两个维度:一个系统平均响应 2 毫秒、最坏 50 毫秒,另一个平均 3 毫秒、最坏 4 毫秒——对实时系统而言后者碾压前者。招聘里常写的"高性能"关注均值,实时工程关注的是最坏情况与方差。
抖动:多次响应延迟的离散程度。测量时不要只记录平均,要记录最大值与分布。一个实用的自检方法:连续触发一万次事件,画延迟直方图,看长尾。长尾越远,系统越"不实时"。
| 维度 | 硬实时 | 软实时 | 非实时(尽力而为) |
|---|---|---|---|
| 错过截止的后果 | 系统失效,可能损毁设备 | 体验劣化,数据偶尔丢失 | 无明确截止概念 |
| 设计关注点 | 最坏情况上界、可分析性 | 平均性能与缓冲深度 | 吞吐量 |
| 典型例子 | 电机换相、急停回路 | 音频、界面、日志上传 | 批量数据转换 |
| 调度手段 | 静态优先级 + 可响应时间分析 | 动态优先级 + 大缓冲 | 分时轮转即可 |
工程上判断"够不够实时",靠的是把最坏情况写出来算,而不是跑一遍看感觉。对固定优先级抢占调度,单核上一个任务的最坏响应时间由三部分构成:自身执行时间、被更高优先级任务抢占的时间、被低优先级任务通过共享资源封锁的时间。用中文表述这笔账:最坏响应等于自身执行,加上所有高优先级任务在窗口期内的多次抢占,再加上一次最长封锁。高优先级任务的抢占次数取决于它们的触发周期,所以周期越密的系统,长任务越难存活。
这笔账给出两个直接的工程结论。第一,优先级不是身份等级,而是截止时间的映射:截止越紧的任务优先级越高,这样才能保证最坏响应算得出来。第二,长任务必须拆:一个 5 毫秒的低优先级计算如果原子地占着处理器,所有高优先级任务的响应上界都跟着它走;拆成状态机或分片计算,封锁就消掉了。第 2 章讲任务划分、第 4 章讲优先级反转时,会反复用到这笔账。

把前面所有分析收拢,实时内核提供的核心服务其实只有三件。第一,分治时间:把处理器时间切成滴答(tick),按优先级分配给任务,高优先级就绪即抢占,事件响应不再取决于主循环走到哪。第二,托管阻塞:任务可以"睡到某时刻""等到某事件",等待期间不占处理器,由内核的延迟链表统一管理,这比手工轮询标志位省电也省心。第三,受控的共享:队列、信号量等原语让数据在任务与中断之间搬运时不必手写竞态防护,规则由内核统一执行。
代价同样要认:上下文切换要消耗数百处理器周期,每个任务独占一份栈吃掉内存,共享资源必须显式加锁而锁用错了会引入新的时序病(第 4 章的优先级反转工单就是典型)。所以"上 RTOS"不是无脑升级:一个只有两三件小事、中断极少的控制器,前后台反而更合适;当功能数量、并发来源、截止时间要求三者中任何两个开始膨胀,内核的抽象就开始回本。
问:用了 RTOS,系统就实时了吗?
不。RTOS 只是把"可分析的最坏响应"交到你手上。优先级设计混乱、临界区过长、中断里干重活,照样可以把一个带内核的系统做得毫无实时性可言。内核是必要条件,不是充分条件。
问:主频翻倍,实时性就翻倍吗?
均值会好,最坏情况未必。抖动大多来自设计(长临界区、无界阻塞),不来自指令执行速度。先修结构,再谈主频。
问:实时性怎么验收?
给每个关键事件定一个可测的截止时间,用示波器或 GPIO 翻转法持续测量"事件到响应"的延迟,记录最大值与分布,而不是测一次平均。第 8 章会给出完整的测量方案。
下一节把镜头拉远:这套以小与稳取胜的内核,二十多年里是如何长成一个横跨数百种芯片、被云厂商收编的生态的——理解它的来路,才能理解它今天每个设计取舍的由来。