1. 进程优先级
1.1 基本概念
-
是什么 :进程获得CPU资源的先后顺序。
-
为什么需要 :因为CPU资源稀缺,需要确定哪个进程优先执行。
-
如何实现 :在进程的
task_struct结构中用整数表示优先级。 -
区分:
-
优先级 (Priority):能否以及何时获得资源。
-
权限 (Permission):能否访问资源(安全控制)。
-
1.2 Linux优先级表示
-
PRI (Priority) :进程的最终优先级,用户无法直接修改。
-
Linux默认值:80
-
Linux范围:[60, 99](共40级)
-
-
NI (Nice Value) :进程优先级的修正值(调整参数)。
-
范围:[-20, 19]
-
用户可以通过
nice或renice命令调整此值。
-
-
计算公式:
最终优先级(PRI) = 基础优先级(默认80) + NI值
-
NI值越低(可为负数),优先级越高。
-
NI值越高(最大19),优先级越低。
1.3 查看与调整优先级
1.3.1 查看进程优先级-ps
bash
# 使用ps命令查看进程详细信息,包括优先级
ps -al | head -1 && ps -al | grep myprocess
# 输出示例:
# F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
# 0 S 1000 24261 24260 0 80 0 1054 hrtime pts/0 00:00:00 myprocess
1.3.2 调整进程优先级-nice(需root权限)
bash
# 启动时指定优先级(普通用户只能增加NI值,降低优先级)
nice -n 10 ./myprocess # 设置NI为10
# 调整运行中进程的优先级
renice -n -100 -p 25110 # 调整进程25110的NI值(实际会限制在[-20,19]内)
renice -n 100 -p 25110 # 调整进程25110的NI值(实际会限制在[-20,19]内)
1.4 系统调用接口
c
#include <sys/time.h>
#include <sys/resource.h>
int getpriority(int which, int who); // 获取优先级
int setpriority(int which, int who, int prio); // 设置优先级
1.5 进程权限与文件访问
-
Linux中,访问任何资源都是以进程身份进行,进程代表用户执行操作。
-
系统通过进程的有效用户ID (EUID) 判断文件访问权限(所有者、所属组、其他用户)。
2. 进程切换与上下文
2.1 时间片与进程调度
-
时间片 (Time Slice):每个进程被分配的执行时间单元。
-
关键事实:
-
进程不会一直占用CPU直到代码执行完毕,而是会在时间片用完后被切换。
-
死循环进程不会"打死"系统,因为它们也会被调度器切换出去。
-
2.2 CPU寄存器与进程上下文
-
寄存器的作用 :CPU内部的临时存储空间,用于保存当前正在执行进程的临时数据。
-
寄存器内容 = 进程的硬件上下文:
-
程序计数器 (PC/EIP):下一条要执行的指令地址。
-
栈指针 (EBP/ESP):栈帧相关信息。
-
通用寄存器 (EAX, EBX, ECX, EDX等):计算中间结果。
-
段寄存器 (CS, DS, ES等):内存段信息。
-
标志寄存器 (EFLAGS):状态标志。
-
2.3 进程切换的类比
"学生当兵"模型:
-
CPU (学校):执行任务的地方。
-
进程 (学生):需要执行的任务。
-
调度器 (辅导员):决定哪个学生使用学校资源。
-
上下文 (学籍):学生当前的学习状态。
-
切换过程:
-
保存上下文 :学生离校当兵前,保存学籍信息(相当于保存进程的寄存器内容)。
-
恢复上下文 :学生返校后,恢复学籍信息(相当于恢复进程的寄存器内容)。
-
继续执行:从上次中断的地方继续学习。
-
2.4 进程切换的核心机制
-
上下文保存位置 :进程的上下文数据被保存到其
task_struct结构体中。 -
具体实现 :在Linux中,上下文信息通常保存在 TSS (Task State Segment,任务状态段) 结构中。
-
切换核心 :进程切换最核心的操作就是保存当前进程的硬件上下文到其PCB中,并将下一个进程的上下文从PCB恢复到CPU寄存器。
3. 任务状态段 (TSS)
概念 :TSS 是 x86 架构里用于保存任务上下文、管理特权级栈、处理中断栈和控制 I/O 权限的专用硬件数据结构。
3.1 TSS结构
c
// 任务状态段数据结构
struct tss_struct {
long back_link; // 链接字段
long esp0; // 0级栈指针
long ss0; // 0级栈段选择符
long esp1; // 1级栈指针
long ss1; // 1级栈段选择符
long esp2; // 2级栈指针
long ss2; // 2级栈段选择符
long cr3; // 页目录基址寄存器
long eip; // 指令指针
long eflags; // 标志寄存器
long eax, ecx, edx, ebx; // 通用寄存器
long esp; // 栈指针
long ebp; // 基址指针
long esi, edi; // 变址寄存器
long es, cs, ss, ds, fs, gs; // 段寄存器
long ldt; // 局部描述符表选择符
long trace_bitmap; // 调试跟踪位图
struct i387_struct i387; // 浮点运算单元状态
};
3.2 TSS在PCB中的位置
c
struct task_struct {
// 各种进程属性...
long state; // 进程状态
long counter; // 时间片计数器
long priority; // 优先级
// ... 其他字段
/* tss for this task */
struct tss_struct tss; // 任务状态段
};
4. O(1)调度算法
调度器目标
-
公平性:确保所有进程都有机会获得CPU时间。
-
高效性:快速选择下一个要运行的进程。
-
响应性:对交互式进程快速响应。****
4.1 核心数据结构

