3、内核抢占模型概述:PREEMPT_NONE、PREEMPT_VOLUNTARY、PREEMPT、PREEMPT_RT

Linux 内核的抢占模型决定了任务调度器在何种条件下可以中断当前正在执行的任务,转而运行更高优先级的任务。这一机制直接影响了系统的实时响应能力与整体吞吐量。从完全非抢占到完全可抢占,内核提供了四种主要的抢占配置:PREEMPT_NONE、PREEMPT_VOLUNTARY、PREEMPT 和 PREEMPT_RT。理解它们之间的差异是进行实时系统选型的基础。

3.1 抢占模型的核心概念

在深入各个模型之前,需要明确两个关键概念:抢占点和临界区。抢占点是指内核代码中允许发生任务切换的位置。临界区是指访问共享资源的代码段,通常需要持有锁来保护。不同的抢占模型,本质上是内核在临界区内和临界区外设置抢占点的密度不同。

关键指标:抢占延迟(Preemption Latency)——从高优先级任务就绪到其获得 CPU 执行所经历的时间。这是衡量实时性能的核心指标。
抢占延迟构成示意图 高优任务就绪 抢占点到达 上下文切换 任务开始执行 抢占延迟 切换延迟 抢占延迟 = 等待抢占点时间 + 上下文切换时间

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 内核的默认配置,主要面向服务器和批处理系统。在该模型下,内核只在以下两个位置检查是否需要抢占:

  • 从系统调用返回用户空间时
  • 从中断处理程序返回时

这意味着,一旦任务进入内核态(例如执行系统调用),它将一直运行直到完成,除非它主动睡眠或等待资源。这种设计最大限度地减少了上下文切换开销,提高了缓存命中率和整体吞吐量,但代价是实时响应能力极差。

注意:在 PREEMPT_NONE 下,一个低优先级的任务如果在内核中执行长时间操作(如文件系统遍历),可以阻塞高优先级任务长达数十毫秒甚至更久。这对于实时系统是不可接受的。

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 保护的临界区会禁用抢占。

PREEMPT 模型:内核抢占点分布 用户空间(User Space) 内核空间(Kernel Space) 可抢占区域 (系统调用、文件操作等) 不可抢占区域 (spinlock 临界区、中断上下文) 橙色圆点表示抢占点(preemption points),绿色区域可被高优先级任务抢占

3.6 PREEMPT_RT:硬实时抢占模型

PREEMPT_RT 是 Linux 内核的实时抢占补丁集(现已合入主线),它将抢占能力推向了极致。其核心改造包括:

  1. 中断线程化:将大部分中断处理程序转换为内核线程,使其可以被高优先级任务抢占
  2. 可抢占的 spinlock:将 spinlock 替换为支持优先级继承的 rt_mutex,使得持有锁的临界区也可被抢占
  3. 高精度定时器:使用 hrtimer 替代传统的 timer wheel,提供微秒级定时精度
  4. 优先级继承协议:防止优先级反转问题
实践建议:PREEMPT_RT 并非万能。它引入了额外的上下文切换开销(约 5-15% 的性能损失),且需要应用程序遵循实时编程规范(如避免动态内存分配、使用实时信号量等)。在选型时,应评估系统对延迟上限的硬性要求。

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+(主线合入)
抢占模型:延迟 vs 吞吐量权衡 实时性能(抢占延迟降低) → 吞吐量 → NONE VOLUNTARY PREEMPT PREEMPT_RT 从 NONE 到 RT:抢占延迟降低约 1000 倍,吞吐量损失约 10-20%

3.8 如何选择:决策流程

在实际项目中,可以按照以下决策树来选择抢占模型:

  1. 系统是否需要硬实时保证?(延迟上限 < 100µs)→ 选择 PREEMPT_RT
  2. 系统是否需要软实时响应?(延迟上限 < 1ms)→ 选择 PREEMPT
  3. 系统是否对吞吐量敏感且允许偶尔延迟?(延迟上限 < 10ms)→ 选择 PREEMPT_VOLUNTARY
  4. 系统是否以吞吐量为唯一目标?→ 选择 PREEMPT_NONE
经验法则:对于大多数嵌入式实时系统,PREEMPT 是性价比最高的选择。它提供了足够的实时性能(亚毫秒级),同时保持了相对较低的吞吐量损失。只有当系统对延迟有硬性上限要求(如 < 100µs)时,才需要升级到 PREEMPT_RT。