2、基础知识:实时操作系统(RTOS)与调度器基础,任务、周期、优先级、截止时间等核心术语

TinyML 设备上的多模型调度,本质上是在资源极度受限的微控制器(MCU)上,对多个推理任务进行时间与计算资源的分配。要理解这一过程,必须首先掌握实时操作系统(RTOS)与调度器的核心概念。与桌面操作系统追求“吞吐量”不同,RTOS 追求的是确定性——即每个任务能否在规定的截止时间(Deadline)之前完成。本章将系统梳理任务、周期、优先级、截止时间等核心术语,并解释它们如何在调度器中相互作用,为后续章节的模型调度策略打下坚实的理论基础。

RTOS 调度核心概念关系图 调度器 (Scheduler) 决定哪个任务运行 任务 A (周期型) 周期 T=10ms 执行时间 C=2ms 任务 B (事件型) 触发: 传感器中断 截止时间 D=5ms 任务 C (模型推理) 优先级: 高 最坏执行时间 WCET 调度策略 • 固定优先级抢占 • 循环时间片 (Round-Robin) • 单调速率 (RMS) • 最早截止时间优先 (EDF) 关键指标 • CPU 利用率 U = Σ(C/T) • 调度延迟 • 上下文切换开销 TinyML 场景 多模型 = 多任务调度

上图展示了 RTOS 调度的核心要素:调度器作为中枢,管理着多个具有不同周期、执行时间和优先级的任务。在 TinyML 场景中,每个神经网络模型(如唤醒词检测、人体活动识别、异常检测)都可以视为一个独立的任务。调度器根据预设的优先级和时序规则,决定在任一时刻哪个模型占用 CPU 进行推理。下面我们将逐一拆解这些核心术语。

2.1 任务(Task)与任务状态机

核心原理:任务是 RTOS 中可独立调度的最小执行单元。在 TinyML 中,一个任务可以是一个完整的推理流程(读取传感器数据 → 预处理 → 模型推理 → 后处理 → 输出结果),也可以是其中的某个阶段。任务在生命周期中会经历多个状态:就绪(Ready)、运行(Running)、阻塞(Blocked)和挂起(Suspended)。调度器只从就绪队列中选择任务进入运行状态。

判断方法:在裸机编程中,我们通常使用一个超级循环(Super Loop)顺序执行所有功能。但在多模型场景下,这种结构会导致严重的时序耦合——如果模型 A 推理耗时 50ms,模型 B 的响应延迟就会高达 50ms。引入 RTOS 任务后,每个模型被封装为独立任务,通过调度器实现时间上的“分时复用”。

