进程优先级
进程优先级
关键结论
- Linux 内核使用数值 小即高优先级 的规则:Deadline(-1)> 实时(0–98)> 普通(100–139)。
static_prio = nice + 120,对普通任务生效;实时任务只看rt_priority(1–99)。- 调度器最终使用的是
p->prio(动态优先级),它在优先级继承(PI)场景中可能与normal_prio不同。normal_prio根据 policy 统一映射到内核内部优先级:Deadline → -1,RT →100 - 1 - rt_prio,
普通 →nice + 120。
从 task_struct 看优先级字段
Linux 内核在 task_struct 中维护了多个优先级相关字段(include/linux/sched.h):
1 | |
这几个字段各司其职。理解它们之间的关系,是掌握 Linux 调度行为的前提。
调度策略与优先级的整体关系
内核通过 policy 字段区分任务的调度类。每种策略使用的优先级参数不同,
内核内部优先级数值也不同。
调度策略一览
1 | |
策略宏定义于 include/uapi/linux/sched.h。
策略 → 优先级依据 → 内核内部优先级映射
| 策略 | 类型 | 调度类 | 优先级依据 | 内核 prio 范围 | 说明 |
|---|---|---|---|---|---|
SCHED_DEADLINE |
Deadline 实时 | dl_sched_class |
runtime / deadline / period |
-1 | 最高优先级;不使用 nice 或 rt_priority |
SCHED_FIFO |
实时 | rt_sched_class |
rt_priority(1–99) |
0–98 | 先进先出;运行至阻塞/让出/被抢占 |
SCHED_RR |
实时 | rt_sched_class |
rt_priority(1–99) |
0–98 | 实时轮转;同优先级线程按时间片轮流 |
SCHED_NORMAL |
普通 | fair_sched_class |
nice(-20–+19) |
100–139 | 默认策略;权重由 nice 决定 |
SCHED_BATCH |
普通 | fair_sched_class |
nice(-20–+19) |
100–139 | 面向批处理;唤醒逻辑与 NORMAL 不同 |
SCHED_IDLE |
普通 | fair_sched_class |
Idle 权重 | 100–139 | 极低优先级;仅在系统空闲时运行 |
SCHED_EXT |
可扩展 | ext_sched_class |
由 BPF 定义 | BPF 决定 | sched_ext 可编程调度(Linux 6.12+) |
核心规则: 内核内部优先级数值越小,调度优先级越高。
在任何 CPU 上,一个可运行的 Deadline 或 RT 任务会立即抢占普通任务。
三类任务的核心行为对比
| 维度 | 普通任务(CFS) | FIFO/RR 实时任务 | Deadline 任务 |
|---|---|---|---|
| 优先级参数 | nice(-20–+19) |
rt_priority(1–99) |
runtime / deadline / period |
| 内核 prio | 100–139 | 0–98 | -1 |
| 时间片 | 有(CFS 按权重动态分配) | FIFO 无、RR 有固定时间片 | 按 runtime 预算 |
| 抢占规则 | 被 RT/DL 抢占;CFS 内部按 vruntime | 更高 rt_priority 抢占;同优先级 FIFO 不抢占 |
Earliest Deadline First (EDF) |
| 用户态设置 | nice(2) / setpriority(2) |
sched_setscheduler(2) |
sched_setattr(2) |
| 适用场景 | 通用应用程序 | 硬实时响应(IRQ 线程、音频) | 周期性实时任务(视频编解码) |
静态优先级 static_prio
static_prio 是普通线程 nice 值的内核内部表示。nice 值在 -20 到 +19 之间,
nice 越大表示线程越”友好”——越容易让出 CPU。
内核内部普通优先级范围是 100 到 139(值越大,调度优先级越低)。
转换关系定义在 include/linux/sched/prio.h:
1 | |
展开即:
1 | |
示例:
| nice 值 | static_prio |
含义 |
|---|---|---|
| -20 | 100 | 普通任务中的最高调度优先级 |
| -10 | 110 | 高于默认 |
| 0 | 120 (DEFAULT_PRIO) |
默认优先级 |
| 10 | 130 | 低于默认 |
| 19 | 139 | 普通任务中的最低调度优先级 |
static_prio在进程生命周期内通常不变,仅可通过nice(2)/setpriority(2)系统调用修改。
实时任务不使用static_prio(其值在 RT 任务中无调度意义)。
实时优先级 rt_priority
rt_priority 仅对 SCHED_FIFO 和 SCHED_RR 生效。用户空间取值范围为 1 到 99(
sched(7) man-page),
0 为系统保留。值越大,实时优先级越高。
可通过 sched_get_priority_min(2) / sched_get_priority_max(2) 查询系统支持范围:
1 | |
兼容性提醒: POSIX.1 仅要求实现提供至少 32 个实时优先级级别。
可移植程序应通过上述系统调用动态获取实际范围,而非硬编码 1–99。
实时任务的 rt_priority 在 CFS 调度器中不参与 vruntime 计算——它的优先级
直接来自调度类的排序(RT > CFS),而非权重分配。
归一化优先级 normal_prio
normal_prio 将不同策略的优先级参数统一映射到内核内部优先级数值。
源码位于 kernel/sched/core.c:
1 | |
映射规则:
| 策略 | 输入参数 | 计算公式 | normal_prio 范围 |
|---|---|---|---|
SCHED_DEADLINE |
— | MAX_DL_PRIO - 1 |
-1 |
SCHED_FIFO / SCHED_RR |
rt_priority (1–99) |
99 - rt_priority |
0(最高 RT)– 98(最低 RT) |
SCHED_NORMAL / SCHED_BATCH / SCHED_IDLE |
nice (-20–+19) |
nice + 120 |
100(nice=-20)– 139(nice=+19) |
计算示例:
1 | |
normal_prio是任务”本来的”优先级——不含任何临时调整。
它由策略和静态参数计算得到,不随运行时状态变化。
动态优先级 prio
prio 是调度器在做调度决策时实际使用的优先级。它在大多数时候等于 normal_prio,
但在特定情况下会被临时修改。设置 prio 的核心函数是 effective_prio()(kernel/sched/core.c):
1 | |
一般调用路径:
1 | |
什么时候 prio ≠ normal_prio?
最常见的情况是 优先级继承(Priority Inheritance, PI)。
场景: 一个高优先级的 RT 任务(rt_priority=80)尝试获取互斥锁,但该锁被一个
低优先级的普通任务(nice=0, prio=120)持有。如果内核不干预,RT 任务会被普通任务
无限期阻塞——这就是著名的优先级反转问题。
内核的处理: rt_mutex 实现会临时提升锁持有者的 prio 到等待者中最高
RT 优先级的水平。在上述场景中,普通任务的 prio 会被临时提升到 RT 范围(例如 prio=19),
使其能尽快执行完临界区并释放锁。effective_prio() 中的 rt_prio(p->prio) 检查
正是用来保留这种临时提升的:如果 prio 已经在 RT 范围,就不要降回 normal_prio。
1 | |
Linux 的 PI 实现细节见内核文档
Documentation/locking/rt-mutex.rst和kernel/locking/rtmutex.c。
优先级转换全景
下图将 nice、rt_priority、policy 到内核内部 prio 的完整映射关系汇总在一处:

