4. 内核配置选项:CONFIG_PREEMPT、CONFIG_PREEMPT_RT、CONFIG_HZ、CONFIG_NO_HZ_FULL 详解

嵌入式 Linux 实时性的核心在于内核的抢占模型与时间管理机制。本章深入剖析四个关键内核配置选项,它们共同决定了系统的实时响应能力、调度延迟和 CPU 利用率。理解这些选项的差异与组合方式,是构建确定性实时系统的基石。

核心观点:实时调度器的配置不是简单的“开/关”选择,而是根据应用场景(硬实时、软实时、吞吐量优先)在抢占粒度、时钟精度和功耗之间进行权衡。错误的配置组合可能导致实时任务错过截止时间。

4.1 抢占模型:CONFIG_PREEMPT 与 CONFIG_PREEMPT_RT

抢占模型决定了内核何时可以打断当前执行的任务,转而运行更高优先级的任务。Linux 内核提供三种主要的抢占模式,通过 CONFIG_PREEMPT 系列选项选择。

4.1.1 三种抢占模式对比

配置选项 抢占模型 调度延迟 适用场景 实时性等级
CONFIG_PREEMPT_NONE 无抢占(服务器) 100ms ~ 1s 批处理、服务器、吞吐量优先 非实时
CONFIG_PREEMPT_VOLUNTARY 自愿抢占(桌面) 10ms ~ 100ms 桌面系统、低延迟交互 软实时(弱)
CONFIG_PREEMPT 全抢占(低延迟) 1ms ~ 10ms 嵌入式、多媒体、工业控制 软实时(强)
CONFIG_PREEMPT_RT 实时抢占(RT) 10µs ~ 100µs 硬实时、航空、医疗、机器人 硬实时
关键差异:CONFIG_PREEMPT 允许在内核态的大部分位置(除持有 spinlock 的临界区外)进行抢占;而 CONFIG_PREEMPT_RT 通过将 spinlock 替换为可抢占的 rt_mutex,实现了几乎全部内核代码的可抢占性,从而将调度延迟降低到微秒级。

4.1.2 CONFIG_PREEMPT_RT 的架构变化

启用 CONFIG_PREEMPT_RT 后,内核内部发生了根本性的变化:

  • 中断线程化:所有硬件中断处理程序(IRQ)变为内核线程,可被高优先级实时任务抢占。
  • 自旋锁替换:大部分 spinlock_t 被替换为 rt_mutex,支持优先级继承,防止优先级反转。
  • RCU 抢占:RCU 读侧临界区变为可抢占。
  • 高精度定时器:默认使用 HRTIMER,提供纳秒级定时精度。
注意:CONFIG_PREEMPT_RT 补丁集需要独立下载(patch-*.rt*.patch.xz),并非主线内核默认包含。启用后,由于中断线程化和锁机制的开销,系统整体吞吐量会下降约 10%~20%。

4.1.3 抢占模型选择决策树

抢占模型选择决策树 是否需要硬实时? 是 否 CONFIG_PREEMPT_RT 需要 RT 补丁集 调度延迟:10µs ~ 100µs 软实时/非实时 延迟敏感? 吞吐优先? CONFIG_PREEMPT 内核态大部分可抢占 调度延迟:1ms ~ 10ms CONFIG_PREEMPT_NONE 仅在用户态可抢占 调度延迟:100ms ~ 1s

4.2 内核时钟频率:CONFIG_HZ

CONFIG_HZ 定义了内核定时器中断的频率,即每秒产生多少次时钟中断(tick)。这个值直接影响调度器的决策粒度、时间片长度和系统开销。

4.2.1 常见 HZ 值及其影响

HZ 值 tick 间隔 时间片长度(默认) CPU 开销 调度响应 典型场景
100 10 ms 100 ms 极低 慢 服务器、省电设备
250 4 ms 40 ms 低 中等 桌面系统
300 3.33 ms 33 ms 低 中等 视频播放、多媒体
1000 1 ms 10 ms 较高 快 嵌入式实时、工业控制
实时建议:对于嵌入式实时系统,推荐设置 CONFIG_HZ=1000。更高的 HZ 值意味着调度器可以更频繁地检查是否需要抢占,从而降低调度延迟。但代价是 CPU 在定时器中断处理上的开销增加约 1%~3%。

4.2.2 HZ 与时间片的关系

在 CFS(完全公平调度器)中,默认时间片长度由 sched_latency_ns(调度延迟)和 nr_running(运行队列任务数)决定,但 HZ 值限制了时间片的最小粒度。内核代码中,时间片的计算涉及 CONFIG_HZ:

// kernel/sched/fair.c (简化示意)
unsigned int sysctl_sched_min_granularity = 0.75 * NSEC_PER_SEC / HZ;
// 当 HZ=1000 时,最小粒度约为 750µs
// 当 HZ=100 时,最小粒度约为 7.5ms

