【Linux操作系统】进程篇(五):从浅入深讲懂进程优先级
Hello,大家好。
整理 Linux 进程这一章时,我发现“进程优先级”特别容易被学成几个孤立的名词:PRI、NI、nice、renice,看上去都不难,背下来也能应付几道题。
可真正要回答“为什么要有优先级”“为什么改 NI 就能影响 PRI”“为什么 Nice 只能从 -20 到 19”“为什么普通用户不能随便把优先级调高”,只背命令就不够了。
这篇博客我想按照老师上课的顺序,把这条线重新走一遍:
CPU 不够用 -> 进程只能排队 -> 排队要有规则 -> 规则体现为优先级 -> 优先级由 PRI 和 NI 共同决定 -> 最后才能安全地修改。
顺着这个顺序理解,优先级就不再是一组需要硬记的数字,而是一套很自然的设计。
零、先补上前面最关键的知识
1. 竞争、独立、并发、并行
讲优先级之前,要先知道进程在系统里到底处在什么环境。
竞争性:系统里有很多进程,但 CPU 核心数量有限,大家都要抢 CPU 和其他资源。因为会竞争,才需要规定谁先使用、谁后使用。
独立性:多个进程运行起来以后,各自的工作应该互不干扰。A 进程算自己的数据,B 进程处理自己的任务,不能随便把对方的内存和状态搅乱。
并行:多个进程在多个 CPU 核心上同时运行。两个核心在同一时刻各执行一个进程,这才是真正意义上的“同时进行”。
并发:系统可能只有一个 CPU 核心,但通过快速切换,让多个进程在一段时间内都得到推进。微观上,CPU 同一个时刻仍然只执行一个进程;宏观上,大家看起来像在一起运行。
这里最值得拿来打比方的是一个咖啡店。
2. 一杯咖啡理解并发与并行
店里只有一台咖啡机,就相当于只有一个 CPU 核心。
顾客排了两条队。咖啡师先给 A 队做一杯,再转头给 B 队做一杯,不停地高速切换。任何一瞬间,咖啡师都只能服务一个人,但过了一会儿再看,两条队都往前走了。这就是并发。
如果店里又装了一台咖啡机,两位咖啡师在同一个时刻各自服务一位顾客,两条队是实打实地一起推进。这就是并行。
所以要记住:
- 并发关注“在一段时间内,多个任务都获得了推进”;
- 并行关注“在同一时刻,多个任务真的同时执行”;
- 单核也能并发,但单核不能实现真正意义上的并行。
想看看自己的机器有几个逻辑 CPU,可以执行:
cat /proc/cpuinfo
这里的路径是 /proc/cpuinfo,不是一个叫 etc/cpuinfo 的文件。Linux 的 /proc 是一块很特殊的虚拟文件系统,内核会通过它把一部分系统状态暴露给用户。
3. 时间片:让多个进程轮流上桌
早期计算机更像单任务批处理:一个程序不跑完,后面的程序可能只能等。现代 Linux 属于分时操作系统,它会为运行队列里的进程安排一段很小的 CPU 连续使用时间,这就是时间片。
时间片通常只有几毫秒到几十毫秒。一个进程用完以后,并不是一直霸占 CPU,而是被操作系统放回队列,换另一个进程上来执行,这个过程叫进程切换。
于是就有了一个很有意思的现象:
- 微观串行:在极短的瞬间,一个 CPU 核心只执行一个进程;
- 宏观并发:因为切换得足够快,人看到的是多个程序都在响应。
这和我们一边聊天一边刷网页的感觉一样。不是 CPU 真的一心多用,而是它把时间切得很碎,快速轮流照顾每个任务。
有了时间片,所有进程至少都有机会运行。但只要“有机会”还不够,系统还得决定:每一轮里,谁先运行?谁的时间片可以多一些?谁应该等一等?
这就轮到进程优先级登场了。
一、进程优先级到底在解决什么问题
1. 本质还是 CPU 太稀缺
一个 CPU 核心在同一时刻只能执行一个进程。
当很多进程同时处于可运行状态时,它们就得在运行队列里排队。优先级的高低,作用就是决定:
排队时,谁更有资格先获得 CPU。
比如系统要处理键盘鼠标响应、播放音视频、进行后台磁盘扫描,也有人在跑编译任务。它们都重要,但着急程度不一样。
如果把所有进程完全一视同仁,后台编译可能占着 CPU 不放,鼠标移动和窗口切换就开始卡;如果永远优先响应用户操作,一些后台任务又可能长期得不到推进。
所以优先级不是“给某个进程开特权”,而是调度器在资源有限时维持效率和体验的一种排队规则。
2. 优先级不是权限
这两个概念初学很容易混,其实问的根本不是同一个问题。
权限决定的是:你能不能做某件事。
比如普通用户没有权限读取 root 才能访问的敏感文件,这是权限问题。
优先级决定的是:你已经有资格做事以后,先做还是后做。
比如多个进程都能运行,谁先拿 CPU,这是调度问题。
一句话区分:
权限管“能不能”,优先级管“先不先”。
当然,这两件事后面会发生联系,因为“谁能提高自己的调度优先级”本身又受到权限控制。
3. UID:内核眼里大家不是名字,而是编号
在 Linux 内核里,识别用户身份主要看的不是“Alice”“Bob”这样的用户名,而是数字形式的 UID,User ID。
用户创建进程时,内核会在该进程的控制块里记录它属于哪个 UID。系统里的每一个进程,底层都能对应到一个数字身份。
常见规则是:
root的 UID 是0;- 普通用户的 UID 通常从
1000左右开始往后分配。
可以用下面的命令查看当前用户的数字 UID:
id -u
也可以在 ps 输出里观察进程拥有者。执行:
ps -l
输出中的 UID 一列,就是这个进程背后的数字身份。
有时候 ls -l 显示的是用户名,那只是程序替我们把 UID 翻译成了更容易读的字符串。加上 -n 以数字形式显示,就能看到更接近内核视角的 UID:
ls -ln
那么,前面讲文件访问,为什么突然插进 UID?
把这条链捋顺就通了:
- 真正访问文件的是进程;
- 进程有没有资格访问,要看它的权限;
- 权限又来自进程的用户身份;
- 用户在系统里对应 UID;
- 所以内核最终能落到一个明确的数字上做判断。
不是“用户名在访问文件”,而是“带着某个 UID 的进程在访问文件”。这样内核处理起来既快又明确。
4. 合理的优先级有什么好处
优先级设计得好,至少有两个直接好处。
第一,改善系统性能。关键的系统服务、实时音视频这一类对延迟敏感的任务,如果能更快得到 CPU,就不容易卡顿。
第二,改善用户体验。用户正在操作的窗口、键盘和鼠标响应,应该比不着急的后台任务更快被照顾到,系统才会显得跟手。
在课上的经典 O(1) 调度模型中,优先级高的进程往往能获得更多、更长的时间片;优先级低的进程得到的时间片相对少一些。
这里先埋一个点:优先级可以影响资源分配,但绝不能让人想调多高就调多高。否则高优先级进程一旦霸占 CPU,后面的进程就会出问题。
二、在 Linux 里怎么看优先级
1. ps -l 先看进程清单
执行:
ps -l
你会看到一组进程信息。常见表头类似:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 3214 3200 0 80 0 - 29115 do_wai pts/0 00:00:00 bash
0 R 1000 3521 3214 0 80 0 - 38312 - pts/0 00:00:00 ps
字段很多,这一节真正要盯住的是两个:
PRI:Priority,最终体现出来的调度优先级;NI:Nice,进程的“谦让值”。
2. PRI:最终优先级
PRI 可以理解成调度器最后拿来比较的那个优先级指标。
在这套经典 Linux 模型里,记住一句话:
PRI 的数字越小,优先级越高;数字越大,优先级越低。
这一点和很多人的直觉相反。不是“数值大就更强”,而是“排在越前面的号码越小”。
3. NI:进程愿不愿意“谦让”
NI 来自单词 nice,可以理解成一个人有多“客气”。
- Nice 值为负,说明进程不太愿意让资源,想要更多 CPU;
- Nice 值为正,说明进程比较谦让,愿意把机会让给别人;
- Nice 值为 0,就是普通状态。
因为 NI 是对优先级的修正量,所以完全不用每次重新计算一个历史优先级。你只需要说“让这个进程多谦让一点”或者“让它少谦让一点”,系统再基于固定基准换算即可。
这也是“基准值 + 修正值”这种设计的方便之处:修改的时候只关心这次想让进程往哪个方向挪,不必追踪一个复杂的历史状态。
三、PRI 和 NI 到底怎么换算
1. 核心公式
在课上的经典 Linux 普通进程模型中,公式非常直接:
PRI(最终) = PRI(基准) + NI
普通进程的基准 PRI 通常取:
80
所以:
PRI = 80 + NI
这里讨论的是课堂上引用的经典 Linux 普通进程调度模型。不同内核版本、不同调度器对内部优先级和 ps 显示的 PRI 可能采用不同映射,所以做实验时不要只拿某一个数字机械套公式;但在理解 PRI 与 Nice 的关系、以及完成本章题目时,这条“基准值 + 修正值”的主线最重要。
2. 带几组数字就明白了
| NI | 计算 | PRI | 含义 |
|---|---|---|---|
-20 | 80 + (-20) | 60 | 最不谦让,优先级最高 |
-10 | 80 + (-10) | 70 | 比较强势,优先级较高 |
0 | 80 + 0 | 80 | 普通状态 |
10 | 80 + 10 | 90 | 比较谦让,优先级较低 |
19 | 80 + 19 | 99 | 最谦让,优先级最低 |
看第一行:NI 变小,PRI 也跟着变小,而 PRI 越小优先级越高。
看最后一行:NI 变大,PRI 也跟着变大,所以优先级更低。
可以记成一句顺口溜:
Nice 越小越霸道,PRI 越小越优先;
Nice 越大越谦让,PRI 越大越靠后。
这里的“霸道”和“谦让”只是帮助理解,不是一个进程真有什么情绪。本质上,它们都只是调度时使用的相对权重。
四、为什么 Nice 只能取 -20 到 19?
1. 先记住范围
Nice 值的范围是:
[-20, 19]
从 -20 到 19,一共正好 40 个整数级别。
配合前面基准值 80,最终 PRI 的范围就是:
60 到 99
2. 范围限制不是为了“少给几个数字”
真正的原因有两个层面:公平和防止饥饿。
操作系统不能只看性能。它还需要尽量让每个进程都有机会向前推进。
如果允许用户无限降低 Nice 值,高优先级进程就可以一直抢占 CPU。后台任务、维护进程、普通用户程序可能长期排不到前面,时间一长就形成进程饥饿。
这就像排队办事。给急事留一个优先通道可以,但如果有人能一直插队,而且没有上限,普通队列就永远动不了。
所以 Nice 必须有一个有限的区间。它允许调度倾斜,但不允许某个进程把整个系统吃到一点不剩。
这里还要分清:
高优先级是一种“更容易先获得 CPU”的相对优势,不是“只要我在跑,别人永远不能跑”的绝对垄断。
3.新进程加进来的流程

