进程调度的艺术:从概念本质到 Linux 内核实现
前言
当我们在电脑上同时打开浏览器、编辑器和音乐播放器时,操作系统如何让这些程序 “和平共处”,既不卡顿又能响应及时?这背后的核心逻辑,正是进程调度 —— 一套决定 “谁先使用 CPU、用多久、如何切换” 的精密规则。
本文将沿着 “概念→实践→内核实现” 的脉络,拆解进程调度的核心知识:从最基础的 “孤儿进程如何被系统收养”,到 “进程优先级(PRI 与 NI)如何影响 CPU 分配”;从如何通过命令查看系统进程的 UID 和状态,到深入理解 “竞争、独立、并行、并发” 这些描述进程关系的关键概念。我们还会剖析进程切换的底层逻辑 —— 为何切换需要保存上下文?切换的成本在哪里?最终,将目光聚焦于 Linux 内核的真实调度算法,解析活动队列、过期队列以及 active/expired 指针如何协作,实现高效的进程调度。
无论你是想搞懂 “为什么调整 NI 值能让程序跑得更快”,还是想理解 “Linux 如何在千差万别的进程中分配资源”,这篇文章都将带你从表象到本质,看透进程调度的 “套路” 与 “智慧”。
目录
孤儿进程
进程优先级
概念理解
查看系统进程
UID:user id
PRI and NI
调整优先级
补充:竞争,独立,并行,并发
进程切换
前提引入
切换理解
进程切换
Linux真实调度算法
结构
活动队列
过期队列
active指针和expired指针
孤儿进程
父子进程关系中,如果父进程先退出,子进程要被1号init/systemd进程领养,这个被领养的进程(子进程),叫做孤儿进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;
}
else if(id == 0){//childprintf("I am child, pid : %d\n", getpid());sleep(10);
}
else{//parentprintf("I am parent, pid: %d\n", getpid());sleep(3);exit(0);
}return 0;
}
会发现子进程2140723变成孤儿进程了,被1号领养
1号进程是谁呢?可以理解是我们的操作系统
在 Linux 系统中,1 号进程是初始化进程(init 进程),它是系统启动后创建的第一个用户态进程,是所有其他进程的 “祖先”,在进程树中处于根节点位置。
为什么要领养?
如果不被领养,就会进入僵尸状态,就会造成内存泄漏。
孤儿进程因父进程退出而被 1 号进程收养,通常在后台运行,且 若其未自行终止,确实需要通过 kill
等命令手动终止。这一特性也体现了 1 号进程的 “管家” 职责 —— 仅负责回收资源,不主动干预孤儿进程的运行逻辑,因此终止孤儿进程的主动权仍在用户手中。
进程优先级
概念理解
是什么?为什么?
优先级是进程得到CPU资源的先后顺序。优先权高的进程有优先执行权利,配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
目标资源稀缺,导致通过优先级确认谁先谁后的问题。
查看系统进程
UID:代表执行者的身份
PID : 代表这个进程的代号 PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI:代表进程的优先级,值越小越被执行,默认:80
NI:代表优先级的修正数据,nice值
进程真实的优先级=PRI(默认)+NI
UID:user id
系统怎么知道我访问文件的时候,是拥有者,所属组还是other?
进程拿着UID和文件的UID进行对比。
LInux系统中,访问任何资源,都是进程访问,进程就代表用户。
PRI and NI
优先级设立不合理,会导致优先级低的进程,长时间得不到CPU资源,进而导致:进程饥饿。
调整优先级
优先级不能随便修改,所以第二次修改我们用root,当我们将nice值设为-10时,是接着上次的90基础上进行变化吗?
显然从结果来看不是的,每次调整都是从默认值上调整的。
补充:
nice 命令:启动进程时设置优先级
用于在启动新进程时指定其 nice 值,语法:
nice [选项] [nice值] 命令
# 以默认优先级(0)启动程序(等价于直接运行 ./test)
nice ./test# 以低优先级(nice 值 10)启动程序
nice -n 10 ./test# root 用户以高优先级(nice 值 -5)启动程序
sudo nice -n -5 ./test
- 选项
-n
用于指定 nice 值(可省略,直接写nice 10 ./test
)。 - 普通用户若尝试设置负的 nice 值,会被拒绝(权限不足)。
renice 命令:调整已运行进程的优先级
用于修改正在运行的进程的 nice 值,语法:
renice [nice值] -p [进程PID]
# 先查看进程 PID(假设 test 进程的 PID 是 12345)
ps -ef | grep test# 将 PID 为 12345 的进程 nice 值改为 5
renice 5 -p 12345# root 用户将进程 nice 值改为 -3(提高优先级)
sudo renice -3 -p 12345
- 选项
-p
用于指定进程 PID(必须指定)。 - 若要调整多个进程,可同时指定多个 PID(如
renice 10 -p 123 456
)。
补充:竞争,独立,并行,并发

