⚙️ 进程管理

进程是操作系统资源分配的基本单位,本章涵盖进程状态转换、PCB结构、线程模型及CPU调度算法
进程状态机 PCB与线程 CPU调度算法
🔄 进程状态机
五状态转换模型 — 点击按钮触发状态转换,观察状态变化
状态转换动画
五状态详解
五状态转换模型:新建→就绪→运行→阻塞/终止。 点击下方按钮触发状态转换事件,观察状态机变化及转换原因。
触发转换事件:
admit(进入就绪)
进程创建后被接纳
dispatch(调度运行)
调度器选中,分配CPU
timeout(时间片耗尽)
时间片用完,回到就绪队列
wait(请求I/O阻塞)
等待I/O或资源
complete(I/O完成)
阻塞解除,回就绪
exit(进程终止)
正常/异常退出
reset(重置)
重新开始
等待操作... 点击上方按钮开始
五状态模型详解:

新建(New):进程刚被创建,尚未加入就绪队列
就绪(Ready):已具备运行条件,等待CPU分配
运行(Running):正在CPU上执行
阻塞(Waiting):等待某事件(I/O完成、资源可用)
终止(Terminated):执行完毕或被中止

七状态模型新增:就绪挂起、阻塞挂起(换出到外存)
状态转换条件:

NULL→新建:创建进程(fork)
新建→就绪:admit,系统允许进程进入就绪队列
就绪→运行:dispatch,调度器分配CPU
运行→就绪:timeout,时间片到期或被抢占
运行→阻塞:等待I/O、等待资源(P操作)
阻塞→就绪:I/O完成、资源可用
运行→终止:执行结束或异常退出
📋 PCB与线程
进程控制块结构 · 进程与线程的对比
PCB(进程控制块)
操作系统管理进程的核心数据结构,唯一标识一个进程。

主要内容:
· 进程标识符 PID/PPID
· CPU上下文(寄存器、PC、PSW)
· 内存信息(基址、限长、页表指针)
· 调度信息(优先级、状态、时间片)
· I/O状态、已打开文件列表
· 会计信息(CPU时间、占用内存)
进程 = 程序 + 数据 + PCB
进程 vs 线程
进程:资源分配的基本单位,拥有独立地址空间
线程:CPU调度的基本单位,共享进程资源

线程共享:代码段、数据段、文件描述符、堆
线程私有:PC、寄存器、栈、线程ID

用户级线程:由线程库管理,内核不可见
内核级线程:由OS内核管理,系统调用代价低
上下文切换开销来源:
· 保存/恢复CPU寄存器
· 切换内存映射(TLB刷新)
· 内核态与用户态切换
线程切换开销小于进程切换(无需切换地址空间)
📋 PCB结构详解(进程控制块)
PCB是操作系统感知进程存在的唯一标志,创建进程就是创建PCB
📖 408标准定义:
PCB(Process Control Block)是操作系统管理进程的核心数据结构,当进程被创建时,操作系统为其分配一个PCB;当进程结束时,回收其PCB。进程 = 程序 + 数据 + PCB。

⚡ 考点:"PCB是进程存在的唯一标志"——没有PCB的进程不存在(或者只是程序,不是进程)。
① 进程标识符
· PID:进程ID,唯一标识
· PPID:父进程ID
· UID:用户ID(谁创建的进程)
· GID:组ID
② 处理器状态(CPU上下文)
· PC(程序计数器):下一条指令地址
· PSW(程序状态字):标志位
· 通用寄存器:AX, BX, CX, DX等
· 栈指针SP:函数调用栈位置
③ 进程调度信息
· 进程状态:新建/就绪/运行/阻塞/终止
· 优先级:数字越小优先级越高
· 时间片大小:剩余时间片
· 等待原因:阻塞原因(等I/O?等信号量?)
④ 内存管理信息
· 基址寄存器:内存起始地址
· 限长寄存器:内存长度(越界保护)
· 页表指针:分页系统的页表基址
· 段表指针:分段系统的段表基址
⑤ I/O状态信息
· 已分配I/O设备:进程占用的设备
· 打开文件表:进程打开的文件列表
· 信号量使用情况:进程持有的信号量
⑥ 会计信息
· CPU使用时间:累计占用CPU时间
· 内存占用:峰值内存使用量
· 计费信息:分时系统计费依据
PCB的组织方式(408选择题高频考点)
① 线性表:把所有PCB放在一个数组里,简单但查找慢(适用于进程数少的系统)
② 链接方式:相同状态的PCB链接成一个队列(就绪队列、阻塞队列等),用指针链接
③ 索引方式:按进程状态建立索引表,索引项指向对应PCB的地址(查找快,但需要额外空间存索引表)
📝 考点:UNIX系统使用进程表(线性表)+ 状态队列(链接方式)的混合组织方式。
🧵 线程模型深度对比
用户级线程、内核级线程、轻量级进程 —— 三种线程实现方式对比
📖 线程的引入:
进程是资源分配的基本单位,线程是CPU调度的基本单位。引入线程后,减少并发执行的开销(创建/终止/切换),提高并发度。

