3、内核抢占模型概述:PREEMPT_NONE、PREEMPT_VOLUNTARY、PREEMPT、PREEMPT_RT
Linux 内核的抢占模型决定了任务调度器在何种条件下可以中断当前正在执行的任务,转而运行更高优先级的任务。这一机制直接影响了系统的实时响应能力与整体吞吐量。从完全非抢占到完全可抢占,内核提供了四种主要的抢占配置:PREEMPT_NONE、PREEMPT_VOLUNTARY、PREEMPT 和 PREEMPT_RT。理解它们之间的差异是进行实时系统选型的基础。
3.1 抢占模型的核心概念
在深入各个模型之前,需要明确两个关键概念:抢占点和临界区。抢占点是指内核代码中允许发生任务切换的位置。临界区是指访问共享资源的代码段,通常需要持有锁来保护。不同的抢占模型,本质上是内核在临界区内和临界区外设置抢占点的密度不同。
3.2 四种抢占模型详解
| 模型名称 | 内核配置选项 | 抢占点密度 | 典型延迟范围 | 适用场景 |
|---|---|---|---|---|
| PREEMPT_NONE | CONFIG_PREEMPT_NONE=y |
极低(仅系统调用返回时) | 10ms ~ 100ms+ | 服务器、批处理、科学计算 |
| PREEMPT_VOLUNTARY | CONFIG_PREEMPT_VOLUNTARY=y |
低(在显式抢占点) | 1ms ~ 10ms | 桌面系统、轻度多媒体 |
| PREEMPT | CONFIG_PREEMPT=y |
高(内核大部分区域) | 100µs ~ 1ms | 嵌入式、工业控制、音频 |
| PREEMPT_RT | CONFIG_PREEMPT_RT=y |
极高(包括中断上下文) | 1µs ~ 100µs | 硬实时、航天、医疗、机器人 |
3.3 PREEMPT_NONE:服务器级吞吐优先
这是 Linux 内核的默认配置,主要面向服务器和批处理系统。在该模型下,内核只在以下两个位置检查是否需要抢占:
- 从系统调用返回用户空间时
- 从中断处理程序返回时
这意味着,一旦任务进入内核态(例如执行系统调用),它将一直运行直到完成,除非它主动睡眠或等待资源。这种设计最大限度地减少了上下文切换开销,提高了缓存命中率和整体吞吐量,但代价是实时响应能力极差。
3.4 PREEMPT_VOLUNTARY:增加显式抢占点
PREEMPT_VOLUNTARY 在 PREEMPT_NONE 的基础上,在内核代码中显式插入了一些抢占点。这些抢占点通常位于可能长时间运行的循环或操作之前。开发者通过调用 cond_resched() 或 might_sleep() 等函数来标记这些位置。
/* PREEMPT_VOLUNTARY 下的典型代码模式 */
for (i = 0; i < large_number; i++) {
/* 处理数据 */
cond_resched(); /* 显式抢占点 */
}
这种模型比 PREEMPT_NONE 的响应速度有所提升,但仍然无法在内核临界区内进行抢占。它适合对响应有一定要求但吞吐量仍重要的场景,如桌面系统。
3.5 PREEMPT:内核大部分区域可抢占
PREEMPT 模型(通常称为“低延迟桌面配置”)显著增加了抢占点的密度。其核心特性是:
- 除了持有 spinlock 的临界区外,内核代码的大部分区域都可以被抢占
- 在每次中断返回时都会检查是否需要抢占
- 支持内核线程的实时优先级调度
该模型将抢占延迟降低到亚毫秒级别,是许多嵌入式 Linux 系统的首选。但它仍然存在一个关键限制:中断上下文(ISR)中无法被抢占,且 spinlock 保护的临界区会禁用抢占。
3.6 PREEMPT_RT:硬实时抢占模型
PREEMPT_RT 是 Linux 内核的实时抢占补丁集(现已合入主线),它将抢占能力推向了极致。其核心改造包括:
- 中断线程化:将大部分中断处理程序转换为内核线程,使其可以被高优先级任务抢占
- 可抢占的 spinlock:将 spinlock 替换为支持优先级继承的 rt_mutex,使得持有锁的临界区也可被抢占
- 高精度定时器:使用 hrtimer 替代传统的 timer wheel,提供微秒级定时精度
- 优先级继承协议:防止优先级反转问题
3.7 四种模型的对比总结
下表从多个维度对四种抢占模型进行了横向对比,帮助开发者根据项目需求做出选择:
| 对比维度 | PREEMPT_NONE | PREEMPT_VOLUNTARY | PREEMPT | PREEMPT_RT |
|---|---|---|---|---|
| 最大抢占延迟 | 100ms+ | 10ms | 1ms | 100µs |
| 吞吐量损失 | 0%(基准) | ~1-3% | ~5-10% | ~10-20% |
| 中断可抢占性 | 否 | 否 | 否 | 是(线程化) |
| spinlock 可抢占性 | 否 | 否 | 否 | 是(rt_mutex) |
| 优先级反转保护 | 无 | 无 | 有限 | 完整(优先级继承) |
| 内核版本要求 | 所有版本 | 2.6+ | 2.6+ | 5.x+(主线合入) |
3.8 如何选择:决策流程
在实际项目中,可以按照以下决策树来选择抢占模型:
- 系统是否需要硬实时保证?(延迟上限 < 100µs)→ 选择 PREEMPT_RT
- 系统是否需要软实时响应?(延迟上限 < 1ms)→ 选择 PREEMPT
- 系统是否对吞吐量敏感且允许偶尔延迟?(延迟上限 < 10ms)→ 选择 PREEMPT_VOLUNTARY
- 系统是否以吞吐量为唯一目标?→ 选择 PREEMPT_NONE