2. PREEMPT_RT 架构:内核抢占模型与实时调度策略
在深入排查 PREEMPT_RT 延迟抖动之前,必须理解 Linux 内核的四种抢占模型以及实时线程的调度策略。抢占模型决定了内核在何种时机可以打断当前执行的任务,而调度策略则决定了实时线程如何获得 CPU 资源。这两者共同构成了实时系统的底层行为框架,也是延迟抖动的根本来源。
2.1 内核抢占模型对比
Linux 内核从通用分时系统演进而来,其抢占能力经历了从无到有、从粗粒度到细粒度的演变。下表对比了四种主流抢占模型的核心差异:
| 抢占模型 | 内核配置宏 | 抢占时机 | 最大延迟 | 适用场景 |
|---|---|---|---|---|
| PREEMPT_NONE | CONFIG_PREEMPT_NONE |
仅在返回用户空间时 | 高(100ms+) | 服务器、批处理 |
| PREEMPT_VOLUNTARY | CONFIG_PREEMPT_VOLUNTARY |
在显式的抢占点(如调度点、锁释放) | 中(10ms~100ms) | 桌面、轻度交互 |
| PREEMPT | CONFIG_PREEMPT |
内核态几乎所有可抢占点(除临界区) | 低(1ms~10ms) | 嵌入式、低延迟桌面 |
| PREEMPT_RT | CONFIG_PREEMPT_RT |
几乎全部内核代码(包括中断处理、自旋锁) | 极低(us 级) | 工业控制、音频、航空航天 |
关键洞察: PREEMPT_RT 的核心贡献在于将大部分自旋锁替换为可抢占的
rt_mutex,并将中断处理线程化,从而消除了内核中最大的不可抢占区间。
下面通过一张 SVG 架构图直观展示四种模型在内核态与用户态之间的抢占边界差异:
排查提示: 在非 PREEMPT_RT 内核中,一个长时间运行的系统调用(如
read() 从慢速设备读取大量数据)可以阻塞高优先级实时线程数百毫秒。这是延迟抖动最常见的根源之一。
2.2 实时线程调度策略
PREEMPT_RT 内核提供了三种实时调度策略,它们决定了线程在就绪队列中的排序与执行规则。理解这些策略是分析调度延迟的前提。
2.2.1 SCHED_FIFO(先入先出)
- 规则: 优先级最高的线程一旦就绪,立即抢占当前运行的低优先级线程。同优先级线程按队列顺序执行,直到主动让出(如睡眠、等待 I/O)或被更高优先级线程抢占。
- 特点: 无时间片轮转,一个线程可以无限期占用 CPU。
- 风险: 如果高优先级线程陷入死循环或长时间忙等,低优先级线程(包括关键内核线程)将永久饿死。
2.2.2 SCHED_RR(轮转)
- 规则: 与 SCHED_FIFO 类似,但同优先级线程之间分配固定时间片(默认 100ms)。时间片耗尽后,线程被放到队列尾部。
- 特点: 提供了同优先级线程间的公平性,防止单个线程独占 CPU。
- 适用: 多个同等重要的实时任务需要共享 CPU 的场景。
2.2.3 SCHED_DEADLINE(截止时间)
- 规则: 基于最早截止时间优先(EDF)算法。每个线程指定三个参数:
runtime(每周期最大执行时间)、deadline(相对截止时间)、period(周期)。内核保证在 deadline 之前完成 runtime 的执行。 - 特点: 可调度性分析最强,适合硬实时任务。
- 限制: 需要精确的任务时序参数,过度使用可能导致系统过载。
| 调度策略 | 优先级范围 | 时间片 | 抢占行为 | 典型延迟抖动 |
|---|---|---|---|---|
| SCHED_FIFO | 1~99 | 无 | 高优先级立即抢占 | 低(但存在优先级反转风险) |
| SCHED_RR | 1~99 | 100ms(默认) | 同优先级时间片轮转 | 中(时间片切换引入抖动) |
| SCHED_DEADLINE | 0(内部动态) | 由 runtime/period 决定 | 基于截止时间 | 极低(可预测性强) |
常见陷阱: 将实时线程设置为 SCHED_FIFO 优先级 99 并绑定到同一个 CPU 核心,如果该线程执行
sched_yield() 或忙等,会导致该核心上的其他所有线程(包括中断处理)被饿死。排查时务必检查 /proc/sched_debug 中的调度统计。
下面通过一张调度流程图展示三种策略在就绪队列中的行为差异:
2.3 抢占模型与调度策略的协同
抢占模型和调度策略并非独立工作。PREEMPT_RT 内核通过以下机制确保实时线程的确定性:
- 中断线程化: 硬件中断被转换为内核线程,运行在 SCHED_FIFO 优先级 50。这意味着实时线程(优先级 80~99)可以抢占中断处理,消除中断带来的不可预测延迟。
- 自旋锁替换: 所有自旋锁被替换为
rt_mutex,支持优先级继承,防止优先级反转。 - RCU 抢占: 读-复制-更新(RCU)机制在 PREEMPT_RT 下支持抢占,不再阻塞实时线程。
实战要点: 在排查延迟抖动时,首先确认内核是否启用了
CONFIG_PREEMPT_RT。然后检查实时线程的调度策略和优先级。一个常见的错误是使用 SCHED_OTHER(非实时策略)运行关键任务,导致其被内核线程随意抢占。
通过本章的学习,你应该能够:
- 区分四种抢占模型对延迟的影响范围。
- 根据任务需求选择合适的实时调度策略。
- 理解 PREEMPT_RT 如何通过架构改造实现微秒级确定性。