⚡ 线程共享 vs 私有:
· 共享:代码段、数据段、堆、文件描述符表
· 私有:线程ID、PC、寄存器集合、栈
① 用户级线程
由用户空间的线程库管理,内核不可见
✅ 优点:切换快(无需陷入内核)、调度灵活
❌ 缺点:一个线程阻塞,整个进程阻塞
典型:POSIX Pthreads(用户级)
② 内核级线程
由操作系统内核管理,内核直接感知线程存在
✅ 优点:一个线程阻塞,其他线程可继续运行
❌ 缺点:切换开销大(需要陷入内核)
典型:Windows、Linux(NPTL)
③ 轻量级进程(LWP)
用户级线程绑定到内核级线程,多对多映射
✅ 优点:结合用户级和内核级优点
❌ 缺点:实现复杂(需要用户和内核协作)
映射:一对一(Linux)、多对多(Solaris)
▲ 用户级线程切换 vs 内核级线程切换(点击播放动画)
📝 408高频考点:
① "用户级线程的切换是否需要内核介入?" → 不需要
② "内核级线程的切换是否需要内核介入?" → 需要
③ "多对一模型有什么问题?" → 一个线程阻塞,整个进程阻塞
④ "一对一模型有什么缺点?" → 创建线程开销大
💬 进程通信(IPC)
进程之间如何传递信息?四种经典IPC方式对比
📖 为什么需要进程通信?
进程是资源分配的基本单位,各进程的地址空间相互独立。但进程之间经常需要交换信息,这就需要进程通信机制

⚡ 两种通信方式:
· 共享内存:多个进程共享同一块内存区域(最快的IPC方式)
· 消息传递:进程通过发送/接收消息来通信
① 共享内存
多个进程共享同一块内存区域
✅ 优点:速度快(无需内核复制)
❌ 缺点:需要进程自己处理同步互斥
典型:shmget()/shmat()
② 管道(Pipe)
半双工通信,数据只能单向流动
✅ 优点:简单、Linux原生支持
❌ 缺点:只能在父子进程间使用
分类:匿名管道、命名管道(FIFO)
③ 消息队列
进程通过消息队列发送/接收消息
✅ 优点:支持多对多通信、消息有边界
❌ 缺点:需要内核复制,速度不如共享内存
典型:msgget()/msgsnd()/msgrcv()
④ 信号(Signal)
用于通知进程发生了某种事件(异步)
✅ 优点:开销小、异步通知
❌ 缺点:只能携带少量信息(信号编号)
典型:SIGINT、SIGKILL、SIGCHLD
共享内存工作原理(最快的IPC方式)
Step 1:进程A调用 shmget() 创建共享内存段
Step 2:进程A调用 shmat() 将共享内存映射到自己的地址空间
Step 3:进程B调用 shmat() 将同一块共享内存映射到自己的地址空间
Step 4:进程A和进程B都可以直接读写这块内存(速度最快!)
Step 5:通信完成后调用 shmdt() 脱离,最后 shmctl() 删除
⚡ 关键点:共享内存是最快的IPC方式(因为不需要内核参与数据传输)!但需要进程自己用信号量来保证同步互斥。
🔒 Peterson算法(临界区软件实现)
纯软件方式实现进程互斥进入临界区 —— 408操作系统早期经典考点
📖 什么是Peterson算法?
Peterson算法是一种纯软件的临界区互斥进入算法(不需要硬件支持)。它满足互斥前进有限等待三个条件。

⚡ 算法思想:两个进程P0和P1,通过两个变量来实现互斥:
· flag[i]:表示进程i想进入临界区
· turn:表示轮到谁(谦让变量)
do {
flag[i] = true; // 我想进入临界区
turn = j; // 谦让给对方
while(flag[j] && turn == j); // 等待
临界区; // 进入临界区!
flag[i] = false; // 离开临界区
} while(true);
变量状态
flag[0] = false
flag[1] = false
turn = -
临界区占用 =
📝 408考点:
① Peterson算法满足互斥前进有限等待
局限性:只适用于两个进程,多个进程需要用Bakery算法
硬件解法:中断屏蔽(单CPU)、硬件原子指令(TS指令、Swap指令)
🛠️ 进程创建与终止(fork/exec详解)
UNIX/Linux中进程的创建过程 —— fork()系统调用详细分析
📖 fork()系统调用:
fork()创建一个与父进程几乎完全相同的子进程(复制PCB、代码段、数据段、堆、栈)。子进程从 fork() 返回处开始执行,但返回值不同:
· 父进程中 fork() 返回子进程的PID(>0)
· 子进程中 fork() 返回0
· 如果失败,返回-1

