
在 Linux 系统中,当数百个进程同时争抢 CPU 资源时,为何有些进程能 “插队” 优先执行?进程切换时 CPU 如何记住上一个任务的执行状态?Linux 2.6 内核的 O (1) 调度算法为何能实现常数时间调度?本文将从进程优先级的核心概念出发,逐步拆解优先级调整机制、进程切换原理,深入剖析 Linux 2.6 内核经典的 O (1) 调度队列架构,结合实战命令带你看透 Linux 进程调度的底层逻辑。下面就让我们正式开始吧!
在 Linux 系统中,进程的数量往往远多于 CPU 核心数 —— 哪怕是普通的个人电脑,后台运行的进程也可能多达数十个。而 CPU 作为核心计算资源,同一时间只能处理有限的任务(单核 CPU 同一时刻仅能运行一个进程)。这就像高速公路上的车辆远超车道数量,必须有一套 “交通规则” 来决定谁先通行,这套规则就是进程优先级。
进程优先级的核心作用的是解决资源竞争问题:通过为不同进程分配不同的优先级,让关键进程(如系统服务、实时任务)优先获得 CPU 时间,确保系统稳定性和响应速度;而普通进程(如后台下载、日志分析)则在空闲时获得资源,避免占用过多系统资源影响整体性能。
举个生活中的例子:操作系统就像一家餐厅的调度员,CPU 是厨师。高优先级进程是 VIP 客户的加急订单,必须优先处理;普通优先级进程是普通顾客的订单,按顺序排队;低优先级进程则是可以延后制作的外卖订单,在厨师空闲时再处理。没有优先级机制,餐厅可能会因为先处理大量普通订单,导致 VIP 客户等待过久,甚至系统服务(如点餐系统)响应迟缓。
要理解进程优先级,首先要学会通过命令行查看相关参数。Linux 提供了ps和top等工具,能直观展示进程的优先级信息。
ps -l查看进程优先级详情 在终端输入ps -l命令,会输出当前终端相关进程的详细信息,其中与优先级相关的核心字段有PRI和NI:
典型输出如下:

各字段含义解析:
从输出可以看到,bash和ps进程的默认PRI都是 80,NI都是 0,这是 Linux 系统中普通进程的默认优先级配置。
ps aux查看系统所有进程优先级 如果想查看系统中所有进程的优先级(包括其他用户的进程、后台守护进程),可以使用ps aux命令,结合grep过滤特定进程:
# 查看系统中所有进程的优先级,过滤包含"nginx"的进程
ps aux | grep nginx输出示例:
root 123 0.0 0.1 4500 1200 ? Ss 08:00 0:00 nginx: master process /usr/sbin/nginx
www-data 124 0.0 0.2 4700 2400 ? S 08:00 0:01 nginx: worker process
www-data 125 0.0 0.2 4700 2380 ? S 08:00 0:00 nginx: worker process 其中,S和Ss是进程状态(睡眠状态),而优先级信息虽未直接显示PRI和NI,但可以通过后续命令进一步查看。
在 Linux 中,进程的实际执行优先级由PRI(静态优先级)和NI(Nice 值)共同决定,核心关系为:
PRI(new) = PRI(old) + NI-20到19(共 40 个级别),用于调整进程的实际优先级。举个例子:
PRI=80,NI=0,则实际优先级PRI(new)=80+0=80;NI调整为-5,则实际优先级PRI(new)=80+(-5)=75,优先级提升;NI调整为10,则实际优先级PRI(new)=80+10=90,优先级降低。 这里需要注意:NI的取值范围是固定的(-20~19),这意味着普通进程的PRI范围是60(80-20)到99(80+19)。而实时进程的优先级范围是0~99(高于普通进程的最高优先级 60),但实时进程通常用于特殊场景(如工业控制、音频处理),本文重点讨论普通进程的优先级机制。
很多初学者会混淆PRI和NI,其实二者的本质区别可以总结为:
PRI的工具,通过修改NI可以间接改变PRI。 打个比方:PRI就像学生的 “最终考试成绩”,NI就像 “加分项 / 减分项”。学生的最终成绩 = 基础成绩(默认 PRI=80)+ 加分项(NI 为负)/ 减分项(NI 为正),最终成绩决定了学生的排名(调度顺序)。
另外需要强调:NI值不能直接改变PRI的本质属性 —— 它只是 “修正值”,而不是优先级本身。例如,不能通过设置NI=-30来让普通进程的PRI低于 60,因为NI的最小值被限制在 - 20,这是 Linux 内核为了防止普通进程抢占实时进程资源而做的限制。
理解进程优先级的同时,还需要掌握四个核心概念,它们是进程调度的基础:
系统中进程数量远多于 CPU 资源,进程之间为了获取 CPU、内存等资源必然存在竞争关系。优先级机制就是为了让竞争更有序 —— 通过为进程分配不同的优先级,确保关键资源优先分配给重要进程。
例如:系统中的sshd(SSH 服务)进程优先级高于普通的bash进程,这样即使系统负载较高,用户也能正常通过 SSH 登录服务器,而不会因为bash进程占用过多 CPU 导致登录超时。
多进程运行时,各自独享系统资源(如虚拟地址空间、文件描述符),运行期间互不干扰。一个进程的崩溃不会影响其他进程的正常运行,这是操作系统稳定性的核心保障。
例如:浏览器进程崩溃后,终端进程、编辑器进程依然能正常工作,这就是进程独立性的体现。而进程的独立性,也为优先级调度提供了基础 —— 每个进程可以独立设置优先级,不会因为其他进程的优先级调整而受到影响。
多个进程在多个 CPU 核心上同时运行,这是真正意义上的 “同时执行”。例如:在 4 核 CPU 的服务器上,同时运行 4 个进程,每个进程占用一个 CPU 核心,这 4 个进程就是并行执行的。
并行的核心是 “多 CPU + 同时执行”,此时进程之间不存在 CPU 资源的竞争(每个进程独占一个核心),但仍可能竞争内存、磁盘等其他资源。
多个进程在一个 CPU 核心上,通过 “进程切换” 的方式,在一段时间内轮流执行,让多个进程都能得到推进。例如:在单核 CPU 的电脑上,同时打开浏览器、编辑器、终端,这些进程通过 CPU 快速切换,让用户感觉它们在同时运行,这就是并发。
并发的核心是 “单 CPU + 切换执行”,此时进程之间的 CPU 竞争最为激烈,优先级机制的作用也最为明显 —— 高优先级进程会获得更多的 CPU 时间片,执行效率更高。
用一个形象的比喻理解并行与并发:
而优先级,就是厨师判断哪个订单需要优先处理的依据 ——VIP 订单(高优先级进程)会被厨师优先翻炒,普通订单(低优先级进程)则在空闲时处理。

