三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

02-进程管理

02-进程管理

第2章:进程管理

进程是操作系统中最基本、最重要的概念。本章系统介绍进程的概念、状态转换、进程控制、线程以及进程同步与通信机制。


2.1 进程的引入

2.1.1 程序的顺序执行与并发执行

特征 顺序执行 并发执行
封闭性 程序独占资源,运行结果仅由初始条件决定 失去封闭性,结果可能受其他程序影响
可再现性 相同输入总是得到相同输出 失去可再现性(非确定性)
与时间关系 无关 执行速度由进程调度决定
前趋图 — 程序的执行依赖关系:程序I1↗   ↖I2    I3↗  ↖    ↗  ↖I4   I5  I6   I7↖  ↗  ↖  ↗I8    I9

前趋图(Precedence Graph)是一个有向无环图(DAG),用于描述程序段之间的执行顺序。

2.1.2 进程的定义与特征

定义角度 描述
行为 程序在一个数据集合上的运行过程
系统 系统进行资源分配和调度的独立单位
结构 由程序段、数据段和PCB三部分组成
特征 说明
动态性 进程是执行中的程序,有生命周期(区别于程序)
并发性 多个进程可同时在内存中并发执行
独立性 进程是独立运行和申请资源的基本单位
异步性 进程按各自不可预知的速度推进
结构性 每个进程配置一个PCB(进程控制块)

⚠️ 进程 vs 程序:

  • 进程是动态的(执行过程),程序是静态的(指令集合)
  • 进程有生命周期,程序可永久保存
  • 一个程序可对应多个进程,一个进程可执行多个程序

2.2 进程控制块(PCB)⭐

2.2.1 PCB的作用

PCB(Process Control Block) 是操作系统感知进程存在的唯一标志。系统通过PCB来管理和控制进程。

2.2.2 PCB中的主要信息

                   ┌─────────────────────┐│        PCB          │├─────────────────────┤│ 进程标识符(PID)      │├─────────────────────┤│ 处理机状态(PSW/寄存器)│  ← 保护与恢复现场├─────────────────────┤│ 进程调度信息          ││  (状态/优先级/时间片)   │├─────────────────────┤│ 进程控制信息          ││ (程序入口/数据地址)     │├─────────────────────┤│ 链接指针             │  ← 将PCB链接成队列├─────────────────────┤│ 资源清单/已打开文件    │└─────────────────────┘
信息类别 包含内容
进程标识符 PID(唯一数字标识)、父进程PID、用户标识
处理机状态 通用寄存器、PC、PSW、栈指针
进程调度信息 状态、优先级、已等待时间、调度策略
进程控制信息 程序入口地址、数据地址
资源清单 已打开文件列表、已分配设备等

2.2.3 PCB的组织方式

组织方式 说明 图示
线性表 所有PCB连续存储,简单但查找慢 [PCB1][PCB2][PCB3]...
链接方式 同状态PCB组成队列 执行队列→就绪队列→阻塞队列
索引方式 用索引表记录各状态PCB的首地址 索引表 → PCB链表
链接方式示意图:执行指针 → [PCB_A] → null就绪队列指针 → [PCB_B]→[PCB_C]→[PCB_D]→null阻塞队列指针 → [PCB_E]→[PCB_F]→null

2.3 进程状态与转换 ⭐⭐⭐

2.3.1 三种基本状态

                    ┌────────────────────┐│      创建态         │└────────┬───────────┘│ ① 创建完成▼┌──────────────────────────────┐时间片用完  │          就绪态               │┌──────────│   (Ready)                    ││          └──────┬───────────────────────┘│                 │ ② 被调度选中▼                 ▼
┌────────────────────────────┐
│         执行态               │
│       (Running)             │───→ 终止
└──┬─────────────────────────┘│ ③ 等待某事件发生(I/O)▼
┌────────────────────────────┐
│         阻塞态               │ 
│       (Blocked)             │
└────────────┬───────────────┘│ ④ 等待的事件发生(I/O完成)└────────→ 就绪态
状态转换 触发条件
① 创建 → 就绪 进程创建完毕,等待调度
② 就绪 → 执行 进程调度程序选中该进程
③ 执行 → 阻塞 进程等待某事件(如I/O操作)
④ 阻塞 → 就绪 等待的事件发生(如I/O完成)
⑤ 执行 → 就绪 时间片用完、被高优先级进程抢占
⑥ 执行 → 终止 进程正常执行完毕或被强制终止

