2. PREEMPT_RT内核原理

本章深入剖析 PREEMPT_RT 内核的核心机制。理解这些原理是后续移植与性能调优的基础。我们将从内核抢占模型的演进历史出发,逐步解析自旋锁与互斥锁的实时化改造、中断线程化机制以及高精度定时器的实现原理。

2.1 内核抢占模型演进:从 UP 到 SMP 再到 RT

Linux 内核的抢占模型经历了从不可抢占到完全可抢占的演进过程,这是实现实时性的关键。下表总结了不同内核版本的抢占模型特点:

内核版本 / 特性 抢占模型 特点 实时性
Linux 2.4 及之前 不可抢占 (UP) 进程在内核态运行时无法被抢占,直到主动返回用户态或阻塞。 差(非实时)
Linux 2.6 (PREEMPT_NONE) 不可抢占 (SMP) 与 UP 类似,但引入了自旋锁用于 SMP 保护。 差(非实时)
Linux 2.6 (PREEMPT_VOLUNTARY) 自愿抢占 在显式的抢占点(如系统调用返回、锁释放)检查是否需要调度。 较低
Linux 2.6 (PREEMPT) 低延迟抢占 允许在中断返回、异常返回等时机抢占内核态进程。 中等(软实时)
PREEMPT_RT (RT) 完全可抢占 通过中断线程化、实时互斥锁等机制,将内核大部分临界区变为可抢占。 高(硬实时)
关键概念: 抢占延迟是衡量实时性的核心指标。它指从高优先级任务被唤醒到其开始执行的时间。PREEMPT_RT 的目标是将最坏情况下的抢占延迟控制在微秒级。

下图展示了从不可抢占内核到 PREEMPT_RT 内核的抢占模型演进路径:

内核抢占模型演进路径 不可抢占 自愿抢占 低延迟抢占 完全可抢占 演进说明: • 不可抢占:进程在内核态运行时,其他进程无法抢占CPU,导致高优先级任务响应延迟不可控。 • 自愿抢占:在特定的抢占点(如系统调用返回、锁释放)检查调度,减少了延迟,但仍不够。 • 低延迟抢占:允许在中断返回时抢占,但自旋锁保护的临界区仍不可抢占。 • 完全可抢占:通过实时互斥锁和中断线程化,使得几乎所有内核代码都可被抢占。

2.2 自旋锁与互斥锁的实时化改造

锁机制是影响内核实时性的关键因素。在标准 Linux 内核中,自旋锁保护的临界区是不可抢占的,这会导致优先级反转和长延迟。PREEMPT_RT 对此进行了根本性改造。

2.2.1 自旋锁的实时化:变为可睡眠锁

在 PREEMPT_RT 中,自旋锁被实现为基于实时互斥锁的可睡眠锁。当进程试图获取一个已被持有的自旋锁时,它会被阻塞(睡眠),而不是忙等待。这允许更高优先级的任务抢占当前持有锁的进程,从而避免优先级反转。

核心变化: spin_lock() 在 PREEMPT_RT 中不再禁用内核抢占,而是调用 rt_mutex_lock()。这意味着持有自旋锁的进程可以被更高优先级的任务抢占。

2.2.2 互斥锁的实时化:优先级继承

标准互斥锁存在优先级反转问题。PREEMPT_RT 使用实时互斥锁(rt_mutex),它实现了优先级继承协议。当低优先级任务持有高优先级任务所需的锁时,低优先级任务会临时继承高优先级任务的优先级,从而尽快释放锁。

下表对比了标准锁与 PREEMPT_RT 锁的关键差异:

特性 标准 Linux 自旋锁 PREEMPT_RT 自旋锁 标准互斥锁 PREEMPT_RT 互斥锁
实现方式 忙等待 + 禁用抢占 基于 rt_mutex 的可睡眠锁 快速用户态锁 (futex) rt_mutex + 优先级继承
抢占性 不可抢占 可抢占 可抢占 可抢占
优先级反转 可能发生 通过可睡眠特性缓解 可能发生 通过优先级继承解决
适用场景 短临界区,SMP 所有临界区 用户态同步 内核态与用户态实时同步

下图展示了优先级继承协议的工作流程:

优先级继承协议工作流程 任务A (优先级 10) 任务B (优先级 50) 共享锁 L 步骤说明: 1. 任务A获取锁L,进入临界区。 2. 任务B被唤醒,尝试获取锁L, 但发现已被任务A持有,因此阻塞。 3. 内核检测到优先级反转,将任务A 的优先级提升至50(继承)。 4. 任务A继续执行,尽快释放锁L。 5. 任务A释放锁后,优先级恢复为10。 6. 任务B获取锁L,开始执行。

2.3 中断线程化 (IRQ Threads) 机制