这意味着,HZ 值越低,调度器能够分配的最小时间片越长,高优先级任务等待低优先级任务的时间片耗尽的时间也越长。

4.3 动态时钟:CONFIG_NO_HZ_FULL

CONFIG_NO_HZ_FULL(全无滴答模式)允许在指定的 CPU 上完全关闭周期性定时器中断,只有在任务状态发生变化(如唤醒、阻塞、优先级变化)时才产生中断。这对于需要极低抖动(jitter)的实时任务至关重要。

4.3.1 三种 NO_HZ 模式对比

配置选项 模式名称 行为描述 适用场景
CONFIG_NO_HZ_IDLE 空闲无滴答 CPU 空闲时关闭 tick,省电 所有系统(默认)
CONFIG_NO_HZ_FULL 全无滴答 指定 CPU 上始终关闭 tick(非空闲时也关闭) 实时计算、DPDK、高频交易
两者均未选 周期性 tick 所有 CPU 始终产生 tick 中断 传统服务器、非实时

4.3.2 全无滴答模式的配置步骤

  1. 内核配置:启用 CONFIG_NO_HZ_FULL=y 和 CONFIG_NO_HZ_FULL_ALL=y(可选)。
  2. 内核启动参数:添加 nohz_full=1-3(指定 CPU 1、2、3 进入全无滴答模式)。
  3. 隔离 CPU:同时使用 isolcpus=1-3 将指定 CPU 从内核负载均衡中隔离。
  4. 绑定实时任务:使用 taskset 或 cpuset 将实时任务绑定到隔离 CPU。
重要限制:全无滴答模式要求指定 CPU 上不能运行任何常规内核线程(如 kworker、rcu_sched)。必须配合 isolcpus 和 rcu_nocbs 使用,否则内核线程的周期性唤醒会破坏无滴答效果。

4.3.3 无滴答模式对实时性的影响

周期性 tick vs 全无滴答模式对比 周期性 tick (HZ=1000) 每 1ms 产生一次定时器中断 → 调度器被强制唤醒 → 引入固定抖动 实时任务执行 实时任务执行 执行 tick 中断 tick 中断 全无滴答模式 (NO_HZ_FULL) 实时任务连续执行,无周期性中断干扰 任务唤醒 任务阻塞 抖动对比:周期性 tick 抖动约 1ms → 全无滴答抖动可降至 < 10µs

4.4 配置组合实战:构建实时内核

以下是一个针对 ARM Cortex-A 系列嵌入式平台的实时内核配置片段,展示了如何组合上述选项:

# 实时抢占模型
CONFIG_PREEMPT_RT=y
CONFIG_PREEMPT=y          # RT 会自动选择此选项

# 高精度时钟
CONFIG_HZ=1000
CONFIG_HIGH_RES_TIMERS=y

# 全无滴答模式(用于实时 CPU)
CONFIG_NO_HZ_FULL=y
CONFIG_NO_HZ_FULL_ALL=y   # 允许所有 CPU 进入全无滴答
CONFIG_RCU_NOCB_CPU=y     # 将 RCU 回调卸载到非实时 CPU
CONFIG_RCU_NOCB_CPU_ALL=y

# 其他实时相关选项
CONFIG_IRQ_FORCED_THREADING=y  # 强制中断线程化
CONFIG_RT_GROUP_SCHED=y        # 实时任务组调度
CONFIG_SCHED_HRTICK=y          # 高精度调度 tick
验证方法:编译内核后,通过 zcat /proc/config.gz | grep -E "PREEMPT|HZ|NO_HZ" 确认配置生效。使用 cyclictest 工具测量调度延迟,对比不同配置下的最大延迟和抖动值。

4.5 配置选项的相互影响与约束

这些配置选项并非独立工作,它们之间存在依赖和约束关系:

  • CONFIG_PREEMPT_RT 依赖 CONFIG_PREEMPT:RT 补丁集建立在全抢占模型之上,不能与 CONFIG_PREEMPT_NONE 或 CONFIG_PREEMPT_VOLUNTARY 共存。
  • CONFIG_NO_HZ_FULL 需要 CONFIG_NO_HZ_IDLE:全无滴答模式是空闲无滴答的超集,必须同时启用。
  • HZ 与 NO_HZ_FULL 的权衡:即使启用了全无滴答,CONFIG_HZ 值仍然影响非隔离 CPU 的行为。建议将实时 CPU 的 HZ 设为 1000,非实时 CPU 可设为 100 以节省功耗。
  • RCU 与 NO_HZ_FULL:全无滴答 CPU 必须配置 RCU_NOCB_CPU,否则 RCU 的周期性处理会破坏无滴答状态。
最佳实践:对于混合负载系统(同时运行实时任务和普通任务),建议采用“CPU 分区”策略:将实时任务绑定到启用了 nohz_full 和 isolcpus 的专用 CPU 核心上,而将内核管理、中断处理和普通任务放在其他 CPU 上。这样既能保证实时任务的低延迟,又不牺牲系统的整体吞吐量。