在多任务操作系统中,进程的优先级是决定其执行顺序的重要依据。Linux 作为一个支持多用户、多任务的操作系统,通过优先级机制来确保系统资源能够合理分配,从而满足不同类型任务的需求。
1.基本概念
- 优先级也是一个整数,值越低,表明优先级越高。


2.PRI and NI
在 Linux 系统中,进程优先级由两个关键指标共同作用PRI(Priority) 和 NI(Nice Value)。
2.1.PRI(Priority)— 动态优先级
PRI 是内核使用的动态优先级值,用于决定进程的调度顺序。它是实际调度的依据,影响进程在 CPU 调度中的优先级高低。
- 范围:
PRI 的范围通常是 0-139,分为两部分:
- 实时优先级:0-99(用于实时进程)。
- 普通优先级:100-139(用于普通进程)。
普通进程的优先级范围是 100-139,但一些文档中提到的 60-99 是调度器内部映射的范围,并非实际的 PRI 值。内核通过公式 映射后值 = PRI - 40 将 100-139 转为 60-99,以便优化调度器的权重计算和处理性能。因此,这只是内核实现中的优化,而非用户可见的 PRI 值变化。此时PRI为默认80。

在部分 ps 版本或显示模式中,普通进程的 PRI(优先级)会进行偏移修正(即减去 100),因此普通进程的优先级显示范围会变为 0-39。

2.2.NI(Nice Value)— 静态优先级
NI 是用户空间可见的优先级值,用户可以通过它调整进程的初始优先级,进而间接影响 PRI 值。可以说NI是进程优先级的修正数据。
- 范围:
NI 的取值范围是 -20 到 19,默认值为 0。
- -20 表示最高优先级。
- 19 表示最低优先级。
进程真实的优先级 = PRI(默认) + NI。下面PRI中显示的已经是真实的优先级。
2.3.修改进程优先级
修改进程优先级:top -> r -> pid -> 修改数据


值得注意的是,进程优先级永远是默认的PRI 加上修正值

修改为10


再修改-10,这是得到的值是80(默认PR)-10,而不是90-10


优先级的极值,将修正值设为-100和100
由上面结果可以看到,优先级区间为[60, 99],nice值为[-20,19]
优先级设置不合理,会导致优先级低的进程长时间得不到CPU资源,进而导致进程饥饿
3.查看进程优先级的命令
3.1.nice
nice 用于以指定的nice值启动一个新进程。
语法:
nice [OPTION] [COMMAND [ARG]...]
参数:
- -n,--adjustment:设置 nice 值的调整量,范围为 -20 到 19,默认为 10。
- -20:最高优先级(最低 nice 值)。
- 19:最低优先级(最高 nice 值)。
使用示例
1. 启动进程并降低优先级
启动 myprocess 并设置 nice 值为 10:
nice -n 10 ./myprocess
2. 提高优先级
设置 nice 值为 -5(需要超级用户权限):
sudo nice -n -5 ./myprocess
![]()
3.2.renice
renice 用于调整已运行进程的 nice 值。
语法:
renice [OPTION] priority [IDENTIFIERS...]
-n:指定 Nice 值的调整幅度
--help:显示帮助信息。
--version:显示 renice 命令的版本信息。
参数:
- priority:新的 nice 值,范围为 -20 到 19。
- IDENTIFIERS:指定的进程标识符,可以是以下之一:
- PID(进程号)。
- PGRP(进程组号,需加 -g 选项)。
- 用户(需加 -u 选项)。
选项:
- -p:默认,表示通过PID调整。
- -g:通过PPID调整。
- -u:通过用户调整。
使用示例
1. 调整单个进程的优先级
将进程优先级设置为 5:
renice -n 5 -p 1234

2. 调整进程组的优先级
将进程组号为 5678 的所有进程设置为 10:
renice -n 10 -g 5678

3. 调整某用户所有进程的优先级
将用户 user1 的所有进程优先级设置为 15:
renice -n 15 -u user1

