Linux 内核调度基础:CFS 调度器原理、时间片与优先级、内核抢占点、中断上下文与进程上下文
在深入 PREEMPT_RT 的配置调优之前,必须牢固掌握 Linux 内核调度的核心基础。实时性优化的本质是减少调度延迟,而调度延迟的根源就藏在这些基础机制中。本章将拆解 CFS 调度器、时间片与优先级、内核抢占点以及上下文切换这四个关键模块。
2.1 CFS 调度器原理
完全公平调度器(CFS,Completely Fair Scheduler)是 Linux 内核默认的调度器,设计目标是让每个进程获得公平的 CPU 时间。CFS 不直接分配时间片,而是引入虚拟运行时间(vruntime)的概念。
- vruntime:进程实际运行时间经过优先级加权后的值。优先级越高(nice 值越小),vruntime 增长越慢。
- 红黑树:CFS 将所有可运行进程按 vruntime 排序,最左侧节点(vruntime 最小)就是下一个被调度的进程。
- 目标延迟:CFS 期望在一个调度周期内,所有可运行进程至少被调度一次。默认值通常为 6ms 到 24ms 之间。
- 最小粒度:进程每次运行的最小时间,默认 1ms,防止频繁上下文切换导致开销过大。
实时调优提示: 在 PREEMPT_RT 环境中,CFS 的公平性机制可能成为实时任务的干扰源。通过调整
sched_min_granularity_ns 和 sched_latency_ns 内核参数,可以压缩非实时任务的调度周期,为实时任务让路。
2.2 时间片与优先级
CFS 没有传统意义上的固定时间片,而是通过权重(weight)和调度周期动态计算运行时间。优先级通过 nice 值(-20 到 +19)体现,数值越小优先级越高。
| nice 值 | 权重 | 相对优先级 | 典型应用场景 |
|---|---|---|---|
| -20 | 88761 | 最高 | 实时音频处理、硬实时控制 |
| -10 | 11051 | 高 | 交互式应用、前台进程 |
| 0 | 1024 | 默认 | 普通用户进程 |
| 10 | 304 | 低 | 后台批处理任务 |
| 19 | 15 | 最低 | 空闲任务、低优先级计算 |
进程实际获得的运行时间计算公式:
进程运行时间 = 调度周期 × (进程权重 / 所有可运行进程权重之和)
关键点: 在 PREEMPT_RT 系统中,实时线程(SCHED_FIFO/SCHED_RR)的优先级范围是 0-99,完全独立于 CFS 的 nice 值。CFS 的优先级机制仅影响非实时进程之间的公平调度。
2.3 内核抢占点
内核抢占点是指内核代码中允许发生进程调度切换的位置。抢占点的密度和位置直接影响系统的调度延迟。Linux 内核支持三种抢占模型:
- PREEMPT_NONE:无内核抢占,只在返回用户空间时检查调度。
- PREEMPT_VOLUNTARY:在显式的抢占点(如系统调用返回、锁释放)处允许抢占。
- PREEMPT_FULL:在大多数内核代码中允许抢占,除了少数临界区。
- PREEMPT_RT:进一步将自旋锁替换为可抢占的互斥锁,几乎实现完全可抢占内核。
注意: 即使启用 PREEMPT_RT,某些不可抢占区域仍然存在,例如中断处理程序的下半部(softirq)和某些原子上下文。这些区域是调优时需要重点关注的延迟来源。
2.4 中断上下文与进程上下文
理解中断上下文和进程上下文的区别,是分析调度延迟的关键。中断处理程序运行在中断上下文中,不可阻塞、不可睡眠;而普通进程运行在进程上下文中,可以睡眠、可以被调度。
| 特性 | 中断上下文 | 进程上下文 |
|---|---|---|
| 可睡眠 | 否 | 是 |
| 可被抢占 | 否(传统) | 是(PREEMPT_RT 下) |
| 持有锁 | 自旋锁 | 互斥锁/自旋锁 |
| 调度延迟影响 | 直接阻塞高优先级任务 | 通过优先级抢占间接影响 |
| 典型处理时间 | 微秒级 | 毫秒级 |
PREEMPT_RT 核心优化: 将中断处理程序转换为内核线程(IRQ threads),使中断上下文变为可抢占的进程上下文。这样,高优先级的实时任务可以在中断处理期间抢占 CPU,大幅降低调度延迟。
2.5 调度延迟的构成
调度延迟(Scheduling Latency)是指从事件触发(如中断、定时器到期)到高优先级任务开始执行的时间间隔。它由以下部分组成:
- 中断延迟:从硬件中断发生到 CPU 开始执行中断处理程序的时间。
- 中断处理时间:中断处理程序执行的时间,包括上半部和下半部。
- 调度决策时间:调度器选择下一个运行进程的时间。
- 上下文切换时间:保存当前进程状态、加载新进程状态的时间。
在标准内核中,中断处理时间是不可抢占的,因此成为实时性的主要瓶颈。PREEMPT_RT 通过线程化中断和可抢占内核锁,将不可抢占区域压缩到最小,从而将调度延迟从毫秒级降低到微秒级。