在标准 Linux 中,中断处理程序运行在中断上下文,具有最高优先级,且不可被抢占。这会导致高优先级任务被中断处理程序延迟。PREEMPT_RT 通过中断线程化解决了这个问题。

核心思想: 将中断处理程序的上半部(Top Half)和下半部(Bottom Half)合并,作为一个内核线程来运行。这个线程可以被调度,可以被其他更高优先级的任务抢占。

  • 中断线程化: 在 request_irq() 时,内核会为每个中断创建一个内核线程(例如 irq/21-eth0)。中断发生时,硬件中断处理程序只做最少的必要工作(如应答中断),然后唤醒对应的中断线程。
  • 线程优先级: 中断线程的优先级可以通过 chrt 命令或 sched_setscheduler() 系统调用进行调整。实时任务可以设置比中断线程更高的优先级,从而确保实时性。
  • 可抢占性: 中断线程是可抢占的。如果一个高优先级实时任务被唤醒,它可以抢占正在运行的中断线程。
注意事项: 并非所有中断都适合线程化。例如,时钟中断(timer tick)和某些架构特定的中断(如 IPI)需要极低延迟,通常不会被线程化。PREEMPT_RT 提供了 irq_set_affinity_hint() 和 request_threaded_irq() 等接口来精细控制。

下图对比了标准中断处理与中断线程化的流程:

标准中断 vs 中断线程化 标准中断处理 1. 硬件中断发生 2. CPU 进入中断上下文 3. 执行 ISR (不可抢占) 4. 调度下半部 (Softirq/Tasklet) 5. 返回被中断的进程 中断线程化 (PREEMPT_RT) 1. 硬件中断发生 2. 执行最小 ISR (应答中断) 3. 唤醒中断线程 (irq/xxx) 4. 中断线程被调度执行 (可抢占) 5. 高优先级任务可抢占中断线程 关键差异: • 标准中断:ISR 运行在中断上下文,优先级高于所有进程,导致实时任务被延迟。 • 中断线程化:中断处理被降级为普通线程,可以被调度器管理,实时任务可以设置更高优先级。 • 效果:将最坏情况下的中断延迟从不可控变为可控,是 PREEMPT_RT 降低延迟的关键技术。

2.4 高精度定时器 (HRTIMER)

标准 Linux 定时器基于周期性时钟滴答(jiffies),精度受限于 HZ 值(通常为 100Hz 或 1000Hz,即 10ms 或 1ms 精度)。PREEMPT_RT 引入了高精度定时器(HRTIMER),它基于硬件定时器(如 TSC、HPET、ACPI PM 定时器)实现,精度可达纳秒级。

2.4.1 HRTIMER 的核心机制

  • 红黑树组织: 所有高精度定时器按到期时间组织在一棵红黑树中,内核可以快速找到下一个到期的定时器。
  • 硬件定时器编程: 内核根据红黑树中最早到期的定时器,直接编程硬件定时器。当硬件定时器到期时,产生一个中断,内核处理该定时器。
  • 动态编程: 与周期性时钟不同,HRTIMER 是事件驱动的。只有在有定时器到期时才会产生中断,避免了无用的时钟滴答,降低了功耗和 CPU 负载。
API 示例: 使用 hrtimer_start() 启动一个高精度定时器。
#include <linux/hrtimer.h>
#include <linux/ktime.h>

struct hrtimer my_timer;

// 定时器回调函数
enum hrtimer_restart my_timer_callback(struct hrtimer *timer) {
    // 执行定时任务
    printk(KERN_INFO "HRTIMER fired!\n");
    return HRTIMER_NORESTART; // 不重启定时器
}

void setup_timer(void) {
    ktime_t ktime;
    // 初始化定时器
    hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    my_timer.function = my_timer_callback;
    // 设置超时时间为 100 微秒
    ktime = ktime_set(0, 100000); // 0秒, 100000纳秒
    hrtimer_start(&my_timer, ktime, HRTIMER_MODE_REL);
}

2.4.2 HRTIMER 与标准定时器对比

特性 标准定时器 (timer_list) 高精度定时器 (hrtimer)
精度 jiffies 粒度 (通常 1-10ms) 纳秒级
触发方式 基于周期性时钟滴答 基于硬件定时器事件驱动
数据结构 链表 红黑树
功耗 较高 (周期性唤醒 CPU) 较低 (事件驱动,空闲时可深度睡眠)
适用场景 非实时、低精度任务 实时控制、音频、网络等需要高精度定时的场景

下图展示了 HRTIMER 的工作流程:

高精度定时器 (HRTIMER) 工作流程 应用程序 hrtimer_start() 红黑树管理 硬件定时器 回调函数执行 流程:应用程序通过 hrtimer_start() 注册定时器 → 内核将定时器插入红黑树 → 内核编程硬件定时器 → 硬件定时器到期产生中断 → 内核执行回调函数。