进程优先级

进程优先级

关键结论

  • 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
2
3
4
5
int             prio;           // 动态优先级——调度器实际使用
int static_prio; // 静态优先级——普通任务的 nice 内核表示
int normal_prio; // 归一化优先级——由 policy 与静态参数计算
unsigned int rt_priority; // 实时优先级——仅对 SCHED_FIFO / SCHED_RR 有效
unsigned int policy; // 调度策略

这几个字段各司其职。理解它们之间的关系,是掌握 Linux 调度行为的前提。

调度策略与优先级的整体关系

内核通过 policy 字段区分任务的调度类。每种策略使用的优先级参数不同,
内核内部优先级数值也不同。

调度策略一览

1
2
3
4
5
6
7
8
9
10
11
/*
* Scheduling policies
*/
#define SCHED_NORMAL 0 // 普通,CFS 调度(也称 SCHED_OTHER)
#define SCHED_FIFO 1 // 实时 FIFO,无时间片
#define SCHED_RR 2 // 实时轮转,同优先级按时间片轮转
#define SCHED_BATCH 3 // 普通批处理,不强调交互
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE 5 // 极低优先级,系统空闲时才运行
#define SCHED_DEADLINE 6 // Deadline 实时,按截止期限调度
#define SCHED_EXT 7 // 可扩展(sched_ext,BPF 调度器)

策略宏定义于 include/uapi/linux/sched.h

策略 → 优先级依据 → 内核内部优先级映射

策略 类型 调度类 优先级依据 内核 prio 范围 说明
SCHED_DEADLINE Deadline 实时 dl_sched_class runtime / deadline / period -1 最高优先级;不使用 nicert_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
2
3
4
5
6
7
8
9
10
11
12
#define MAX_NICE         19
#define MIN_NICE -20
#define NICE_WIDTH (MAX_NICE - MIN_NICE + 1) // = 40

#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO // = 100

#define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) // = 140
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) // = 120

#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO)
#define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO)

展开即:

1
static_prio = nice + 120

示例:

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_FIFOSCHED_RR 生效。用户空间取值范围为 1 到 99
sched(7) man-page),
0 为系统保留。值越大,实时优先级越高。

可通过 sched_get_priority_min(2) / sched_get_priority_max(2) 查询系统支持范围:

1
2
3
4
// SCHED_FIFO 返回 1;SCHED_RR 返回 1
int min = sched_get_priority_min(SCHED_FIFO);
// SCHED_FIFO 返回 99;SCHED_RR 返回 99
int max = sched_get_priority_max(SCHED_FIFO);

兼容性提醒: POSIX.1 仅要求实现提供至少 32 个实时优先级级别。
可移植程序应通过上述系统调用动态获取实际范围,而非硬编码 1–99。

实时任务的 rt_priority 在 CFS 调度器中不参与 vruntime 计算——它的优先级
直接来自调度类的排序(RT > CFS),而非权重分配。

归一化优先级 normal_prio

normal_prio 将不同策略的优先级参数统一映射到内核内部优先级数值。
源码位于 kernel/sched/core.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static inline int __normal_prio(int policy, int rt_prio, int nice)
{
int prio;

if (dl_policy(policy))
prio = MAX_DL_PRIO - 1; // = -1
else if (rt_policy(policy))
prio = MAX_RT_PRIO - 1 - rt_prio; // = 99 - rt_prio
else
prio = NICE_TO_PRIO(nice); // = nice + 120

return prio;
}

static inline int normal_prio(struct task_struct *p)
{
return __normal_prio(p->policy, p->rt_priority,
PRIO_TO_NICE(p->static_prio));
}

映射规则:

策略 输入参数 计算公式 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
2
3
4
5
// SCHED_FIFO, rt_priority=99 → normal_prio = 99-99 = 0  (最高 RT)
// SCHED_FIFO, rt_priority=1 → normal_prio = 99-1 = 98 (最低 RT)
// SCHED_NORMAL, nice=-10 → normal_prio = 110 (高于默认)
// SCHED_NORMAL, nice=5 → normal_prio = 125 (低于默认)
// SCHED_DEADLINE → normal_prio = -1 (高于一切)

normal_prio 是任务”本来的”优先级——不含任何临时调整。
它由策略和静态参数计算得到,不随运行时状态变化。

动态优先级 prio

prio 是调度器在做调度决策时实际使用的优先级。它在大多数时候等于 normal_prio
但在特定情况下会被临时修改。设置 prio 的核心函数是 effective_prio()
kernel/sched/core.c):

1
2
3
4
5
6
7
8
9
10
11
static int effective_prio(struct task_struct *p)
{
p->normal_prio = normal_prio(p);
/*
* 如果当前动态优先级已经处于 RT 范围(说明正在享受
* 优先级继承带来的临时提升),则保留之;否则退回 normal_prio。
*/
if (!rt_prio(p->prio))
return p->normal_prio;
return p->prio;
}

一般调用路径:

1
p->prio = effective_prio(p);

什么时候 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
2
3
PI 提升前:普通任务 prio = 120 = normal_prio
PI 提升后:普通任务 prio = 19 ≠ normal_prio (120)
——任务获得了临时的 RT 级调度优先级

Linux 的 PI 实现细节见内核文档
Documentation/locking/rt-mutex.rst
kernel/locking/rtmutex.c

优先级转换全景

下图将 nicert_prioritypolicy 到内核内部 prio 的完整映射关系汇总在一处:

图中三条色带从上到下对应三种优先级域。Deadline 任务始终优先于 RT,
RT 始终优先于普通。
绿色区域的普通任务中,static_prioprio
通常相等,但在优先级继承发生时 prio 会临时上浮到 RT 区域。

动态优先级的运行时变化

除了 PI,还有两种常见情况下 prio 会发生变化:

  1. nice(2) 系统调用:修改 static_prio 后,内核重新计算
    normal_prio 并调用 effective_prio() 更新 prio

  2. sched_setscheduler(2):改变 policy 和/或 rt_priority 后,
    normal_prio() 重新计算,prio 随之更新。

以下是一个 SCHED_FIFO 的简单示例(仅示意,省略错误处理):

1
2
3
4
5
6
7
8
9
10
#include <sched.h>
#include <stdio.h>

struct sched_param param = { .sched_priority = 80 };
sched_setscheduler(0, SCHED_FIFO, &param);
// 此后:
// policy = SCHED_FIFO
// rt_priority = 80
// normal_prio = 99 - 80 = 19
// prio = 19(在 RT 范围内)

常见问题

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_RTTIMEgetrlimit(2)):
    限制实时任务连续运行时间,超出后发送 SIGXCPU 信号。
  • 调度器节流(RT throttling)/proc/sys/kernel/sched_rt_runtime_us
    限制每周期 RT 任务的总运行时间(默认 RT 占 95%,留 5% 给普通任务)。

3. SCHED_BATCHSCHED_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_IDLEnice=19 哪个优先级更低?

SCHED_IDLEnice=19 更低。SCHED_IDLE 使用特殊的 Idle 权重,
仅在没有其他可运行任务时才获得 CPU 时间。nice=19 的普通任务
至少还会在 CFS 中获得极小的时间片。直观排序如下:

1
Deadline > RT(0-98) > nice=-20(prio=100) > ... > nice=19(prio=139) > SCHED_IDLE

总结

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 的数值比较才决定谁先运行。

优先级模型


参考资料


进程优先级
http://example.com/2026/07/12/Linux Kernel/process_management/进程优先级/
作者
CJ1018
发布于
2026年7月12日
许可协议