2.3.2 七状态模型(含挂起)

                      ┌──────────┐│  创建态   │└────┬─────┘▼┌─────────┐  激活   ┌──────────┐│ 静止就绪  │←───────│ 活动就绪  ││ (挂起)   │────────→│          │└─────────┘  挂起   └────┬─────┘▼┌──────────┐激活        │  执行态   │┌─────────┐←───────│          │─────→ 终止│ 静止阻塞  │  挂起   └──┬───────┘│ (挂起)   │────────→  │└─────────┘            ▼激活        ┌──────────┐←──────────│ 活动阻塞  │└──────────┘

挂起(Suspend):将进程调出内存,放到外存(磁盘)上。目的:平衡系统负载,为紧急进程腾出内存。


2.4 进程控制

2.4.1 原语(Primitive)

原语是由若干指令构成的原子操作,执行期间不允许被中断。通过关中断实现。

原语 功能
创建原语 分配PID、PCB、资源,初始化后插入就绪队列
撤销原语 回收进程资源,移除PCB
阻塞原语 将执行态进程转为阻塞态,插入阻塞队列
唤醒原语 将阻塞队列中某进程移入就绪队列
挂起原语 将进程调出内存(挂起)
激活原语 将挂起的进程重新调入内存(激活)

2.4.2 fork() 与 exec()

函数 功能 关键特征
fork() 创建子进程(父进程的副本) 返回两次:父进程得到子PID,子进程得到0
exec() 替换进程映像 调用后不返回(除非出错)

2.5 线程 ⭐⭐

2.5.1 线程的引入

为什么需要线程?

  • 进程切换开销大(需切换内存映射、PCB等)
  • 应用需要"轻量级"的并发执行单位
  • 多处理器环境下需要真正并行

线程是CPU调度的基本单位,进程是资源拥有的基本单位。

对比项 进程 线程
调度单位 独立调度(传统OS) CPU调度的基本单位
资源拥有 拥有资源的基本单位 几乎不拥有资源
并发性 进程间可并发 同一进程内多线程可并发
系统开销 切换开销大 切换开销小(不切换地址空间)
独立性 相互独立 同一进程内线程共享地址空间
通信 需IPC机制 直接通过共享变量

2.5.2 线程的实现方式

用户级线程(ULT):                内核级线程(KLT):┌──────────────────┐            ┌──────────────────┐
│ 线程库(用户态)     │            │  进程             │
│ ┌────┐┌────┐┌────┐│            │ ┌────┐┌────┐┌────┐│
│ │T1  ││T2  ││T3  ││            │ │T1  ││T2  ││T3  ││
│ └──┬─┘└──┬─┘└──┬─┘│            │ └──┬─┘└──┬─┘└──┬─┘│
│    └─────┼─────┘  │            │    └─────┼─────┘  │
└──────────┼────────┘            └──────────┼────────┘│                                │┌──────┴──────┐              ┌──────────┴──────────┐│ 进程 (内核可见)│              │       内核           ││   =1个进程   │              │   T1  T2  T3       │└─────────────┘              │  每个线程都对内核可见 │└─────────────────────┘
对比项 用户级线程(ULT) 内核级线程(KLT)
线程管理 线程库(用户态) 内核
内核感知 不感知,认为是单线程进程 完全感知每个线程
切换开销 极小(无模式切换) 较大(需系统调用)
阻塞问题 一个线程阻塞,整个进程阻塞 一个线程阻塞,不影响其他线程
多处理器 无法利用多处理器 可以真正并行
代表系统 POSIX Pthreads(U模式) Windows、Linux

2.5.3 多线程模型

模型 说明 图示
多对一 多个ULT映射到1个KLT ULT₁ ULT₂ ULT₃ → KLT₁
一对一 每个ULT映射到1个KLT ULT₁→KLT₁, ULT₂→KLT₂
多对多 多个ULT映射到多个KLT ULT₁ ULT₂ ULT₃ → KLT₁ KLT₂

2.6 进程同步 ⭐⭐⭐

2.6.1 临界资源与临界区

临界资源(Critical Resource):一次仅允许一个进程访问的共享资源。