Linux 提供了多种方式调整进程优先级,包括创建进程时指定优先级、修改已运行进程的优先级,下面结合实战命令详细讲解。
top命令实时查看优先级 top命令是 Linux 系统中查看进程状态的核心工具,能实时显示进程的PRI、NI值,以及 CPU、内存占用情况:
# 运行top命令,实时查看进程状态
top 在top界面中,核心字段解析:
PRI(静态优先级); 在top界面中,按P键可以按 CPU 占用率排序(默认排序方式),按N键按 PID 排序,按M键按内存占用率排序,方便快速定位高优先级或高资源占用的进程。
ps -eo自定义查看优先级字段 如果想自定义查看进程的特定字段(如 PID、PRI、NI、命令),可以使用ps -eo命令,指定需要显示的字段:
# 查看进程的PID、PRI、NI、COMMAND字段,按PRI升序排序(优先级越高越靠前)
ps -eo pid,pri,ni,cmd --sort=pri输出示例:
PID PRI NI CMD
1 60 -20 /sbin/init splash
123 70 -10 /usr/sbin/sshd -D
456 80 0 /bin/bash
789 90 10 /usr/bin/dd if=/dev/zero of=/tmp/test bs=1M count=1000从输出可以看到:
init进程(PID=1)的PRI=60,NI=-20,是系统中优先级最高的普通进程;sshd进程(PID=123)的PRI=70,NI=-10,优先级次之;bash进程(PID=456)的PRI=80,NI=0,是默认优先级;dd进程(PID=789)的PRI=90,NI=10,优先级最低。top命令 使用top命令可以直接修改已运行进程的NI值,进而调整其PRI,操作步骤如下:
# 1. 运行top命令
top
# 2. 在top界面中,按"r"键(rename priority),进入优先级修改模式
# 3. 输入需要修改的进程PID(例如修改PID=789的dd进程)
# 4. 输入新的NI值(例如输入5,注意NI的范围是-20~19)
# 5. 按回车确认,修改完成 修改完成后,可以在top界面中看到该进程的NI值已更新,PRI值也会随之变化(PRI=new=PRI=old+NI=new)。
注意事项:
NI值调整为0~19(即降低进程优先级或保持默认),不能设置负数(提升优先级);sudo权限的用户)才能将NI值调整为-20~19(可以提升或降低优先级);NI值超出范围(如 - 25 或 20),系统会自动调整为最接近的合法值(-20 或 19)。nice命令 nice命令用于在创建新进程时指定其NI值,从而设置初始优先级。语法格式如下:
# nice [选项] [NI值] 命令
nice -n NI值 命令 示例 1:创建一个低优先级的dd进程(用于测试磁盘写入),设置NI=19(最低优先级):
# 以NI=19的优先级运行dd命令,避免占用过多CPU资源
nice -n 19 dd if=/dev/zero of=/tmp/test bs=1M count=1000 示例 2:root 用户创建一个高优先级的wget进程(用于下载文件),设置NI=-10(提升优先级):
# 以NI=-10的优先级运行wget命令,加快下载速度
sudo nice -n -10 wget https://example.com/large_file.iso 创建后,可以通过ps -eo pid,pri,ni,cmd --sort=pri命令验证NI值是否生效。
renice命令 renice命令与top命令的优先级修改功能类似,但可以直接在命令行中执行,无需进入交互界面,语法格式如下:
# renice [NI值] -p 进程PID
renice NI值 -p PID 示例 1:将 PID=789 的dd进程的NI值修改为 10:
# 修改PID=789的进程优先级,NI=10
renice 10 -p 789 示例 2:root 用户将 PID=456 的bash进程的NI值修改为 - 5:
# root用户提升进程优先级,NI=-5
sudo renice -5 -p 456执行后,系统会输出修改结果:
456 (process ID) old priority 0, new priority -5注意事项:
renice命令的权限限制与top命令一致:普通用户只能提高NI值(降低优先级),root 用户可以任意调整;-p后跟上多个 PID,例如:sudo renice -5 -p 456 789。getpriority与setpriority 除了命令行工具,Linux 还提供了系统调用函数getpriority和setpriority,用于在 C/C++ 程序中获取和设置进程优先级。虽然本文要求代码为 bash,但可以通过awk等工具间接调用(或参考其底层逻辑)。
核心函数原型:
#include <sys/resource.h>
// 获取进程优先级
int getpriority(int which, int who);
// 设置进程优先级
int setpriority(int which, int who, int prio); 在 bash 中,可以通过awk调用系统函数(示例仅供参考,实际开发中更常用 C 语言):
# 使用awk调用getpriority获取PID=1的进程优先级
awk 'BEGIN{
printf("PID=1的优先级:%d\n", getpriority(PRIO_PROCESS, 1));
}'输出示例:
PID=1的优先级:60 这与我们之前通过ps命令查看的init进程优先级一致(PRI=60)。
当多个进程在 CPU 上并发执行时,Linux 内核需要频繁地在不同进程之间切换 —— 暂停当前进程的执行,保存其状态,然后加载下一个进程的状态并继续执行。这个过程就是进程切换(也称为上下文切换,Context Switch)。
CPU 的寄存器是进程执行的 “临时工作台”,保存着进程执行过程中的关键数据(如程序计数器、栈指针、通用寄存器值等)。这些寄存器中的数据统称为CPU 上下文。
进程切换的核心步骤可以概括为 “保存 - 加载 - 执行” 三部曲:
task_struct(PCB)中;task_struct中保存的上下文数据加载到 CPU 寄存器中;
举个形象的例子:进程切换就像厨师在烹饪多个订单时的 “换锅” 操作 ——
进程切换的速度直接影响系统的并发性能 —— 如果切换速度过慢,会导致大量 CPU 时间浪费在 “换锅” 上,而不是 “炒菜”(进程执行)上。因此,Linux 内核一直在优化进程切换的效率,而task_struct和上下文数据的组织方式是优化的关键。
进程切换不会随机发生,只有在特定条件下才会被内核触发,主要包括以下几种情况:
sleep()、wait()等系统调用时,会主动进入睡眠状态(阻塞状态),内核会切换到其他就绪进程;exit())或被信号终止(如kill -9 PID)时,内核会回收其资源,切换到其他就绪进程。其中,“时间片耗尽” 和 “高优先级进程抢占” 是最常见的触发条件,也是保证系统响应速度的核心机制。
进程切换虽然很快,但依然会产生一定的系统开销,主要包括:
为了减少切换开销,Linux 内核做了很多优化,例如:
task_struct的结构,让上下文数据集中存储,减少内存访问次数;内核中相关的源码如下:

在 Linux 2.6 内核之前,使用的是 O (n) 调度算法 —— 调度器需要遍历所有就绪进程,找到优先级最高的进程,调度时间随进程数量增加而线性增长(n 为就绪进程数)。当系统中进程数量较多时,调度开销会显著增加。
而 Linux 2.6 内核引入了O (1) 调度算法,实现了 “常数时间调度”—— 无论系统中有多少个就绪进程,调度器都能在固定时间内找到优先级最高的进程,极大提升了系统的并发性能。

O (1) 调度算法的核心是runqueue(运行队列),每个 CPU 核心都有一个独立的runqueue,用于管理该 CPU 上的所有就绪进程。这样可以避免多个 CPU 核心争抢同一个运行队列,提高调度效率。
runqueue的核心结构定义(简化版)如下:
struct rq {
spinlock_t lock; // 保护runqueue的自旋锁
unsigned long nr_running; // 就绪进程总数
struct task_struct *curr; // 当前运行的进程
struct task_struct *idle; // 空闲进程(CPU空闲时运行)
struct prio_array *active; // 活跃队列(时间片未耗尽的进程)
struct prio_array *expired; // 过期队列(时间片已耗尽的进程)
struct prio_array arrays[2];// 活跃队列和过期队列的实际存储
// 其他字段...
};
// 优先级数组结构
struct prio_array {
unsigned int nr_active; // 该队列中的进程数
DECLARE_BITMAP(bitmap, MAX_PRIO+1); // 优先级位图(快速查找非空队列)
struct list_head queue[MAX_PRIO]; // 进程链表(按优先级分组)
}; 从结构中可以看出,runqueue的核心设计是 “双队列 + 优先级分组”,下面逐一拆解。
runqueue包含两个相互独立的队列:active(活跃队列)和expired(过期队列),它们的功能分工明确:

双队列的工作流程如下:
active和expired指针的指向 —— 原来的过期队列变为新的活跃队列,原来的活跃队列变为新的过期队列;这种设计的核心优势是:时间片重新计算和队列切换可以在 O (1) 时间内完成,无需遍历所有进程。
每个prio_array(优先级数组)中包含两个关键组件:queue数组和bitmap位图,用于实现按优先级分组和快速查找。
queue是一个数组,数组的下标代表进程的优先级(0~139),每个元素是一个链表,存储该优先级的所有进程。例如:
queue[60]:存储PRI=60的进程(NI=-20的普通进程);queue[80]:存储PRI=80的进程(NI=0的默认优先级进程);queue[99]:存储PRI=99的进程(NI=19的低优先级进程)。 同一优先级的进程按 “先进先出”(FIFO)的顺序排队,确保公平性。例如,两个PRI=80的进程,先进入队列的会先被调度执行。
bitmap是一个位图(bitmap),用于标记哪个优先级的队列中存在进程。位图的每一位对应一个优先级(0~139):
1,表示对应的优先级队列中有进程;0,表示对应的优先级队列中没有进程。 例如,bitmap的第 60 位为1,表示queue[60]中有进程;第 80 位为1,表示queue[80]中有进程。
位图的核心作用是快速查找优先级最高的非空队列,步骤如下:
1的位,对应的下标就是最高优先级;queue链表中取出第一个进程,调度执行。 由于位图的扫描可以通过 CPU 的位运算指令(如ffs——find first set)实现,整个查找过程只需 O (1) 时间(与进程数量无关),这是 O (1) 调度算法的核心优化。
结合runqueue的结构和双队列、优先级分组设计,O (1) 调度算法的完整流程如下:
每个 CPU 核心启动时,会初始化一个runqueue,创建active和expired两个队列,初始化queue数组和bitmap位图。
当一个进程从阻塞状态被唤醒(或新建进程)时,内核会:
PRI值,将其加入active队列对应的queue[PRI]链表尾部;bitmap位图中对应PRI的位设置为1;active队列的nr_active计数器(进程数)。调度器需要选择进程时,会:
active队列的bitmap位图,找到第一个值为1的位(最高优先级);queue[PRI]链表中取出第一个进程(FIFO 顺序);curr(当前运行进程),并分配 CPU 时间片。当进程的时间片耗尽时,内核会:
active队列移动到expired队列对应的queue[PRI]链表尾部;active和expired队列的nr_active计数器和bitmap位图。 当active队列的nr_active计数器变为 0(所有进程时间片耗尽)时,内核会:
active和expired指针的指向(active = expired; expired = 原来的active);expired队列的nr_active计数器和bitmap位图;active队列中包含所有重新分配了时间片的进程,调度继续。如果进程执行完成(退出)或进入阻塞状态(如等待 I/O),内核会:
active队列中移除;bitmap位图(若该队列已空,将对应位设为0);nr_active计数器,调度器重新选择进程。O (1) 调度算法的最大优势是调度时间与进程数量无关—— 无论系统中有 10 个还是 1000 个就绪进程,调度器都能在固定时间内找到优先级最高的进程,极大提升了系统在高负载下的性能。
此外,该算法还具有:
O (1) 调度算法虽然高效,但也存在一些局限性:
为了解决这些问题,Linux 2.6.23 内核引入了 CFS(完全公平调度器),取代了 O (1) 调度算法。CFS 基于 “完全公平” 原则,通过计算进程的 “虚拟运行时间” 来分配 CPU 资源,更适合现代系统的交互性和实时性需求。但 O (1) 调度算法的设计思想(双队列、位图查找)依然是 Linux 调度器的核心基础,值得我们深入学习。
Linux 进程调度是内核的核心模块之一,其设计思想融合了数据结构、算法和系统优化的精华。希望本文能帮助你看透进程优先级和切换的底层逻辑,在实际工作中更好地优化系统性能。如果有任何疑问或补充,欢迎在评论区交流!