⚡ 写时复制(Copy-On-Write, COW):现代操作系统并不在 fork() 时立即复制内存,而是让父子进程共享同一块内存,只有当其中一个进程写入时,才复制被写入的页面。
▲ fork()执行流程(父进程创建子进程,写时复制COW)
fork() + exec() 典型用法
场景:shell执行命令时,先 fork() 创建子进程,然后在子进程中调用 exec() 加载新的程序。

Step 1:父进程调用 fork(),创建子进程(子进程是父进程的副本)
Step 2:子进程调用 exec() 系列函数,替换自己的代码段、数据段
Step 3:父进程调用 wait() 等待子进程结束,回收其PCB

⚡ exec() 的神奇之处:调用 exec() 后,进程的代码段、数据段、堆、栈全部被新程序替换,但PID不变
进程终止(Termination)
正常终止:
· 从 main() return(隐式调用 exit())
· 显式调用 exit() / _exit()

异常终止:
· 被信号终止(如 kill -9)
· 执行非法指令(如除零)

终止后:
· 进程变成僵尸进程(Zombie):PCB还在,等待父进程调用 wait() 回收
· 如果父进程先终止,子进程变成孤儿进程(Orphan),被 init 进程(PID=1)收养
🔄 进程切换(上下文切换)
进程切换是操作系统最核心的操作之一,涉及大量寄存器保存/恢复
📖 什么时候发生进程切换?
① 时间片到期(超时)→ 从运行态到就绪态
② 进程阻塞(等待I/O、等待信号量)→ 从运行态到等待态
③ 新进程创建(可能被调度)
④ 中断/异常发生(切换到内核态处理中断)

⚡ 关键:进程切换只能在内核态进行!
进程切换的详细步骤(408标准答案版)
1
保存用户态上下文:把用户态的寄存器保存到用户栈或内核栈
2
切换到内核态:通过中断/异常/trap指令,从用户态陷入内核态
3
保存内核上下文:把内核态的寄存器保存到当前进程的PCB中
4
调度器选择新进程:调用调度算法,从就绪队列中选一个进程分配CPU
5
恢复新进程的上下文:从新进程的PCB中恢复寄存器值到CPU
6
切换到用户态:恢复新进程的用户栈,从内核态返回用户态
7
继续执行:新进程从上次被中断的地方继续执行
📝 考点:
① "进程切换时需要保存哪些上下文?" → 用户寄存器、PC、PSW、内核栈指针
② "进程切换和模式切换的区别?" → 模式切换只切换用户态/内核态,不切换进程
③ "进程切换的开销主要来自于?" → 保存/恢复寄存器TLB刷新Cache刷新
🖥️🖥️ 多处理机调度(多核/NUMA)
多CPU/多核系统的调度策略 —— 现代操作系统重要考点
📖 为什么需要多处理机调度?
现代计算机都是多核(甚至多CPU)系统。多个CPU可以同时运行多个进程/线程,这就需要新的调度策略来提高缓存命中率内存访问局部性
① 对称多处理(SMP)
所有CPU地位平等,都运行自己的调度器。

优点:简单、负载均衡
缺点:缓存亲和性差
② 处理器亲和性
尽量让进程在同一个CPU上运行,这样它的数据还在CPU的缓存里。

软亲和性:操作系统尽量但不强制
硬亲和性:通过 sched_setaffinity() 强制
③ NUMA调度
NUMA架构中,每个CPU有自己的本地内存(访问快)和远端内存(访问慢)。

调度策略:尽量让进程访问本地内存
▲ NUMA架构示意图(每个CPU有本地内存,访问远端内存更慢)
📝 408考点:
多核调度需要考虑缓存亲和性内存访问局部性
NUMA感知调度:把进程调度到离它的内存最近的CPU上
负载均衡:多处理机调度还需要考虑各个CPU的负载是否均衡
⚡ CPU调度算法
六大经典调度算法动画演示,支持自定义进程参数
FCFS 先来先服务
SJF 短作业优先
SRTF 最短剩余时间
RR 时间片轮转
优先级调度
MLFQ 多级反馈队列
HRRN 高响应比
抢占式优先级