五、怎么调整进程优先级
1. 优先级在启动前后都能改
Linux 提供了两种最典型的调整时机:
- 进程启动前,用
nice指定初始 Nice 值; - 进程运行中,用
renice动态调整 Nice 值。
先讲一个很重要的区别:
nice管“出生时”的优先级,renice管“出生后”的优先级。
2. nice:启动时就决定谦让程度
比如有一个编译任务,不急,但会占用 CPU,我们可以让它更谦让一些:
nice -n 10 g++ main.cpp -o main
这里的 -n 10 表示把 Nice 值设为 10。它比普通状态更谦让,因此优先级会低一些。
如果不写 -n,很多 nice 实现会默认把 Nice 值调整为 10:
nice g++ main.cpp -o main
不过写命令时最好把数值明确写出来,这样自己看得清楚,别人也不会猜错意图。
3. renice:进程跑起来以后也能改
假设一个正在运行的进程 PID 是 1234,想把它的 Nice 值改成 5:
renice -n 5 -p 1234
如果想把 Nice 值降到 -5,也就是提高它的调度优先级,就需要 root 权限:
sudo renice -n -5 -p 1234
修改后可以马上查看结果:
ps -o pid,ni,pri,cmd -p 1234
这样就能看到 PID 对应的 NI 是否变了,以及 PRI 是否按照公式跟着变化。
4. top 里也能直接改
如果不喜欢敲一长串参数,也可以在 top 界面里改。
- 执行
top; - 按
r,进入 renice 模式; - 输入目标进程的 PID;
- 输入新的 Nice 值;
- 回车确认。
平时调试时这种方式很直观,因为它能一边看 CPU 使用情况,一边调整某个进程。
5. 用系统调用操作
如果要在程序里改优先级,可以使用下面两个系统调用:
#include <sys/time.h>
#include <sys/resource.h>
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);
它们的作用从名字就能看出来:
getpriority:读取优先级;setpriority:设置优先级。
which 决定操作对象是什么类别,比如某个进程、进程组或者某个用户;who 指定具体对象;prio 是目标 Nice 值。
命令行工具本质上也是通过系统调用完成任务。我们把底层接口和上层命令对应起来,就不会觉得它们是两套毫不相关的知识。
六、权限限制:为什么不能想提多高就提多高
调整 Nice 值时,权限规则是很容易考、也很容易踩坑的一点。
普通用户可以做的事情是增加 Nice 值,也就是让自己的进程更“谦让”,把调度机会让出去。
但普通用户不能随便把 Nice 值降到比当前更低,更不能随意设成负数,因为这等于提高自己在系统中的调度优先级。若所有普通进程都能这么做,优先级机制就失去意义,也容易被用来抢占系统资源。
| 操作 | 普通用户 | root |
|---|---|---|
| 提高 Nice,让进程更谦让 | 通常允许 | 允许 |
| 降低 Nice,让进程更强势 | 受限制 | 可以 |
| 设置负 Nice | 通常不允许 | 可以 |
所以“提高进程优先级”和“普通用户能运行进程”是两件事。前者涉及系统公平和资源分配,于是又回到了那个区别:
权限决定你能不能操作,优先级决定操作以后先不先运行。
七、把整篇内容压缩成一张图
现在再从开头往回看,整条逻辑已经连起来了:
CPU 资源有限
|
进程之间存在竞争
|
多个进程需要排队
|
调度器用优先级决定先后
|
最终 PRI = 基准值 80 + Nice
|
Nice 越小,PRI 越小,优先级越高
Nice 越大,PRI 越大,优先级越低
|
Nice 限制在 [-20, 19],对应 PRI 为 [60, 99]
|
用 nice / renice / top / 系统调用修改
|
降 Nice 提高优先级通常需要更高权限
最后留下几个最该带走的结论:
-
并发不等于并行。并发可以是单核快速切换,多个任务在一段时间内都有推进;并行是多核在同一时刻真正同时执行。
-
时间片是并发的表象来源。从微观上看 CPU 串行执行,换得足够快,宏观上就像多个进程同时在跑。
-
优先级不解决“能不能”,只解决“谁先”。权限和优先级不是同一个维度。
-
UID 是进程身份落到内核里的数字。权限判断最终要依附到进程的 UID,而不是一个只在终端里好看的用户名。
-
PRI 数字越小,优先级越高;NI 越大,进程越谦让。 这一对方向一定要记牢。
-
经典模型中
PRI = 80 + NI,Nice 范围为[-20, 19],对应 PRI 为[60, 99]。 -
nice用于启动时设置,renice用于运行时修改;想降低 Nice、提高调度优先级,通常要借助 root 权限。 -
范围限制的根本目的是公平调度和防止进程饥饿。系统允许不同进程有不同权重,但不允许某一个进程无限度地垄断 CPU。
到这里,进程优先级这条线就完整了:它从“CPU 不够用”出发,经过“排队”和“调度”,落到 PRI、NI 和几组命令,最后又被权限和安全规则收住。
不是所有数字都要死记,真正要记住的是它们为什么要这样设计。
好了,这篇就写到这里。以后再看到 PRI 和 NI,别只把它们当成两列数字,想一想那个只有一个 CPU、却排着很多任务的系统,整篇逻辑就顺了。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)