【Linux】进程优先级切换调度
1. 进程优先级
1.1 基本概念
是什么?
是进程得到CPU资源的先后顺序
为什么?
目标资源稀缺,导致要通过优先级确认谁先谁后的问题
优先级 vs 权限
优先级本质上是能得到资源,先后的问题。权限本质是是否能得到某种资源的问题。
怎么办?
也是一种数字。int,task_struct
值越低,优先级越高,反之,优先级越低。
基于时间片的分时操作系统,考虑公平性,优先级可能变化,但是变化幅度不能太大
1.2 查看系统进程
- UID:即用户 ID,决定进程拥有的权限(如普通用户 UID≠0,无法执行修改系统配置等 root 操作),确保进程只能访问自身权限范围内的资源。
- PID:进程唯一标识,内核通过 PID 管理进程(如
kill PID
终止进程、waitpid(PID)
回收子进程),避免进程混淆。 - PPID:父进程 ID,明确进程的 “血缘关系”,用于追溯进程来源(如通过 PPID 找到创建当前进程的父进程),也是孤儿进程被领养的判断依据。
- PRI:静态优先级(内核设定,用户无法直接修改),直接影响 CPU 调度顺序,值越小越先被分配 CPU 时间片。
- NI:nice 值(用户可调整,范围 - 20~19),用于动态修正 PRI(PRI = 基础 PRI + NI),NI 越小,PRI 越低,进程优先级越高(如
nice -n -5 程序
提升进程优先级)。
1.3 修改进程优先级的命令
1. ⽤top命令更改已存在进程的nice:
2. nice
:启动新进程时指定优先级
作用:在启动进程的同时设置其 NI
值(优先级修正值)。
语法:
nice -n [NI值] 命令
参数说明:
NI值
范围:-20
(最高优先级)~19
(最低优先级),默认0
。- 普通用户只能设置
0~19
(降低优先级),-20~-1
需root
权限(提高优先级)。
示例:
# 以 NI=5 启动程序(降低优先级)
nice -n 5 ./myprogram# 以 NI=-3 启动程序(提高优先级,需 root)
sudo nice -n -3 ./myprogram
3. renice
:调整已运行进程的优先级
作用:修改正在运行的进程的 NI
值。
语法:
renice [新NI值] -p [进程PID]
参数说明:
-p
:指定进程的PID
(可通过ps
或top
查看)。- 权限限制同
nice
:普通用户只能调高自身进程的NI
(降低优先级)。
示例:
# 将 PID=1234 的进程 NI 改为 10(降低优先级)
renice 10 -p 1234# 将 PID=5678 的进程 NI 改为 -5(提高优先级,需 root)
sudo renice -5 -p 5678
1.4 补充概念-竞争、独立、并行、并发
2. 进程切换
2.1 死循环进程如何运行
死循环进程(如while(1);
)会持续占用 CPU,但操作系统通过时间片轮转调度限制其独占:
- 内核为每个进程分配固定时间片(如几毫秒),死循环进程运行到时间片结束时,内核会触发时钟中断,强行剥夺其 CPU 使用权。
- 进程被暂时挂起,放入调度队列,等待下一次被调度(重新获得时间片)。
- 因此,死循环进程看似 “一直运行”,实则是在 “运行 - 挂起 - 再运行” 的循环中反复切换,与其他进程共享 CPU。
2.2 聊聊 CPU、寄存器
- CPU:执行指令的核心硬件,通过 “取指 - 译码 - 执行” 循环处理程序指令。
- 寄存器:CPU 内部的高速存储单元,用于临时存放数据和指令,包括:
- 通用寄存器:存放临时数据(如变量值、运算结果);
- 程序计数器(PC):记录下一条要执行的指令地址;
- 栈指针(SP):指向当前栈顶位置;
- 状态寄存器:保存 CPU 状态(如运算结果标志、中断屏蔽位)。
- 进程运行时,其指令和数据需加载到寄存器中被 CPU 处理,寄存器状态是进程运行的 “即时快照”。
2.3 进程切换如何实现
进程切换(上下文切换)是内核将 CPU 从一个进程切换到另一个进程的过程,核心步骤:
保存旧进程上下文:
当进程时间片结束或被中断时,内核将旧进程的寄存器状态(PC、SP、通用寄存器等)保存到其 PCB(进程控制块)中。选择下一个进程:
调度器根据优先级、时间片等策略,从就绪队列中选取下一个要运行的进程。恢复新进程上下文:
从新进程的 PCB 中读取之前保存的寄存器状态,加载到 CPU 寄存器中,包括将 PC 设置为新进程的下一条指令地址。切换完成:
CPU 开始执行新进程的指令,新进程进入运行态,旧进程则根据状态(如就绪、阻塞)进入对应队列。
进程切换最核心的是保存和恢复当前进程的硬件上下文数据,即CPU内寄存器的内容!
3. 进程调度
3.1 O (1) 调度器的核心结构与调度逻辑
心结构与逻辑
runqueue
:每个 CPU 对应一个运行队列,管理该 CPU 上的可运行进程。prio_array_t
(活跃 / 过期数组):- 用
bitmap[5]
(位图)快速标记 “哪些优先级有进程”,queue[140]
按优先级存放进程链表(优先级范围 0 - 139,数值越小优先级越高)。 - 调度时,通过位图 O (1) 时间 找到最高优先级的进程链表,再从链表取进程,实现高效调度。
- 用
- 负载因子:基于 CPU 队列中进程平均数量,平衡多 CPU 间的负载。
O (1) 调度器通过 “位图 + 优先级链表”,能在常数时间内挑选进程,提升调度效率。
3.2 O (1) 调度器的双队列机制
3.2.1 固定优先级调度的饥饿问题
固定优先级且无动态优先级调整的进程调度机制下,若存在两个持续循环的进程:
- 进程 A,优先级为 60(数值越小优先级越高);
- 进程 B,优先级为 90。
当进程 A 每次用完分配的 CPU 时间片后,会被重新放回对应优先级(60)的就绪队列前端。由于调度器始终优先选取优先级更高的进程执行,进程 A 会持续抢占 CPU 资源。而优先级较低的进程 B,因进程 A 长期占据高优先级调度权且自身持续循环,将长期无法获得 CPU 时间,从而陷入进程饥饿状态,即长时间得不到调度执行的情况。
3.2.2 双队列机制
在 O (1) 调度器中,为解决固定优先级导致的饥饿问题,设计了 “活跃队列(active)” 与 “过期队列(expired)” 的双队列机制:
- 活跃队列(active):存放当前可调度的进程,调度器从这里选取最高优先级进程执行。
- 过期队列(expired):当进程用完时间片后,不会直接放回原优先级的活跃队列,而是被移至过期队列。
当活跃队列中的所有进程都耗尽时间片后,调度器会交换活跃队列与过期队列的角色(活跃变过期,过期变活跃),让原本在过期队列中的进程(包括低优先级进程)获得调度机会。
这种设计避免了高优先级进程长期垄断 CPU:即使进程 A(优先级 60)用完时间片后进入过期队列,待队列交换后,进程 B(优先级 90)也能在新的活跃队列中被调度,从机制上缓解了饥饿问题。