权限要求:
- 普通用户:
- 只能增加 nice 值(降低优先级)。
- 无法将 nice 值降低到负值。
- 超级用户(root):
- 可以任意调整 nice 值,包括设置负值。
nice 值越小,PRI 越低,优先级越高。
nice 值越大,PRI 越高,优先级越低。
3.3.top
top 是 Linux 中常用的实时性能监控工具,显示系统的资源使用情况和活动进程。通过它,可以查看 CPU、内存、任务、负载等信息,并对进程进行交互式管理。
(1)命令基础
top [选项]
常用选项
-d <秒> 指定刷新间隔时间,默认是 3 秒。
-p <PID> 仅显示指定 PID 的进程。
-u <用户名> 仅显示指定用户的进程。
-n <次数> 运行指定次数后退出。
-b 批处理模式,用于脚本或文件保存。
-i 忽略闲置和僵尸进程,仅显示活动进程。
-o <字段> 按指定字段排序(如 %CPU、MEM 等).
示例
1. 显示某用户的进程:
top -u alice
2. 每秒刷新一次并忽略闲置进程:
top -d 1 -i
(2)top 界面解析
全局状态信息
在 top 的输出顶部,会显示系统的全局状态,包括系统性能、任务和资源使用情况。
系统时间:当前时间、运行时间、在线用户数、系统平均负载。
任务信息:任务总数、运行、睡眠、停止和僵尸任务的数量。
CPU 使用率:按用户、系统、中断、空闲等分类显示 CPU 的使用率。
内存使用信息:显示物理内存的总量、已用、空闲和缓存/缓冲区占用情况。
交换分区信息:显示交换分区的总量、已用、空闲及缓存到交换分区的内存量。
进程详细信息
下方是活动进程的详细列表,显示系统中每个进程的实时状态。
PID:进程的唯一标识,可通过 kill PID 命令管理进程(如终止、暂停)。
USER:启动进程的用户。
PR:进程的调度优先级,数值越小优先级越高。
NI:nice 值,范围 -20(最高优先级)到 19(最低优先级),可通过 renice 命令调整。
VIRT:虚拟内存总量,包含物理内存、交换空间和未实际分配的内存,仅作参考(数值大不一定代表实际占用多)。
RES:物理内存实际占用大小,是判断进程内存消耗的核心指标(如 RES 持续增长需排查内存泄漏)。
SHR:共享内存大小,反映进程间内存共享程度(如多个服务进程共享库文件时 SHR 占比高)。
S 进程状态:
- R:正在运行或等待运行,数量多说明 CPU 负载高;
- S:休眠(等待事件),是正常状态;
- D:不可中断休眠(多因 I/O 阻塞),需排查设备故障;
- Z:僵尸进程,需找到父进程清理;
- T:被暂停,需手动恢复或终止。
%CPU:进程的 CPU 使用率,持续高占比(如超过 80%)的进程需排查是否存在死循环、计算密集型逻辑优化不足等问题。
%MEM:进程的内存使用率,结合 RES 可判断内存资源是否被过度占用(如占比超 50% 需评估是否优化)。
TIME+:总 CPU 时间,用于判断进程的运行时长和资源消耗趋势(如短期进程 TIME+ 激增可能异常)。
COMMAND:进程的启动命令,可通过命令路径或参数判断进程功能(如 nginx 是 Web 服务进程)。

(3)交互式操作
在 top 界面中,可以使用以下快捷键对显示内容和进程进行交互操作。
h 显示帮助菜单,列出所有快捷键。
q 退出 top。z 切换颜色高亮显示。
l 切换显示平均负载和系统运行时间的第一行信息。
t 切换任务和 CPU 信息显示。
m 切换内存和交换分区信息显示。
1 显示每个 CPU 的单独使用情况(多核系统)。
c 显示或隐藏完整命令行(默认只显示命令名)。
n 更改显示的进程数。
u 按用户过滤进程。
k 杀死指定进程(需输入 PID)。
r 更改指定进程的优先级(需输入 PID 和新 nice 值)。
P 按 CPU 使用率排序。
M 按内存使用率排序。
T 按运行时间排序。
4. 竞争、独立、并行、并发
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行:多个进程在多个CPU下分别同时进行运行,这称之为并行。
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称
之为并发。
5.进程切换
进程切换是指操作系统暂停当前运行的进程,并将 CPU 资源分配给另一个进程的过程。这种切换由内核负责,是实现多任务操作系统的重要机制。
进程切换最核心的,就是保存和恢复当前进程硬件上下文数据,即CPU内寄存器的内容。这些内容会被保存到task_struct中。
1.为什么需要进程切换?
多任务操作系统需要支持多个进程同时运行,而 CPU 在任意时刻只能执行一个任务。进程切换通过时间分片和资源调度,让用户感觉多个任务同时进行,从而实现 并发 或 并行。
2.进程切换的触发条件
进程切换可以在以下情况下触发:
1) 时间片到期
- 使用时间片轮转调度算法时,当前进程的时间片耗尽,操作系统会切换到下一个进程。
2) I/O 或阻塞操作
- 当前进程请求 I/O 设备(如读写文件、网络操作),导致进程进入阻塞状态,操作系统会调度其他进程运行。
3) 优先级调度
- 高优先级的进程就绪时,低优先级进程会被暂停,CPU 切换到高优先级进程。
4) 主动让出 CPU
- 进程调用系统调用(如 sched_yield())主动放弃 CPU,切换给其他进程。
5) 硬件中断
- 当外部设备产生中断(如键盘输入、网络数据到达)时,当前进程会被暂停,切换到处理该中断的中断处理程序。
6) 系统调用
- 进程发起某些系统调用(如文件操作、内存分配)时,可能会切换到内核态或其他进程。
3.进程切换的步骤
进程切换涉及保存当前进程的状态,并加载另一个进程的状态。
1) 保存当前进程的状态
- 保存 CPU 寄存器内容:包括程序计数器(PC)、栈指针(SP)和通用寄存器。
- 保存内存管理信息:如页表指针。
- 更新进程控制块 (PCB):将当前进程的状态更新为“就绪”或“等待”。
2) 切换上下文
- 调度器选择下一个要运行的进程。
- 加载该进程的 PCB 和内存上下文。
3) 恢复新进程的状态
- 恢复目标进程的 CPU 寄存器、栈指针等信息。
- 将目标进程的状态设置为“运行”。
总结
- 进程切换是多任务操作系统中实现并发的重要机制。
- 切换由操作系统内核管理,包括保存和恢复进程上下文。
- 虽然进程切换有开销,但通过高效调度算法可以降低影响。
6.O(1)调度算法
在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法!
Linux 中的 O(1) 调度算法是一种高效的进程调度方式,其设计目的是在常数时间内完成进程调度决策,不论系统中运行的进程数目有多少。这是通过合理组织数据结构(如 runqueue 和优先级队列)来实现的。
6.1.关键数据结构
1) runqueue(运行队列)
- 每个 CPU 上都有一个 runqueue,它包含了所有处于就绪状态的进程。
- runqueue 中的进程按照优先级进行组织,进程根据其优先级分配到不同的队列。

