Linux 进程优先级
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 进程优先级概述
进程的优先级值越小,表示进程的优先级越高,系统将为其分配更多的 CPU 资源。
2.1 普通进程的优先级
普通进程,在目前绝大多数 Linux 系统上,是指通过 CFS(Completely Fair Scheduler) 调度类别 fair_sched_class 的进程。
普通进程的优先级,其数值位于半开半闭区间为 [100, 140):
/* include/linux/sched/prio.h */
#define MAX_NICE 19
#define MIN_NICE -20
#define NICE_WIDTH (MAX_NICE - MIN_NICE + 1) /* 19 - (-20) + 1 = 40 */
...
/*
* Priority of a process goes from 0..MAX_PRIO-1, valid RT
* priority is 0..MAX_RT_PRIO-1, and SCHED_NORMAL/SCHED_BATCH
* tasks are in the range MAX_RT_PRIO..MAX_PRIO-1. Priority
* values are inverted: lower p->prio value means higher priority.
*
* The MAX_USER_RT_PRIO value allows the actual maximum
* RT priority to be separate from the value exported to
* user-space. This allows kernel threads to set their
* priority to a value higher than any user task. Note:
* MAX_RT_PRIO must not be smaller than MAX_USER_RT_PRIO.
*/
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO /* 100 */
#define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) /* 100 + 40 = 140 */
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) /* 100 + 40 / 2 = 120 */
对于普通进程的优先级,读者更熟悉的叫法可能是 nice 值,其数值区间 [-20, 19],普通进程 nice 值和优先级的相互转化公式如下:
/* include/linux/sched/prio.h */
/*
* Convert user-nice values [ -20 ... 0 ... 19 ]
* to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
* and back.
*/
#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO) /* nice => prio: [-20, 19] => [100, 139] */
#define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO) /* prio => nice: [100, 139] => [-20, 19] */
2.2 实时进程的优先级
对于大多数普通进程,通过 CFS 调度器能满足业务需求;但对于一些对响应速度有要求的、时间敏感的进程,使用 CFS 调度器可能无法满足需求,为满足这类业务需求,Linux 在系统中了加入实时调度器类别 rt_sched_class 。
实时调度器调度的进程,其优先级数值位于区间 [0, 99],下面的代码注释非常明确地说明这一点:
/*
* Priority of a process goes from 0..MAX_PRIO-1, valid RT
* priority is 0..MAX_RT_PRIO-1, and SCHED_NORMAL/SCHED_BATCH
* tasks are in the range MAX_RT_PRIO..MAX_PRIO-1. Priority
* values are inverted: lower p->prio value means higher priority.
*
* The MAX_USER_RT_PRIO value allows the actual maximum
* RT priority to be separate from the value exported to
* user-space. This allows kernel threads to set their
* priority to a value higher than any user task. Note:
* MAX_RT_PRIO must not be smaller than MAX_USER_RT_PRIO.
*/
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO /* 100 */
3. 进程优先级初始化
Linux 内核通过下面的数据结构来管理进程的各种优先级:
struct task_struct {
...
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
...
};
上面各数据域的作用在后面将一一加以说明。
3.1 系统中第一个静态创建进程的优先级
/* include/linux/init_task.h */
#define INIT_TASK(tsk) \
{ \
... \
.prio = MAX_PRIO-20, /* 120 */ \
.static_prio = MAX_PRIO-20, /* 120 */ \
.normal_prio = MAX_PRIO-20, /* 120 */ \
.policy = SCHED_NORMAL, \
... \
}
可以看到,系统中第一个进程 init_task 的优先级为 MAX_PRIO-20,即 120,也即缺省普通进程的优先级 DEFAULT_PRIO 。
3.2 动态创建进程的优先级初始化
除了系统中第一个进程 init_task 外,其它进程都是通过动态方式创建的。动态创建进程的优先级,在创建过程中初始化:
/* kernel/fork.c */
kernel_thread() / clone() / fork() / vfork()
_do_fork()
copy_process()
...
/* 复制 当前进程 的 优先级 到 新进程 @p */
p = dup_task_struct(current, node);
...
/* Perform scheduler related setup. Assign this task to a CPU. */
/* 设定 新进程 @p 的 优先级 */
retval = sched_fork(clone_flags, p);
...
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
struct task_struct *tsk;
...
tsk = alloc_task_struct_node(node); /* 分配进程结构体 task_struct */
...
err = arch_dup_task_struct(tsk, orig); /* 复制旧进程数据到新进程,也包括优先级 */
...
return tsk;
...
}
/* kernel/sched/core.c */
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
...
/*
* Make sure we do not leak PI boosting priority to the child.
*/
/*
* 用当前进程的 current->normal_prio 重新设定新进程的 p->prio :
* 避免当前进程因为某些原因(如为解决优先级反转的优先级继承场景)而
* 临时提升的 优先级传递给子进程。
*
* prio 是一个可能会动态变化的优先级。
*/
p->prio = current->normal_prio;
...
/* 按优先级来决定进程的调度器类型 */
if (dl_prio(p->prio)) {
put_cpu();
return -EAGAIN;
} else if (rt_prio(p->prio)) {
p->sched_class = &rt_sched_class;
} else {
p->sched_class = &fair_sched_class;
}
...
}
4. 进程优先级的动态修改
进程的优先级,在其创建时进行初始化,从章节 4. 分析中了解到,进程的初始优先级,继承自发起进程创建的当前进程;在进程运行过程中,可以通过系统接口动态改变进程的优先级。动态修改进程优先级的接口分为两类,分别用于普通进程优先修改和实时进程优先级修改。
4.1 普通进程的优先级修改
普通进程优先级 范围为 [100, 139],对应 nice 区间为 [-20, 19]。普通进程优先级修改的核心接口为 set_user_nice(),按 nice 值来修改普通进程的优先级:
/* kernel/sched/core.c */
void set_user_nice(struct task_struct *p, long nice)
{
...
/*
* The RT priorities are set via sched_setscheduler(), but we still
* allow the 'normal' nice value to be set - but as expected
* it wont have any effect on scheduling until the task is
* SCHED_DEADLINE, SCHED_FIFO or SCHED_RR:
*/
/*
* 设置 DL,RT 类进程静态优先级(p->static_prio)。
* RT 优先级是通过 sched_setscheduler() 设置的,但仍然允许设置进程的
* nice 值。在进程使用 SCHED_DEADLINE、SCHED_FIFO 或 SCHED_RR 实时
* 调度策略期间,它不会对调度产生任何影响;只有进程切换为普通进程的调度
* 策略时才会对调度产生影响。
*/
if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
p->static_prio = NICE_TO_PRIO(nice);
goto out_unlock;
}
...
/*
* 对普通进程,根据新 nice 值,重新设定进程的:
* . 优先级
* . 运行时间权重
*/
p->static_prio = NICE_TO_PRIO(nice); /* 普通进程的 nice 值转为静态优先级 p->static_prio */
set_load_weight(p); /* 设置 普通进程 的 运行时间权重 */
...
p->prio = effective_prio(p);
...
out_unlock:
...
}
/*
* Calculate the current priority, i.e. the priority
* taken into account by the scheduler. This value might
* be boosted by RT tasks, or might be boosted by
* interactivity modifiers. Will be RT if the task got
* RT-boosted. If not then it returns p->normal_prio.
*/
static int effective_prio(struct task_struct *p)
{
p->normal_prio = normal_prio(p);
/*
* If we are RT tasks or we were boosted to RT priority,
* keep the priority unchanged. Otherwise, update priority
* to the normal priority:
*/
if (!rt_prio(p->prio)) /* 普通进程 */
return p->normal_prio;
return p->prio; /* RT(Real Time), DL(Deadline) 进程 */
}
/*
* Calculate the expected normal priority: i.e. priority
* without taking RT-inheritance into account. Might be
* boosted by interactivity modifiers. Changes upon fork,
* setprio syscalls, and whenever the interactivity
* estimator recalculates.
*/
/* DL, RT, 普通 进程 优先级 归一化处理 */
static inline int normal_prio(struct task_struct *p)
{
int prio;
if (task_has_dl_policy(p)) /* DL(Deadline) 进程 */
prio = MAX_DL_PRIO-1; /* DL(Deadline) 进程的优先级值为 -1 */
else if (task_has_rt_policy(p)) /* RT(Real Time) 进程 */
prio = MAX_RT_PRIO-1 - p->rt_priority;
else /* 普通进程 */
prio = __normal_prio(p);
return prio;
}
/* 普通进程的优先级和 nice 值一一对应: [-20,19] => [100,139] */
static inline int __normal_prio(struct task_struct *p)
{
return p->static_prio;
}
普通内核线程调用 set_user_nice() 修改优先级示例:
/* mm/kmemleak.c */
/* kmemleak 扫描线程修改其优先级 */
static int kmemleak_scan_thread(void *arg)
{
...
set_user_nice(current, 10);
...
}
用户空间进程可通过系统调用接口 nice(), setpriority() 来修改优先级:
/* kernel/sched/core.c */
SYSCALL_DEFINE1(nice, int, increment)
{
...
set_user_nice(current, nice);
...
}
/* kernel/sys.c */
SYSCALL_DEFINE3(setpriority, int, which, int, who, int, niceval)
{
...
switch (which) {
case PRIO_PROCESS:
if (who)
p = find_task_by_vpid(who);
else
p = current;
if (p)
error = set_one_prio(p, niceval, error);
break;
...
}
...
}
static int set_one_prio(struct task_struct *p, int niceval, int error)
{
...
set_user_nice(p, niceval);
...
}
Linux 系统还提供了命令行工具 nice, renice 来修改、查询普通进程的 nice 值。
4.2 实时进程的优先级修改
实时进程,包括 RT(Real Time)、DL(Deadline) 进程。对于 DL 进程,其优先级固定为 -1;对于 RT 进程,其优先级范围为 [0, 99]。实时进程优先级的修改核心接口为 __sched_setscheduler() :
static int __sched_setscheduler(struct task_struct *p,
const struct sched_attr *attr,
bool user, bool pi)
{
/* DL 进程的优先级固定为 -1 (即 MAX_DL_PRIO - 1,MAX_DL_PRIO 为 0) */
int newprio = dl_policy(attr->sched_policy) ? MAX_DL_PRIO - 1 :
MAX_RT_PRIO - 1 - attr->sched_priority;
...
...
rq = task_rq_lock(p, &rf);
...
__setscheduler(rq, p, attr, pi);
...
}
/* Actually do priority change: must hold pi & rq lock. */
static void __setscheduler(struct rq *rq, struct task_struct *p,
const struct sched_attr *attr, bool keep_boost)
{
__setscheduler_params(p, attr);
/*
* Keep a potential priority boosting if called from
* sched_setscheduler().
*/
p->prio = normal_prio(p);
if (keep_boost)
p->prio = rt_effective_prio(p, p->prio);
/* 按优先级选择调度器算法 */
if (dl_prio(p->prio))
p->sched_class = &dl_sched_class;
else if (rt_prio(p->prio))
p->sched_class = &rt_sched_class;
else
p->sched_class = &fair_sched_class;
}
static void __setscheduler_params(struct task_struct *p,
const struct sched_attr *attr)
{
int policy = attr->sched_policy;
...
if (dl_policy(policy)) /* DL 进程 */
__setparam_dl(p, attr);
else if (fair_policy(policy)) /* 普通进程 */
p->static_prio = NICE_TO_PRIO(attr->sched_nice); /* nice 值转静态优先级 */
/*
* __sched_setscheduler() ensures attr->sched_priority == 0 when
* !rt_policy. Always setting this ensures that things like
* getparam()/getattr() don't report silly values for !rt tasks.
*/
p->rt_priority = attr->sched_priority; /* 记录设置的 RT 进程优先级 */
p->normal_prio = normal_prio(p);
set_load_weight(p);
}
内核空间修改 RT 进程优先级示例:
/* softlockup 内核线程提升为 RT 实时进程,提高被调度的概率 */
/* kernel/watchdog.c */
static void watchdog_enable(unsigned int cpu)
{
...
watchdog_set_prio(SCHED_FIFO, MAX_RT_PRIO - 1); /* 设置为 RT 调度类 的 调度策略 SCHED_FIFO */
}
static void watchdog_set_prio(unsigned int policy, unsigned int prio)
{
struct sched_param param = { .sched_priority = prio };
sched_setscheduler(current, policy, ¶m);
}
用户空间修改 RT 进程优先级示例,如音频播放场景:
#include <sched.h>
struct sched_param sched_param;
sched_getparam(0, &sched_param);
sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
sched_setscheduler(0, SCHED_RR, &sched_param);
Linux 用户空间提供命令 chrt 修改实时进程的调度策略和优先级。
5. 进程优先级反转
假定有高、中、低三个优先级的 3 个进程,高、低优先级的两个进程共享了某个资源,且通过锁来互斥访问该资源。在某一时刻,低优先级的获取了资源锁后,被中优先级的进程给抢占了,这样中优先级的进程,比高优先级的进程获得了更多的 CPU 时间,发生了进程优先级反转。
解决进程优先级反转基本思路是:进入临界区时,临时提升低优先级进程的优先级,这样避免低优先级进程在临界区时被中优先级的进程抢占。要将低优先级进程优先级临时提升到什么程度,不同的解决方案给出了不同的答案。常见的方案有:Priority inheritance,Priority ceiling protocol,Random boosting 。
当然,既然进程优先级反转是由阻塞引起的,那么还可以通过禁用中断抢占、避免阻塞来解决。
Linux 社区公版代码的 CONFIG_RT_MUTEXES 配置功能用来解决优先级反转问题,其核心功能实现在 kernel/locking/rtmutex.c 和 kernel/sched/core.c 中。另外,在 kernel/futex.c 也穿插有相关代码。kernel/sched/core.c 中的函数 rt_mutex_adjust_prio() 就是临时提升进程优先级的核心接口。
对于优先级反转问题,除 Linux 社区公版代码外,各个厂家也私下有各自的解决方案。
对用户空间,可通过 glibc 提供的接口 pthread_mutexattr_setprotocol() 来设置优先级反转处理协议方案。
6. 进程优先级小结
struct task_struct {
...
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
...
};
prio
存储进程运行时优先级,也称为动态优先级。在大多数时候和normal_prio相同,在少数优先级临时提升场景不同于normal_prio,如前面提到优先级反转场景临时。static_prio
存储普通进程 nice 值对应的优先级,也称为静态优先级,对 RT、DL 进程没有意义。取值区间为[100, 139]。normal_prio
存储进程优先级经normal_prio()归一化处理后的值。
o 对于DL(Deadline)进程,normal_prio的值固定为 -1,表示DL进程的优先级。
o 对于RT(Real Time)进程,存储的是将rt_priority的值经公式MAX_RT_PRIO - 1 - rt_priority转换后的优先级值。取值区间为[0, 99]。
o 对于普通进程,存储的是将nice值转换后的优先级值static_prio。普通进程的normal_prio和static_prio值相同。取值区间为[100, 139]。
normal_prio() 的所谓归一化处理,是指将各类型进程的优先级,包括DL 进程、RT 进程、普通进程,统一到同一数轴上来,其区间范围为[-1, 139]:-1用于DL进程,[0, 99]用于RT进程,[100, 139]用于普通进程。rt_priority
存储用户空间设置的RT 进程的优先级,取值区间范围为[0, 99]。对用户空间而言,数值越大,表示 RT 优先级越高,为什么?normal_prio()对 RT 进程优先级归一化处理代码片段prio = MAX_RT_PRIO-1 - p->rt_priority;揭示了这一点。
用户空间可以通过 top,ps 等工具观察进程优先级。

浙公网安备 33010602011771号