Linux内核实时性补丁驱动适配:中断线程化改造实战 - 第3节
3.1 PREEMPT_RT 补丁的组成结构
PREEMPT_RT 补丁并非单一文件,而是一组针对 Linux 主线内核的增量补丁集合。其核心目标是将通用 Linux 内核改造为硬实时内核,改造范围覆盖调度器、中断子系统、时钟子系统、锁机制以及 RCU 等核心模块。
3.2 内核配置选项详解
启用 PREEMPT_RT 需要在内核配置中开启一系列选项。这些选项分布在“Processor type and features”、“General setup”、“Kernel Features”等菜单中。以下是关键配置项及其作用:
| 配置选项 | 所属菜单 | 作用描述 |
|---|---|---|
CONFIG_PREEMPT_RT |
Kernel Features | 启用完整实时抢占,替代 CONFIG_PREEMPT 和 CONFIG_PREEMPT_VOLUNTARY |
CONFIG_HIGH_RES_TIMERS |
General setup / Timers | 启用高精度定时器,提供纳秒级定时精度 |
CONFIG_IRQ_FORCED_THREADING |
Kernel Features | 强制所有中断线程化,包括非共享中断 |
CONFIG_PREEMPT_RCU |
RCU Subsystem | 启用可抢占 RCU,配合 RT 调度器实现 RCU 读侧临界区可抢占 |
CONFIG_RT_MUTEXES |
Kernel Features | 启用实时互斥锁,支持优先级继承协议 |
CONFIG_SPINLOCK_RT |
Kernel Features | 将自旋锁替换为可睡眠的 rt_mutex 基锁 |
CONFIG_EXPERT 以暴露更多 RT 相关选项。配置完成后,可通过 make olddefconfig 自动补全依赖选项。
3.3 关键特性一:中断线程化
中断线程化是 PREEMPT_RT 最核心的改造之一。传统 Linux 内核中,中断处理程序(ISR)运行在不可抢占的上下文(中断上下文),优先级高于任何用户态进程。这导致高优先级实时任务可能被低优先级硬件中断长时间阻塞。
PREEMPT_RT 将中断处理程序改造为内核线程:
- 中断上半部(Hard IRQ): 仅执行最必要的硬件操作(如 ACK 中断、清除中断标志),然后唤醒对应的中断线程。
- 中断下半部(Threaded IRQ): 以实时线程形式运行,受调度器管理,可被抢占,支持优先级继承。
in_interrupt() 判断上下文,因为 threaded IRQ 运行在进程上下文。应改用 in_irq() 或 in_serving_softirq() 进行精确判断。
3.4 关键特性二:高精度定时器
PREEMPT_RT 强制启用 CONFIG_HIGH_RES_TIMERS,将内核定时器精度从传统的 jiffy 级别(通常 1~10ms)提升到纳秒级别。高精度定时器基于硬件时钟事件设备(clock_event_device)实现,不再依赖周期性的 tick 中断。
- 传统定时器: 基于 jiffies 的定时器,精度受限于 HZ 值,最小超时粒度为 1/HZ 秒。
- 高精度定时器(hrtimer): 使用红黑树组织,按绝对时间排序,支持纳秒级超时,与硬件事件设备直接交互。
| 特性 | 传统定时器(timer_list) | 高精度定时器(hrtimer) |
|---|---|---|
| 精度 | 1/HZ(通常 1~10ms) | 纳秒级(受硬件限制) |
| 数据结构 | 链表 + 时间轮 | 红黑树 |
| 触发方式 | 依赖 tick 中断 | 硬件事件设备直接触发 |
| 上下文 | 软中断(TIMER_SOFTIRQ) | 硬中断或软中断(可配置) |
| RT 适配 | 不保证实时性 | 支持线程化回调 |
hrtimer 替代 timer_list。hrtimer 回调默认运行在软中断上下文,但可通过 HRTIMER_MODE_REL 和 HRTIMER_MODE_ABS 控制触发模式。对于实时要求严格的场景,可结合 HRTIMER_CB_IRQSAFE 标志。
3.5 关键特性三:RCU 优先级继承
RCU(Read-Copy-Update)是 Linux 内核中广泛使用的无锁同步机制。在 PREEMPT_RT 中,RCU 读侧临界区可能被高优先级任务抢占,导致 RCU 宽限期(grace period)无限延长。为此,PREEMPT_RT 引入了 RCU 优先级继承机制:
- 可抢占 RCU(PREEMPT_RCU): 允许在读侧临界区内发生抢占,RCU 宽限期检测器会跟踪被抢占的读者。
- RCU 优先级继承: 当低优先级读者阻塞了高优先级写者时,读者临时继承写者的优先级,加速宽限期完成。
rcu_read_lock(),确保临界区内不调用可能睡眠的函数。对于长时间持有的 RCU 读锁,考虑使用 srcu 或 rcu_read_lock_sched() 替代。
3.6 关键特性四:自旋锁与互斥锁替换
PREEMPT_RT 对内核锁机制进行了根本性改造:将传统自旋锁(spinlock)替换为基于实时互斥锁(rt_mutex)的可睡眠锁。这是实现内核可抢占的关键步骤。
| 原始锁类型 | PREEMPT_RT 替换类型 | 关键变化 |
|---|---|---|
spinlock_t |
rt_spinlock(基于 rt_mutex) |
可睡眠,支持优先级继承 |
rwlock_t |
rt_rwlock(基于 rt_mutex) |
读写锁可睡眠,写者优先 |
raw_spinlock_t |
保持不变 | 仍为原始自旋锁,用于真正不可抢占的路径 |
mutex |
保持不变(已支持优先级继承) | mutex 本身已支持 PI,RT 下行为一致 |
spin_lock() 内部可能睡眠,因此不能在真正不可抢占的上下文(如硬件中断处理函数、raw_spinlock 临界区)中使用。驱动中必须使用 raw_spin_lock() 保护那些绝对不允许睡眠的极短临界区。
锁替换带来的实际影响:
- 临界区可抢占: 持有 spinlock 的代码可能被更高优先级的任务抢占,降低了优先级反转风险。
- 优先级继承: 当低优先级任务持有锁而高优先级任务等待时,低优先级任务临时继承高优先级,加速锁释放。
- API 兼容性: 驱动代码无需修改锁 API 调用,PREEMPT_RT 通过宏定义和类型重定义实现透明替换。
3.7 关键特性总结与驱动适配对照
下表汇总了 PREEMPT_RT 四大关键特性对驱动开发的影响,帮助开发者快速定位适配要点:
| 关键特性 | 对驱动的影响 | 适配动作 |
|---|---|---|
| 中断线程化 | 中断处理函数运行在进程上下文 | 移除 in_interrupt() 判断;使用 irq_set_thread_prio() 设置线程优先级 |
| 高精度定时器 | 定时器精度提升,回调上下文变化 | 迁移到 hrtimer API;注意回调中不能调用可能导致睡眠的函数 |
| RCU 优先级继承 | RCU 读侧临界区可抢占 | 缩短 RCU 读侧临界区;避免在临界区内调用复杂函数 |
| 自旋锁替换 | spinlock 变为可睡眠锁 | 使用 raw_spin_lock() 保护极短临界区;检查锁使用上下文 |