实时操作系统与可靠性 — 可预测比更快重要

一句话:实时系统的目标不是「跑得快」,而是「在最坏情况下也不迟到」;所以设计时算的是最坏执行时间与截止期,验收时看的是抖动,而不是平均吞吐。

🎯 这 20% 能解决哪些问题

你遇到的困惑本质原因马上能改的动作
while(1) 里一个模块卡住,全部失灵超级循环把耦合塞进同一执行流拆任务 + 队列通信,或用状态机把每步做到微秒级
加了 RTOS 后某个功能偶发不响应优先级设计不当,高优先级任务把 CPU 占满按截止期而非重要性排优先级,给每个任务算占用率
任务栈莫名溢出、随机 HardFault栈太小或中断嵌套叠加用高水位实测 + 余量 50%,深调用链与浮点单独加预算
数据偶尔被写坏、两个任务同时改共享全局变量无保护消灭共享:改走队列/消息;必须共享就用互斥量
高优先级任务被低优先级拖住优先级反转用带优先级继承的互斥量,而不是二值信号量
系统偶尔死锁,只能重启加锁顺序不固定,形成循环等待规定全局加锁顺序,或改用一次只持一把锁的设计
中断里调用阻塞 API 后系统卡住ISR 上下文不允许等待只用 ...FromISR API;把等待放回任务
偶发重启后查不到原因复位源与故障信息没落盘掉电非易失区记录复位源、错误码与运行时间戳

一、什么叫实时:截止期、抖动、可预测

类别违约后果例子设计目标
硬实时灾难性失效电机换相、气囊点火、刹车最坏情况必须满足截止期,需 WCET 分析
软实时性能可接受地下降音频播放、屏刷新保证 99.9% 及时,允许偶发迟到
非实时只要求最终完成日志上传、参数配置吞吐与功耗优先
指标含义怎么测
响应延迟事件发生到任务开始执行GPIO 打点 + 逻辑分析仪
抖动周期的最大偏差连续采样一个周期的起止时间
最坏执行时间最长路径耗时关中断区 + 边界输入实测,不靠平均
CPU 负载各任务占用之和空闲任务计数法;控制类留 30% 余量

结论:「平均 2ms 处理完」在实时系统里不是答案,「最坏 8ms 且截止期 10ms」才是。


二、任务与调度:把功能切成可预测的执行单元

设计问题判据反面做法
要不要拆成任务有独立周期或独立截止期才拆每个 .c 文件建一个任务,最后全在等锁
周期怎么定由最快需要响应的输入决定拍脑袋 1ms 全表扫描,把 CPU 吃光
优先级怎么定截止期紧 + 频率高者优先按「功能重要性」排,导致控制回路被日志压住
栈给多大局部变量 + 最深调用 + 中断帧 + 50%全用默认 128 字节,跑飞后才查
任务粒度单任务只做一件事,超时能被看门狗察觉一个任务串完全部业务,卡死也照样喂狗
graph TB
    A["输入事件"] --> B{"有独立截止期?"}
    B -->|"否"| C["并入现有任务,做状态机一步"]
    B -->|"是"| D["独立任务 + 队列通信"]
    D --> E["按截止期设优先级"]
    E --> F["最坏执行时间实测 + 负载核算"]
    F --> G{"负载 < 70%?"}
    G -->|"否"| C
    G -->|"是"| H["交付并加抖动监控"]

抢占式 + 时间片:同优先级任务轮转(FreeRTOS 需 configUSE_TIME_SLICING);跨优先级永远抢占。需要「都按顺序执行」时,把相关逻辑放进同一任务,比调优先级更稳。


三、同步与通信原语:选错比写错更常见

原语语义适合陷阱
二值信号量通知「发生了」ISR → 任务的事件通知当锁用会优先级反转,可被多次 give 计满
计数信号量资源计数有限资源池、缓冲块忘记 take 配平导致永久阻塞
互斥量占有式锁 + 优先级继承保护共享数据必须在同一任务里 release;不能在中断用
队列拷贝传值,可阻塞任务间数据流(推荐默认)传指针等于又引入共享,需所有权约定
事件组多条件与/或等待等多个就绪标志组合条件复杂时说明任务拆分错了
任务通知单目标轻量唤醒替代一对信号量只能一个接收者

优先策略:默认用队列传所有权(谁拿到数据谁负责释放),把「共享 + 锁」降级为「消息 + 拷贝」。小内存系统里用静态队列/静态任务创建,避免堆碎片导致运行时失败。