c
// 简化表示
struct prio_array {
unsigned int bitmap[5]; // 优先级位图,指示哪些优先级队列非空
struct list_head queue[140]; // 140个优先级队列,每个队列对应一个优先级
unsigned int nr_active; // 活跃进程总数
};
struct runqueue {
struct prio_array *active; // 指向活跃队列
struct prio_array *expired; // 指向过期队列
// ... 其他调度信息
};
4.2 调度过程
bitmap[5] :⼀共140个优先级,⼀共140个进程队列,为了提⾼查找⾮空队列的效率,就可以⽤
5*32个比特位表示列是否为空,这样,便可以⼤⼤提⾼查找效率!

-
两个队列设计:
-
活跃队列 (active) :存放仍有时间片的进程。
-
过期队列 (expired) :存放时间片已用完的进程。
-
-
调度步骤 :
a. 从活跃队列的位图中查找最高优先级 的非空队列。
b. 从该优先级队列中取出第一个进程 执行。
c. 进程时间片用完时,重新计算优先级和时间片 ,放入过期队列。
d. 当活跃队列为空时,交换活跃队列和过期队列的指针。
-
性能特点 :选择下一个进程的时间复杂度为 O(1),与系统进程数无关。
4.2 优先级映射
-
Linux优先级范围 [60, 99] 映射到调度器的140个优先级队列。
-
映射关系考虑公平性和响应性需求。
4.3 进程饥饿问题
-
定义 :由于优先级设置不合理,导致低优先级进程长时间得不到CPU资源。
-
解决方案:
-
动态优先级调整:根据进程行为(CPU密集型 vs I/O密集型)动态调整优先级。
-
时间片补偿:为长时间未运行的进程提供额外时间片。
-
公平调度算法:如CFS(完全公平调度器),确保所有进程公平分享CPU时间。
-
4.4 实时操作系统 vs 分时操作系统
-
分时操作系统 (如桌面Linux):
-
目标:公平分享CPU时间。
-
特点:支持优先级,但可能被抢占。
-
应用:通用计算环境。
-
-
实时操作系统 (如VxWorks, RTLinux):
-
目标:保证任务在规定时间内完成。
-
特点:严格优先级,高优先级任务可立即抢占低优先级任务。
-
应用:工业控制、航空航天、医疗设备等关键任务系统。
-
5. 总结
-
孤儿进程是父进程先退出后由init进程领养的子进程,防止僵尸进程累积。
-
进程优先级通过PRI和NI值确定,PRI=80+NI,范围[60,99],可通过nice/renice调整。
-
进程切换 的核心是保存和恢复硬件上下文(寄存器内容),保存到PCB的TSS结构中。
-
O(1)调度算法使用活跃/过期双队列设计,实现常数时间的进程选择。
-
进程饥饿是低优先级进程长期得不到CPU的问题,现代调度算法通过动态调整避免。
-
分时操作系统强调公平性,实时操作系统强调确定性,适用于不同应用场景。