2) 活跃队列(Active Queue)
- 时间片还没有结束的所有进程都按照优先级放在该队列
- nr_active:总共有多少个处于运行状态的进程
- queue[140]:一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
- 从该结构中,选择一个最合适的进程,过程是怎么的呢?
- 从0下表开始遍历queue[140]
- 找到第一个非空队列,该队列必定为优先级最高的队列
- 拿到选中队列的第一个进程,开始运行,调度完成!
- 遍历queue[140]时间复杂度是常数!但还是太低效了!
- bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用
5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!
3) 过期队列(Expired Queue)
- 过期队列和活动队列结构一模一样。
- 过期队列存储的是已经耗尽时间片的进程,这些进程暂时不能继续执行,需要经过优先级调整后,才能重新回到活动队列中。
当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
4) active 和 expired 指针
- active 指针指向活跃队列中的队列头(最优先的进程)。
- expired 指针指向过期队列中的队列头。
- 通过这两个指针,调度器可以快速地从活动队列或过期队列中选择一个进程进行调度。
5) 优先级
- Linux 中的进程优先级决定了进程的执行顺序。O(1) 调度算法采用优先级队列来区分不同优先级的进程。
- 进程有不同的优先级级别,在 Linux 的 O(1) 调度中,优先级被划分为 多个优先级队列,每个队列对应一个优先级范围。
6.2 O(1) 调度算法的工作原理
O(1) 调度算法的核心思想是 保持每个优先级的活跃队列 和 过期队列,并通过一个常数时间复杂度的操作来完成进程调度。它的实现方式是通过两个主要的队列管理进程的优先级和运行状态:
1) 当 CPU 空闲时:直接取活动队列最高优先级进程
- 查找活跃队列中的第一个进程,即 active 指针指向的进程。如果活跃队列中有可运行的进程,则将该进程加载到 CPU 并开始执行。反之,调度器会向后查找更低优先级的非空子队列。
2) 进程时间片耗尽
- 如果进程的时间片耗尽,它会被移到过期队列 expired 中,同时其优先级会被动态调整(优先级越高,时间片重置后的值越大)。
- 这样活动队列里的进程会越来越少,而过期队列里的进程会越来越多
3) 切换活跃队列与过期队列
- 当活动队列为空时,调度器交换 active 和 expired 指针(而非物理移动进程),让过期队列直接变为新的活动队列
4) O(1) 时间复杂度
- 每个优先级对应独立子队列,插入、删除进程时只需操作对应子队列(时间复杂度 O (1));
- 调度时通过 active/expired 指针直接定位目标队列,无需遍历所有进程;
- 队列切换仅需交换指针,无进程移动开销。
总结
O(1) 调度算法通过使用 活动队列 和 过期队列,结合 active 和 expired 指针,实现了高效的进程调度。该算法确保了调度操作在 常数时间复杂度 下完成,无论进程的数量如何变化,都能保持较低的开销。通过这种方式,Linux 能够在保证高效调度的同时,平衡系统资源,确保任务公平性和响应性。
2789

被折叠的 条评论
为什么被折叠?