图中三条色带从上到下对应三种优先级域。Deadline 任务始终优先于 RT,
RT 始终优先于普通。 绿色区域的普通任务中,static_prio 和 prio
通常相等,但在优先级继承发生时 prio 会临时上浮到 RT 区域。
动态优先级的运行时变化
除了 PI,还有两种常见情况下 prio 会发生变化:
nice(2)系统调用:修改static_prio后,内核重新计算normal_prio并调用effective_prio()更新prio。sched_setscheduler(2):改变policy和/或rt_priority后,normal_prio()重新计算,prio随之更新。
以下是一个 SCHED_FIFO 的简单示例(仅示意,省略错误处理):
1 | |
常见问题
1. 为什么 rt_priority 只到 99 而不是更大?
MAX_USER_RT_PRIO 定义为 100,用户可用范围为 1–99(0 保留给内核)。
这个值自 Linux 早期版本即固定,内部优先级空间(0–139)总共 140 个级别
(MAX_PRIO = MAX_RT_PRIO + NICE_WIDTH = 100 + 40),从设计上区分了
RT 和普通任务的优先级区间。
2. 实时任务会不会饿死普通任务?
会。高 rt_priority 的 CPU 密集型实时任务会持续占用 CPU,
导致所有普通任务无法运行。这是设计的取舍——实时调度策略假定
实时任务的行为是可控且受限的。在实践中有两个保护机制:
RLIMIT_RTTIME(getrlimit(2)):
限制实时任务连续运行时间,超出后发送SIGXCPU信号。- 调度器节流(RT throttling):
/proc/sys/kernel/sched_rt_runtime_us
限制每周期 RT 任务的总运行时间(默认 RT 占 95%,留 5% 给普通任务)。
3. SCHED_BATCH 和 SCHED_NORMAL 有什么区别?
两者同属 fair_sched_class,使用相同的 nice 权重体系。区别在于唤醒行为:SCHED_BATCH 任务的唤醒延迟更长,调度器会认为它们对交互响应不敏感,
从而减少抢占和迁移开销。适合编译任务、日志处理等批处理负载。
4. Deadline 调度不使用 rt_priority,那优先级怎么确定?
SCHED_DEADLINE 通过三个参数控制调度:
sched_runtime:每个周期内的最大执行时间sched_deadline:任务必须在此相对截止期限内完成sched_period:重复周期
调度器使用 Earliest Deadline First (EDF) 算法:截止期限越近,
调度优先级越高。约束条件为 runtime ≤ deadline ≤ period。
详见内核文档 Documentation/scheduler/sched-deadline.rst。
5. SCHED_IDLE 和 nice=19 哪个优先级更低?
SCHED_IDLE 比 nice=19 更低。SCHED_IDLE 使用特殊的 Idle 权重,
仅在没有其他可运行任务时才获得 CPU 时间。nice=19 的普通任务
至少还会在 CFS 中获得极小的时间片。直观排序如下:
1 | |
总结
Linux 的优先级体系由四个核心字段构成一个层次分明的整体:
static_prio:面向普通任务,由nice值线性映射(nice + 120),范围 100–139。rt_priority:面向 RT 任务,用户可设 1–99,大即高优先级。normal_prio:将policy与上述静态参数统一定价到内核内部优先级(-1 到 139)。prio:调度器实际使用的动态优先级。通常等于normal_prio,但在优先级继承等
场景下会临时偏离——这是内核处理优先级反转的核心机制。
在同一个 CPU 上,调度类的选择优先于内部优先级数值:Deadline 调度类先于 RT 调度类,
RT 调度类先于 CFS 调度类。只有在调度类内部,prio 的数值比较才决定谁先运行。

参考资料
include/linux/sched/prio.h— 优先级常量和转换宏(MAX_RT_PRIO,MAX_PRIO,DEFAULT_PRIO,NICE_TO_PRIO等)kernel/sched/core.c—effective_prio(),normal_prio(),__normal_prio()实现include/uapi/linux/sched.h— 调度策略宏定义(SCHED_NORMAL,SCHED_FIFO等)sched(7)man-page — 调度策略和优先级 API 的用户空间文档sched_setattr(2)man-page —SCHED_DEADLINE参数说明Documentation/locking/rt-mutex.rst— 优先级继承设计文档Documentation/scheduler/sched-deadline.rst— Deadline 调度类文档- 性能优化基本功(一):掌握 Linux 和 Android 线程优先级 — 应用视角的优先级解读
- Linux 内核源码 (v6.14.3) — 所有源码引用均在 Bootlin Elixir 可查