四、优先级反转、死锁与饿死

问题成因解法
优先级反转中优先级任务抢占持锁的低优先级任务,高优先级只能等优先级继承互斥量;缩短持锁时间;禁止持锁做 IO
死锁四条件互斥 + 持有并等待 + 不可剥夺 + 循环等待破坏循环等待:全局固定加锁顺序;或带超时获取
优先级饿死高优先级任务过载核算 CPU 占用;把高优先级任务做得更短更稀疏
中断风暴压垮任务ISR 太长/太密ISR 改批处理(DMA + 完成中断),限频与背压
数据撕裂多字节读写被中断临界区或双缓冲 + 序号校验

持锁时间上限要写进代码注释与 review 清单——嵌入式里「拿着锁去发 UART」是最常见的整系统卡顿原因。


五、资源约束下的工程习惯

约束做法收益
RAM 小静态分配优先,对象池替代 malloc消除碎片与运行时分配失败
Flash 紧张关未用的库特性、-Os、日志分级编译期裁剪版本可控,现场留诊断空间
无 MMU 保护关键缓冲区加长度校验与 magic 头越界能被早期发现
时间基准Tick 溢出处理(uint32_t 约 49.7 天 @1ms)长期运行不死机
参数保存双副本 + CRC + 写入计数掉电写坏可回退上一份
升级A/B 分区 + 启动失败回滚 + 镜像签名变砖可自恢复

六、可靠性:把「重启」变成设计的一部分

机制正确用法错误用法
独立看门狗各关键任务各自置标志,由低优先级任务集中喂在定时器中断里无条件喂狗
窗口看门狗捕获「跑得太快/顺序错」的异常只当普通看门狗用
心跳与状态上报周期上报运行状态、复位源、错误计数只上报成功,问题不可见
故障降级传感器失效 → 用安全默认值并标记不可信直接死循环或复位隐藏根因
复位源记录掉电非易失区累计各类复位次数复位后什么都不留,现场无从判断

判据:看门狗只能救「卡住」,救不了「算错」。控制类产品要同时有输出侧安全机制(限幅、超时停驱动、独立硬件保护)。


七、选型:裸机、RTOS 还是 Linux

形态适合不适合典型栈
裸机超级循环单一职责、微秒级确定性、成本极敏感多外设多协议并发无 / 自写状态机
裸机 + 协作式调度中等复杂度,需要轻量定时任务有硬截止期的并发定时器驱动的状态机
RTOS多任务并发、ms 级截止期、资源 KB 级需要文件系统/网络栈/驱动的复杂交互FreeRTOS、RT-Thread、Zephyr
嵌入式 Linux需要富交互、网络协议栈、生态与算力硬实时(需 PREEMPT_RT 或协处理器)Buildroot/Yocto + POSIX 线程

混合形态最常见:Linux 做业务与联网,MCU 做实时控制,两者用串口/SPI + 明确协议连接;实时性归 MCU,可观测性归 Linux。


🎓 实战场景(TIL)

  • 场景:设备运行 40 余天后调度逐渐紊乱。本质:tick 计数 32 位溢出,(now - last) < period 判断反转。要点:所有时间比较统一用差值形式并测过翻转点,长期老化测试要覆盖溢出边界。
  • 场景:加了日志任务后电机控制抖动变大。本质:日志任务与控制任务同优先级轮转,日志单条耗时上毫秒。要点:日志降到低优先级 + 缓冲批量输出,控制任务保持微秒级。
  • 场景:OTA 后现场大量设备启动失败。本质:新固件超出分区大小并被截断,且无回滚机制。要点:镜像带长度与 CRC 校验,启动失败三次自动回滚旧分区。

📋 一页速查

我要做记住这条
定优先级按截止期,不按业务重要性
任务间传数据队列传所有权,别共享全局变量
保护共享资源互斥量(带继承)而不是二值信号量;持锁不做 IO
ISR 里能做什么只做搬数据/置标志,调用 ...FromISR API
估栈高水位实测 + 50% 余量;浮点与深递归另加
算负载各任务最坏执行 × 频率 < 70%,留余量给突发
看门狗各关键路径置标志,集中喂;绝不在中断里无脑喂
时间比较用减法形式,处理 tick 回绕(1ms 下约 49.7 天)
现场排障记录复位源 + 错误码 + 运行时长,量产也留串口
选型KB 级并发用 RTOS;要文件系统和网络生态用 Linux;硬实时留在 MCU

🔗 相关链接