4. PREEMPT_NONE 模型:设计目标、适用场景(服务器)与性能特征

PREEMPT_NONE 是 Linux 内核默认的抢占模型(对应内核配置 CONFIG_PREEMPT_NONE=y),其核心设计哲学是“吞吐优先,延迟让位”。该模型几乎完全禁用了内核态抢占,仅在返回用户态时执行调度检查,是四种抢占模型(NONE / VOLUNTARY / LL / RT)中抢占开销最低、但最差情况延迟最高的配置。

4.1 设计目标:最大化吞吐量

PREEMPT_NONE 的设计目标可归纳为三个层次:

  • 消除内核抢占开销:进程在内核态执行系统调用时,不会被其他进程抢占,避免了抢占点检查、上下文切换和缓存污染。
  • 保持内核路径原子性:系统调用、中断下半部(softirq / tasklet)和内核线程在临界区内连续执行,无需考虑被中途打断后的数据一致性。
  • 优化 CPU 缓存利用率:长时运行的内核路径可以充分利用 L1/L2 缓存的热数据,减少因频繁切换导致的缓存失效。
核心权衡: PREEMPT_NONE 用“不可预测的调度延迟”换取“最高的系统吞吐量”。在服务器场景中,这种权衡通常是合理的——用户更关心每秒处理多少请求,而非单个请求的尾部延迟。
PREEMPT_NONE vs 可抢占模型:内核执行流对比 PREEMPT_NONE 用户态进程 A 内核态系统调用 连续执行(无抢占) 返回用户态(调度点) 可抢占模型(PREEMPT_LL / RT) 用户态进程 B 内核态系统调用 被高优任务抢占 抢占恢复 + 调度 PREEMPT_NONE 在内核态不被打断,可抢占模型则可能被高优先级任务插入执行

4.2 适用场景:吞吐优先的服务器工作负载

PREEMPT_NONE 最适合以下服务器场景:

场景 典型负载 为什么适合 PREEMPT_NONE
Web 服务器 Nginx / Apache 处理 HTTP 请求 大量短连接,吞吐量是首要指标;毫秒级延迟波动可接受
数据库服务器 MySQL / PostgreSQL 查询处理 磁盘 I/O 和内存访问占主导,内核抢占节省的开销可忽略
文件服务器 NFS / Samba 文件共享 批量数据传输,延迟不敏感,吞吐量决定用户体验
批处理 / 离线计算 Hadoop / Spark 作业 任务级并行,单个任务的完成时间不重要,整体吞吐是关键
虚拟化宿主机 KVM / Xen 管理虚拟机 虚拟机调度由 hypervisor 管理,宿主机内核抢占模型影响较小
经验法则: 如果你的系统对 99.9 百分位延迟 没有硬性要求(例如 < 10ms),且 CPU 密集型或 I/O 密集型工作负载占主导,PREEMPT_NONE 通常是默认且最优的选择。

4.3 性能特征:吞吐量优势与上下文切换成本

PREEMPT_NONE 的性能特征可以通过以下关键指标量化:

  • 上下文切换率:相比 PREEMPT_VOLUNTARY 降低约 30%~50%,相比 PREEMPT_RT 降低 60%~80%。
  • 系统调用延迟:平均延迟最低,因为无需在每次系统调用入口/出口检查抢占标志。
  • 中断响应延迟:中断处理完成后,高优先级任务必须等待当前内核路径执行完毕(直到返回用户态)才能获得 CPU。
  • 缓存污染:最小化,因为内核路径连续执行,不会因抢占导致 TLB / cache 刷新。
四种抢占模型性能特征对比(归一化评分,越高越好) 吞吐量 95% (NONE) 80% (VOLUNTARY) 60% (LL) 40% (RT) 延迟确定性 20% (NONE) 40% (VOLUNTARY) 70% (LL) 95% (RT) 低切换开销 90% (NONE) 70% (VOLUNTARY) 50% (LL) 30% (RT) PREEMPT_NONE 在吞吐量和低切换开销上表现最佳,但延迟确定性最差 PREEMPT_RT 则完全相反——以吞吐量换取可预测的延迟

4.4 延迟分析:最差情况延迟的来源

PREEMPT_NONE 的延迟问题主要源于以下三个内核路径:

  1. 系统调用长路径:例如 read() 从磁盘读取大量数据时,内核可能在 D state 下阻塞数毫秒甚至数十毫秒,期间高优先级任务无法抢占。
  2. 中断下半部(softirq):网络栈的 NET_RX_SOFTIRQ 可能一次处理数百个数据包,占用 CPU 数百微秒到数毫秒。
  3. 内核锁竞争:当多个 CPU 同时争用同一把锁(如 tasklist_lock),等待锁的 CPU 可能自旋等待数百微秒。
关键警告: 在 PREEMPT_NONE 下,最差情况延迟(Worst-Case Latency)可能达到 10ms ~ 100ms 级别。对于音频处理、工业控制、机器人等实时应用,这种延迟是不可接受的。即使对于某些软实时服务器场景(如高频交易),PREEMPT_NONE 也可能导致尾部延迟超标。

4.5 内核配置与运行时验证

确认当前系统是否使用 PREEMPT_NONE 模型:

# 方法一:查看内核配置
zcat /proc/config.gz | grep CONFIG_PREEMPT

# 方法二:查看 /proc/sys/kernel/sched_features
cat /proc/sys/kernel/sched_features

# 方法三:使用 uname 查看内核抢占标记
uname -a
# 输出示例(PREEMPT_NONE 无特殊标记):
# Linux server01 5.15.0-91-generic #101-Ubuntu SMP ... x86_64 GNU/Linux
# 注意:PREEMPT_NONE 内核在 uname 输出中不会出现 "PREEMPT" 字样

测量 PREEMPT_NONE 下的调度延迟:

# 使用 cyclictest 测量(需要安装 rt-tests)
sudo cyclictest -l 100000 -m -n -p 99 -i 1000 -h 100

# 输出示例(重点关注最大延迟):
# T: 0 (12345) P: 99 I: 1000 Min: 2 Act: 5 Avg: 4 Max: 1876
# 最大延迟 1876 µs ≈ 1.9 ms,远高于 PREEMPT_RT 的典型值(< 50 µs)
性能数据参考: 在相同的 x86_64 服务器硬件上(Intel Xeon Gold 6248,2.5GHz),使用 cyclictest 测量 100 万次循环:
- PREEMPT_NONE:最大延迟 1.5ms ~ 5ms,平均延迟 3µs ~ 8µs
- PREEMPT_RT:最大延迟 15µs ~ 50µs,平均延迟 2µs ~ 5µs
PREEMPT_NONE 的最大延迟比 PREEMPT_RT 高出 30~100 倍。

4.6 何时应该避免使用 PREEMPT_NONE

以下场景应主动切换到 PREEMPT_VOLUNTARY 或 PREEMPT_LL:

  • 交互式桌面系统:用户期望鼠标点击、键盘输入得到毫秒级响应。
  • 音频/视频播放:缓冲区欠载(underrun)会导致卡顿或爆音。
  • 软实时服务器:如游戏服务器、视频流服务器,尾部延迟影响用户体验。
  • 嵌入式控制:即使非硬实时,也需要可预测的响应时间。

对于这些场景,PREEMPT_NONE 的“吞吐优先”策略反而会降低整体服务质量——因为单个慢请求可能阻塞后续所有请求的处理。