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 抢占模型选择决策树
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 全无滴答模式的配置步骤
- 内核配置:启用
CONFIG_NO_HZ_FULL=y和CONFIG_NO_HZ_FULL_ALL=y(可选)。 - 内核启动参数:添加
nohz_full=1-3(指定 CPU 1、2、3 进入全无滴答模式)。 - 隔离 CPU:同时使用
isolcpus=1-3将指定 CPU 从内核负载均衡中隔离。 - 绑定实时任务:使用
taskset或cpuset将实时任务绑定到隔离 CPU。
重要限制:全无滴答模式要求指定 CPU 上不能运行任何常规内核线程(如 kworker、rcu_sched)。必须配合
isolcpus 和 rcu_nocbs 使用,否则内核线程的周期性唤醒会破坏无滴答效果。
4.3.3 无滴答模式对实时性的影响
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 上。这样既能保证实时任务的低延迟,又不牺牲系统的整体吞吐量。