进程切换
前提引入
死循环进程如何运行?
一旦一个进程占有CPU,会把自己的代码跑完吗?不会,除非很短。 系统会给进程分配一个时间片的东西。
对于死循环进程,不会打死系统,不会一直占用CPU。
CPU and 寄存器
CPU里面有很多个寄存器,寄存器用于临时存放进程执行过程中的关键数据。
- 程序计数器(PC):记录下一条要执行的指令地址(进程 “当前运行位置” 的标记)。
- 通用寄存器:存放算术运算、逻辑操作的临时结果(如
a + b
的中间值)。 - 状态寄存器(FLAGS):记录指令执行后的状态(如是否溢出、是否为零)。
- 栈指针(SP):指向当前进程的栈顶位置,用于函数调用时的参数传递和返回地址存储。
这些数据是进程 “运行状态” 的直接体现 —— 离开它们,CPU 无法继续执行进程。
结论:1. 寄存器就是CPU内部的临时空间! 2. 寄存器!= 寄存器里面的数据。
切换理解
进程切换小故事
在校大学生小胡想要去当兵,首先通知学校,得去跟辅导员宝报备,当兵一年回来继续学习,需要保留学籍,辅导员给了小胡一份档案,里面记录了小胡的信息,学校一份,给了小胡一份,当兵回来之后,需要恢复学籍,然后辅导员根据上次离开的学习信息,继续给小胡安排。
学校--CPU 导员--调度器 小胡--进程 学籍--进程运行的临时数据,CPU寄存器里面的内容(当前进程上下文数据)
保留学籍--保存进程上下文数据,CPU内寄存器里面的内容,保存起来。
恢复学籍--恢复进程上下文数据,保存起来,恢复到CPU内寄存器里
当兵--进程从CPU剥离出来
现实场景(小胡当兵) | 操作系统概念(进程调度) | 核心逻辑一致点 |
---|---|---|
学校 | CPU | 是 “运行” 的核心载体(学校是学习的场所,CPU 是进程执行的硬件载体)。 |
辅导员 | 调度器(Scheduler) | 负责 “安排谁在什么时候使用核心资源”(辅导员安排学生的学习 / 当兵时间,调度器安排进程使用 CPU)。 |
小胡 | 进程(Process) | 是 “被管理的主体”(小胡是学校管理的学生,进程是操作系统管理的执行单元)。 |
学籍信息(当前课程、进度) | 进程上下文(CPU 寄存器数据、PC 指针、栈信息等) | 是 “主体当前状态的记录”(学籍记录学习进度,上下文记录进程执行到哪一步、临时数据是什么)。 |
保留学籍 | 保存上下文(Context Save) | 暂时离开核心载体时,必须保存当前状态(小胡当兵前存档,进程被切换前保存寄存器数据到 PCB)。 |
恢复学籍 | 恢复上下文(Context Restore) | 回到核心载体时,需加载之前的状态(小胡返校后接着上学,进程被调度时从 PCB 加载寄存器数据到 CPU)。 |
当兵期间 | 进程被剥夺 CPU 使用权(进入阻塞 / 就绪态) | 暂时脱离核心载体,不占用资源但状态被保留(小胡不在学校,进程不在 CPU 上运行)。 |
返校继续学习 | 进程被调度到 CPU 运行(进入运行态) | 重新获得核心载体的使用权,基于之前的状态继续推进(小胡接着上之前的课,进程接着执行之前的指令)。 |
相当于进程的一次切换
进程切换
进程切换,最核心的,就是保存和恢复当前进程的硬件上下文的数据,即CPU内寄存器的内容!!!
故引出新的问题,1. 所以当前进程要把自己的进程硬件上下文数据保存起来,保存到哪里?
保存到进程的task_struct里面,TSS --任务状态段
2. 全新的进程VS已经调度过的进程
在task_struct增加一个标记位来表示进程是否调度,全新设置为0.
Linux真实调度算法
调度和切换构成了调度器
结构
Linux2.6内核进程O(1)调度队列

活动队列
过期队列
active指针和expired指针
结束语
进程调度看似是 “分配 CPU 时间” 的简单问题,实则是操作系统对 “效率与公平” 的永恒权衡。从孤儿进程被 1 号进程收养的设计,到 PRI 与 NI 共同决定的优先级体系;从进程切换时上下文的精准保存与恢复,到 Linux 调度算法中活动队列与过期队列的巧妙轮转,每一个细节都体现了 “让系统更流畅、资源利用更高效” 的目标。
理解这些知识,不仅能帮我们在实际工作中更好地调试程序(比如通过调整优先级优化资源占用),更能让我们透过现象看本质:操作系统的每一个机制,都是对 “如何管理复杂系统” 的抽象与落地。
进程调度的故事远未结束 —— 随着多核 CPU、实时系统等场景的发展,调度算法仍在不断进化。但无论技术如何迭代,“理解进程的行为与调度规则” 始终是程序员与操作系统 “对话” 的基础。希望本文能成为你探索更深层系统知识的起点,也欢迎在评论区分享你对进程调度的思考或实践经验~