跳转至

实时调度、中断与时间基准

飞控的关键问题不仅是“结果算得对不对”,还包括“结果是否在截止时间前得到”。平均运行很快不能排除偶发阻塞、抖动或截止期限违约。

实时任务周期与截止期限

六个容易混淆的时间概念

概念 含义
周期 计划隔多久运行一次
频率 每秒计划运行次数,近似为周期的倒数
执行时间 一次任务真正占用处理器的时间
延迟 事件发生到结果产生之间的时间
抖动 周期、启动时间或延迟相对期望值的变化
截止期限 结果最迟必须完成的时刻

频率高不等于延迟低,也不等于每次执行都按时完成。

实际测量时再细分

测量量 起点与终点 是否包含被抢占时间
CPU执行时间 任务真正占用处理器的累计时间 不包含
墙钟执行时间 函数实际开始到返回 包含执行期间的抢占和等待
释放抖动 实际开始时刻减计划释放时刻 不适用
任务响应时间 计划释放时刻到任务完成 包含晚启动、抢占和等待
端到端延迟 物理采样时刻到执行器命令生效 包含整条链路

截止期限必须来自控制架构、任务需求或项目已有调度定义。不能因为某次测得620微秒,就事后把期限定义为更大的数字。

一个可复算的时间预算

假设控制链每1毫秒释放一次任务,相对截止期限为800微秒:

周期 = 1毫秒 = 1000微秒
最坏一次执行时间 = 620微秒
释放抖动与抢占预算 = 140微秒
截止期限前剩余裕量 = 800 - 620 - 140 = 40微秒
截止期限到下次周期 = 1000 - 800 = 200微秒

如果只测得平均执行时间300微秒,不能据此宣称满足期限。一次偶发的存储写入、总线重试或高优先级中断就可能使响应时间超过800微秒。

最坏执行时间需要测量

最坏情况执行时间(WCET,Worst-Case Execution Time)在复杂硬件上很难通过一次测试严格证明,但仍应记录:

  • 观察窗口;
  • 最小、平均、百分位和最大值;
  • 当时启用的功能;
  • 传感器和日志速率;
  • CPU负载;
  • 中断负载;
  • 是否出现截止期限违约;
  • 计时器精度和开销。

“运行十秒没有问题”不能覆盖罕见路径。应主动制造日志写入、总线错误、通信高负载和传感器切换等压力条件。

中断服务程序只做必要工作

中断服务程序(ISR,Interrupt Service Routine)通常用于快速响应硬件事件。常见原则:

  • 读取或确认中断状态;
  • 保存最少量数据;
  • 记录准确时间戳;
  • 通知任务继续处理;
  • 避免动态内存分配;
  • 避免阻塞锁;
  • 避免格式化打印;
  • 避免不可控循环和慢速总线事务。

中断执行过长会推迟其他中断和任务。即使单次很短,高频中断也可能占用大量处理器时间。

直接存储器访问不是自动安全

直接存储器访问(DMA,Direct Memory Access)可以在较少占用处理器的情况下搬运数据,但开发者仍要处理:

  • 缓冲区所有权;
  • 传输完成和错误中断;
  • 缓存一致性;
  • 对齐;
  • 长度和边界;
  • 通道冲突;
  • 传输期间禁止修改的内存;
  • 超时和恢复。

把栈上临时缓冲区交给尚未完成的DMA,或者在传输期间重复使用缓冲区,可能造成难以复现的数据损坏。

优先级与阻塞

高优先级任务等待低优先级任务持有的锁,可能形成优先级反转。常见缓解方式包括:

  • 缩短临界区;
  • 避免在锁内执行输入输出;
  • 使用优先级继承机制;
  • 对单生产者单消费者数据使用经过验证的无锁结构;
  • 复制一致快照,而不是长期持锁;
  • 把低优先级工作移出控制路径。

无锁不等于无风险。必须明确原子性、内存顺序、覆盖策略和读写双方的生命周期。

并发术语的最小模型

假设中断负责写入最新传感器样本,控制任务负责读取:

  • 生产者:产生并写入数据的一方,这里是中断;
  • 消费者:读取并使用数据的一方,这里是控制任务;
  • 原子操作:其他执行上下文不会看到“只完成一半”的单次读写;
  • 一致快照:消费者读到的多个字段来自同一版本,而不是新旧样本混合;
  • 内存顺序:处理器和编译器必须按约定让数据内容先于“数据已就绪”标志对消费者可见;
  • 无锁结构:不使用普通互斥锁,但仍必须证明读写顺序、覆盖和生命周期正确。

仅把变量声明为volatile通常只能限制部分编译器优化,不能自动提供跨中断或跨线程的原子性与一致性。应优先使用项目已有队列、消息总线、临界区或原子封装。

时间戳必须说明时钟域

记录时间时至少说明:

  • 单调时钟还是日历时间;
  • 单位;
  • 分辨率;
  • 位宽;
  • 溢出周期;
  • 采样时刻还是处理时刻;
  • 不同处理器或传感器之间如何同步。

飞控内部计算周期和样本年龄通常应使用不受系统时间校准影响的单调时钟。

处理计数器回绕

固定宽度无符号计数器最终会回绕。对短于回绕周期的时间间隔,常见做法是使用无符号减法:

uint32_t elapsed_us = now_us - start_us;

这依赖无符号整数的模运算语义。需要分别理解:

  • 计算经过时间时,真实间隔必须小于计数器的完整模周期,且读取机制不能漏过一整圈;
  • 使用有符号差值判断两个时间点先后时,通常要求两者间隔小于半个模周期;
  • 超时检查的执行间隔必须足够短,能够在回绕约束内观察状态;
  • 不能把原始时间戳直接转换成有符号数后比较大小。

数据新鲜度比“有数据”更重要

控制模块读取到一个结构体,不代表数据仍有效。消息或样本应携带:

  • 采样时间;
  • 接收时间;
  • 序号;
  • 有效标志;
  • 错误计数;
  • 数据来源实例;
  • 更新率或超时定义。

当驱动停止更新时,缓存中可能仍保留最后一个数值。若只检查指针非空或数值范围,控制器可能继续使用过期数据。

实时性验证表

路径 目标周期 截止期限 最大执行时间 最大样本年龄 超限行为
惯性测量单元(IMU,Inertial Measurement Unit)采集 待填 待填 待测 待测 丢样、重试或故障
状态估计 待填 待填 待测 待测 降级或无效
角速度控制 待填 待填 待测 待测 限制或故障
执行器输出 待填 待填 待测 待测 保持、停止或故障
日志 待填 非关键或受限 待测 不适用 丢弃低优先级记录

超限行为必须由具体平台和风险分析确定,不能统一写成“继续运行”或“立即停机”。

检查理解

  1. 平均执行时间为什么不能证明满足实时要求?
  2. 中断服务程序为什么不应执行格式化打印?
  3. DMA传输期间为什么要明确缓冲区所有权?
  4. 控制器为什么需要检查样本时间戳而不只检查数值?
  5. 无符号时间差在什么前提下可以处理计数器回绕?

主要参考