临界区(Critical Section):访问临界资源的代码段。

临界区访问需遵循的四个原则:while (true) {entry section;   // ① 进入区:检查是否可以进入critical section;// ② 临界区:访问临界资源exit section;    // ③ 退出区:释放临界资源remainder section;// ④ 剩余区:其余代码
}

临界区的四点要求:

要求 含义
空闲让进 临界资源空闲时,允许请求的进程立即进入
忙则等待 临界资源被占用时,其他进程必须等待
有限等待 进程等待时间有限,不会无限期等待
让权等待 不能进入临界区时,应立即释放CPU(阻塞而非忙等)

2.6.2 同步机制应遵循的原则

原则 说明
互斥(Mutual Exclusion) 同一时间只有一个进程在临界区
前进(Progress) 临界区空闲时,能快速选出下一个进入者
有限等待(Bounded Waiting) 进程请求进入后,等待次数有上限

2.6.3 软件实现方法

方法 思想 缺陷
单标志法 用一个turn变量轮流进入 必须轮流,违反"空闲让进"
双标志法(先检查) 用flag[i]表示想进入 可能同时进临界区(检查与修改不原子)
双标志法(后检查) 先设flag再检查对方 可能形成"互相谦让"导致饥饿
Peterson算法 turn+flag组合 仅适用于两个进程

2.6.4 硬件实现方法

方法 说明 优点 缺点
关中断 临界区执行时关中断 简单 仅单CPU、影响系统
Test-and-Set指令 原子地测试并设置lock 多CPU可用 忙等待、未满足让权等待
Swap指令 原子交换lock和key 多CPU可用 忙等待

2.7 信号量机制 ⭐⭐⭐

2.7.1 整型信号量

int S;  // 整型信号量
wait(S): while (S <= 0);  // 忙等S--;
signal(S): S++;

问题:wait操作中的while循环会导致忙等待,未满足"让权等待"。

2.7.2 记录型信号量 ⭐