任务定义示例(基于 FreeRTOS):
// 定义人体活动识别任务
void vHAR_Task(void *pvParameters) {
    float sensor_data[64];
    float result;
    for(;;) {
        // 1. 读取 IMU 传感器数据
        read_imu_data(sensor_data);
        // 2. 执行模型推理(假设为 8ms)
        result = run_har_model(sensor_data);
        // 3. 发送结果到通信队列
        xQueueSend(xResultQueue, &result, 0);
        // 4. 等待下一次周期释放(周期为 20ms)
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

案例拆解:假设一个智能门锁设备需要同时运行三个模型:人脸检测(周期 100ms)、活体检测(周期 200ms)、异常敲击检测(事件触发)。在裸机中,若人脸检测耗时 80ms,活体检测将被迫等待。通过 RTOS 将三者设为独立任务,活体检测任务可以在人脸检测任务阻塞等待传感器数据时抢占执行,从而保证所有模型的实时性。

常见误区:初学者常将“任务”与“函数”混淆。任务必须是一个无限循环(for(;;)),且不能直接使用阻塞式延时(如 HAL_Delay())占用 CPU,而应使用 vTaskDelay() 或事件标志等机制主动让出 CPU。

2.2 周期(Period)与截止时间(Deadline)

核心原理:周期(T)是指任务两次被激活之间的时间间隔。截止时间(D)是指任务从被激活到必须完成的最晚时间。在硬实时系统中,D 通常等于 T(即任务必须在下一个周期到来前完成);在软实时系统中,D 可以大于 T,但超过 D 会导致服务质量下降。TinyML 中的模型推理任务通常属于周期型任务,例如每 50ms 执行一次关键词识别。

判断方法:确定任务的周期和截止时间是调度设计的第一步。对于传感器采样任务,周期由物理信号的最高频率决定(遵循奈奎斯特定理);对于模型推理任务,周期由应用需求决定(如视频流 30fps 对应 33ms 周期)。截止时间则取决于下游任务对数据时效性的容忍度。

任务类型 典型周期 (T) 截止时间 (D) 最坏执行时间 (WCET) CPU 利用率 (C/T)
唤醒词检测 50 ms 50 ms 15 ms 30%
人体活动识别 200 ms 180 ms 40 ms 20%
异常声音检测 事件触发 10 ms 8 ms 峰值 80%
BLE 通信 100 ms 100 ms 5 ms 5%

案例拆解:以智能音箱的语音交互为例。音频采集任务周期为 10ms(对应 16kHz 采样率,每周期采集 160 个样本),唤醒词检测任务周期为 50ms。若唤醒词检测的 WCET 为 20ms,则其 CPU 利用率为 40%。此时调度器必须确保在 50ms 周期内,音频采集任务(10ms 周期)能够被及时抢占执行,否则音频缓冲区会溢出。

经验提醒:在 TinyML 中,模型推理的 WCET 并非固定值。输入数据的不同(如不同长度的音频)会导致推理时间波动。建议使用 静态分析工具(如 aiWCET)或多次实测取最大值,并预留 20% 的余量。

2.3 优先级(Priority)与调度策略

核心原理:优先级是调度器决定任务执行顺序的关键属性。在固定优先级抢占式调度(Fixed-Priority Preemptive Scheduling)中,高优先级任务可以随时打断低优先级任务的执行。TinyML 中,事件型任务(如紧急停止检测)通常赋予最高优先级,而周期型模型推理任务根据其截止时间的紧迫程度分配优先级。

判断方法:常用的优先级分配算法是单调速率调度(RMS)——周期越短的任务优先级越高。例如,音频采集任务(T=10ms)的优先级应高于唤醒词检测任务(T=50ms)。另一种是最早截止时间优先(EDF),它动态调整优先级,截止时间越近的任务优先级越高,但 EDF 在 MCU 上实现开销较大。

// FreeRTOS 优先级配置示例
#define AUDIO_TASK_PRIORITY   (configMAX_PRIORITIES - 1)  // 最高优先级
#define WAKEUP_TASK_PRIORITY  (configMAX_PRIORITIES - 2)
#define HAR_TASK_PRIORITY     (configMAX_PRIORITIES - 3)
#define BLE_TASK_PRIORITY     (configMAX_PRIORITIES - 4)  // 最低优先级

案例拆解:考虑一个可穿戴设备,运行心率监测(周期 100ms)和跌倒检测(事件触发)。跌倒检测必须立即响应,因此赋予最高优先级。当跌倒检测任务被触发时,即使心率监测任务正在执行模型推理,也会被抢占。这种设计确保了安全关键功能的实时性,但代价是心率监测任务的完成时间可能被推迟。

常见误区:优先级反转(Priority Inversion)是经典问题。当低优先级任务持有高优先级任务需要的资源时,高优先级任务会被阻塞。在 TinyML 中,若两个模型共享同一个传感器数据缓冲区,必须使用互斥锁(Mutex)保护,并考虑使用优先级继承协议来缓解反转问题。

2.4 调度器的工作机制与调度延迟

核心原理:调度器在系统时钟节拍(Tick)的驱动下,周期性检查就绪队列。每次任务切换涉及上下文切换——保存当前任务的寄存器、堆栈指针,恢复新任务的上下文。上下文切换时间(通常为 2-10μs)是调度开销的主要来源。在 TinyML 中,如果模型推理任务本身只有几毫秒,频繁的上下文切换会显著降低有效计算效率。

判断方法:评估调度器性能的关键指标是调度延迟——从任务被激活到实际开始执行的时间。它由中断延迟、调度器执行时间和上下文切换时间组成。对于硬实时任务,必须保证最坏情况下的调度延迟小于截止时间。

  1. 中断延迟:从硬件中断触发到 ISR 开始执行的时间,通常由 CPU 的关中断时间决定。
  2. 调度决策时间:调度器扫描就绪队列并选择下一个任务的时间,与任务数量成正比。
  3. 上下文切换时间:保存和恢复寄存器的时间,与 MCU 架构相关(Cortex-M 系列通常为 12 个周期)。

案例拆解:在 STM32F4(Cortex-M4,168MHz)上运行 FreeRTOS,系统 Tick 设为 1ms。假设有 4 个模型任务,调度器扫描就绪队列耗时约 2μs,上下文切换约 4μs。若一个高优先级任务被释放,其调度延迟约为中断延迟(0.5μs)+ 调度决策(2μs)+ 上下文切换(4μs)= 6.5μs。这个值远小于模型推理的毫秒级时间,因此调度开销可以接受。

调度延迟测量方法:使用 GPIO 翻转法。在任务入口处拉高一个 GPIO,在任务出口处拉低,用逻辑分析仪测量脉冲宽度,即可得到实际的任务响应时间。

2.5 可调度性分析(Schedulability Analysis)

核心原理:在设计多模型调度方案时,必须验证所有任务能否在截止时间内完成。对于固定优先级调度,利用率测试是最简单的充分条件:对于 n 个周期任务,如果总利用率 U = Σ(Ci/Ti) ≤ n(2^(1/n) - 1),则任务集可调度。当 n 较大时,该界限趋近于 ln2 ≈ 0.693。

判断方法:对于 TinyML 场景,通常任务数不超过 5 个,因此可以使用精确的响应时间分析(RTA)。任务的响应时间 Ri 满足:Ri = Ci + Σ(Cj × ⌈Ri/Tj⌉),其中 j 是优先级高于 i 的任务。通过迭代求解 Ri,若 Ri ≤ Di,则任务可调度。

任务 周期 T (ms) 执行时间 C (ms) 优先级 利用率 C/T
音频采集 10 2 高 0.20
唤醒词检测 50 15 中 0.30
BLE 通信 100 5 低 0.05

总利用率 U = 0.20 + 0.30 + 0.05 = 0.55。对于 3 个任务,RMS 的充分条件界限为 3(2^(1/3) - 1) ≈ 0.779。由于 0.55 < 0.779,该任务集在 RMS 下可调度。但需注意,这只是充分条件,即使 U 超过界限,任务集仍可能可调度,需要进一步做 RTA 分析。

案例拆解:假设在上述任务集中,唤醒词检测的 WCET 从 15ms 增加到 25ms,则总利用率变为 0.20 + 0.50 + 0.05 = 0.75,仍小于 0.779。但通过 RTA 计算,唤醒词检测的响应时间 R = 25 + 2×(⌈R/10⌉) = 25 + 2×3 = 31ms(迭代两次后收敛),小于其截止时间 50ms,因此仍然可调度。但如果 WCET 增加到 30ms,R = 30 + 2×4 = 38ms,依然可调度。这说明利用率测试偏保守,实际可调度性更好。

经验总结:在 TinyML 中,模型推理的 WCET 往往占主导地位。建议在开发初期就建立每个模型的 WCET 基准表,并定期更新。当新增模型时,先做利用率估算,若 U > 0.7,则需要考虑优化模型(如量化、剪枝)或调整周期。

2.6 常见误区与设计复盘

误区一:所有任务都设为最高优先级。这会导致低优先级任务饿死,且调度器频繁切换,增加开销。正确做法是:只有对安全或用户体验至关重要的任务(如跌倒检测)才设为最高优先级。

误区二:忽略中断对任务时序的影响。中断服务程序(ISR)会抢占所有任务。如果 ISR 执行时间过长(如超过 100μs),会严重影响模型推理任务的截止时间。建议 ISR 只做标记事件,实际处理放在任务中。

误区三:使用阻塞式延时模拟周期。在裸机中常用的 HAL_Delay() 在 RTOS 中会阻塞整个任务,导致其他任务无法运行。必须使用 vTaskDelay() 或 vTaskDelayUntil() 实现精确周期。

误区四:忽视优先级反转。当两个模型任务共享一个 I2C 总线访问传感器时,若低优先级任务持有总线锁,高优先级任务会被阻塞。解决方法是使用互斥锁并启用优先级继承。

设计复盘清单:
  • ✅ 每个模型是否封装为独立任务?任务是否包含无限循环?
  • ✅ 是否根据 RMS 或 EDF 原则分配了优先级?
  • ✅ 是否测量了每个模型的最坏执行时间(WCET)?
  • ✅ 是否计算了总 CPU 利用率?是否小于 70%?
  • ✅ 是否验证了所有任务的响应时间小于截止时间?
  • ✅ 是否处理了共享资源的互斥访问?
  • ✅ 是否测量了实际调度延迟?