typedef struct {int value;          // 资源数量queue process_list; // 等待该信号量的进程队列
} semaphore;void wait(semaphore S) {S.value--;if (S.value < 0) {block(S.process_list);  // 阻塞自己,释放CPU}
}void signal(semaphore S) {S.value++;if (S.value <= 0) {wakeup(S.process_list);  // 唤醒一个等待进程}
}
S.value 的含义 解释
S.value > 0 还有
S.value = 0 资源刚好用完,无等待进程
S.value < 0 有

2.7.3 AND型信号量

将进程需要的所有资源一次性全部分配给进程,要么全给,要么一个都不给。避免死锁。

2.7.4 信号量的应用

利用信号量实现互斥

semaphore mutex = 1;  // 互斥信号量,初值为1// 进程 Pi
wait(mutex);临界区
signal(mutex);

利用信号量实现前趋关系

S1 ──→ S2 的实现:semaphore S = 0;  // 初值为0S1:               S2:...              wait(S);signal(S);       ...

2.7.5 经典同步问题 ⭐⭐⭐

(1) 生产者—消费者问题

semaphore mutex = 1;    // 互斥访问缓冲区
semaphore empty = N;    // 空缓冲区数量
semaphore full = 0;     // 满缓冲区数量// 生产者
Producer() {while (true) {produce an item;wait(empty);     // ⚠️ 先申请资源,再申请互斥wait(mutex);put item into buffer;signal(mutex);signal(full);}
}// 消费者
Consumer() {while (true) {wait(full);      // ⚠️ 先申请资源,再申请互斥wait(mutex);take item from buffer;signal(mutex);signal(empty);consume the item;}
}

⚠️ 关键:多个wait操作顺序不能颠倒,必须先资源信号量,后互斥信号量,否则可能死锁。

(2) 读者—写者问题

// 读者优先版本
int readcount = 0;
semaphore rmutex = 1;    // 保护 readcount
semaphore wmutex = 1;    // 写者互斥// 读者
Reader() {wait(rmutex);if (readcount == 0) wait(wmutex);  // 第一个读者锁定写者readcount++;signal(rmutex);执行读操作wait(rmutex);readcount--;if (readcount == 0) signal(wmutex);  // 最后一个读者释放写者signal(rmutex);
}// 写者
Writer() {wait(wmutex);执行写操作signal(wmutex);
}

读者优先 vs 写者优先 vs 公平竞争:经典的三种策略,读者优先可能导致写者饥饿。

(3) 哲学家进餐问题

防止死锁的方法:

  1. 最多允许4个哲学家同时拿筷子
  2. 奇数号先拿左,偶数号先拿右
  3. 只有两支筷子都可用时才允许拿(AND型信号量)

(4) 理发师问题

核心:用信号量协调理发师(服务者)和顾客(消费者)之间的同步。


2.8 管程(Monitor)⭐

2.8.1 管程的定义

管程是由一组公共变量和对这些变量的操作组成的软件模块,它提供了互斥访问和同步控制的统一机制。

管程结构:┌─────────────────────────────┐
│         管程 Monitor         │
│                             │
│   共享变量声明(private)     │
│   ┌───────────────────┐     │
│   │   入口过程1        │     │  ← 一次只允许一个
│   │   入口过程2        │     │    进程进入管程
│   │   ...             │     │
│   └───────────────────┘     │
│                             │
│   条件变量: condition x, y   │
│   x.wait() / x.signal()     │
└─────────────────────────────┘

2.8.2 条件变量与同步

操作 功能
x.wait() 当前进程阻塞在条件变量x上,并释放管程
x.signal() 唤醒一个阻塞在x上的进程

⚠️ signal vs wait:管程的signal/wait与信号量的signal/wait不同!管程中signal若无等待进程则无效(无累积效应)。

2.8.3 信号量与管程的对比

对比项 信号量 管程
实现位置 分散在各进程代码中 集中在管程模块中
互斥 程序员显式调用wait/signal 编译器自动实现
安全性 容易出错(顺序、遗漏) 更安全,结构化好
代表语言 C(POSIX信号量) Java(synchronized)、Pascal+

2.9 进程通信(IPC)⭐

2.9.1 通信类型

类型 说明 实现方式
共享存储 进程共享一块内存空间 共享数据结构/共享存储区
消息传递 以格式化的消息进行通信 直接/间接通信
管道通信 连接读写进程的共享文件 无名管道/命名管道(FIFO)
Client-Server 通过网络通信 Socket、RPC

2.9.2 消息传递方式

直接通信:进程P ──send(Q, msg)──→ 进程Q进程Q ──receive(P, msg)──→ 进程P间接通信(信箱):进程P ──send(A, msg)──→ [信箱A] ──receive(A, msg)──→ 进程Q
对比项 直接通信 间接通信(信箱)
对称性 收发双方显式指定对方 通过信箱中转
链路 进程间自动建立 共享信箱时才建立连接
灵活性 耦合度高 耦合度低,更灵活

2.9.3 同步方式

通信方式 说明
阻塞send + 阻塞receive 发送方和接收方都阻塞(最可靠,紧密同步)
非阻塞send + 阻塞receive 发送方继续运行,接收方阻塞等待
非阻塞send + 非阻塞receive 双方都不阻塞

2.10 内核同步机制(拓展)

机制 说明 应用场景
原子操作 不可中断的硬件指令 简单计数、标志位
自旋锁 忙等待的锁 短临界区、中断上下文
互斥锁 阻塞等待的锁 长临界区、进程上下文
RCU 读-复制-更新 读多写少的场景(Linux内核)
读写锁 读共享,写互斥 读多写少的场景
完成变量 等待某事件完成的同步 子系统初始化等

⭐ 本章小结

知识点 核心内容
进程概念 程序+数据+PCB,动态、并发、独立
PCB 操作系统感知进程存在的唯一标志
三态模型 就绪→执行→阻塞(及挂起七态模型)
线程 CPU调度基本单位,进程是资源拥有单位
临界区 空闲让进、忙则等待、有限等待、让权等待
信号量 记录型信号量(无忙等),可用于互斥和同步
经典问题 生产者-消费者、读者-写者、哲学家进餐
管程 封装互斥和同步的高级语言机制
进程通信 共享存储、消息传递、管道、Socket

📖 参考资料

教材 作者 相关章节
《计算机操作系统》(第4版) 汤小丹 第2章 进程管理
《现代操作系统》 Tanenbaum 第2章 进程与线程
《UNIX环境高级编程》 Stevens 进程控制、进程关系

← 返回列表