生命周期、调度与进程间通信¶
进程从创建到退出会在运行、就绪和等待等状态之间迁移。调度器选择下一个执行流,IPC 则让相互隔离的进程交换数据与同步事件。本页把三者放在同一张状态图中理解。
进程状态机¶
最小模型包含:
- 就绪:具备运行条件,只缺 CPU。
- 运行:当前占用某个 CPU 核心。
- 等待/阻塞:等待 I/O、锁、定时器或消息,给 CPU 也无法继续。
区分就绪与等待是诊断的关键。一个任务长期不运行,可能是 CPU 竞争,也可能是等待事件;两者需要完全不同的优化。
fork、exec、wait 与 exit¶
Unix 风格进程创建把“复制当前进程”和“装入新程序”拆开:
fork创建子进程,子进程从近似相同的执行现场继续。- 子进程可调整文件描述符、工作目录和环境。
exec用新程序替换当前地址空间。- 父进程用
wait获取子进程退出状态并回收记录。
现代 fork 通常使用写时复制:父子先共享只读物理页,任一方写入时才复制。这使创建后立即 exec 的常见路径无需复制全部内存。
最小例子¶
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
execlp("printf", "printf", "child process\n", (char *)0);
_exit(127);
}
if (pid > 0) {
int status;
waitpid(pid, &status, 0);
}
return pid < 0;
}
exec 成功后不会返回;失败路径用 _exit,避免重复刷新从父进程复制来的用户态缓冲。
僵尸与孤儿¶
子进程退出后,内核仍需保存退出码和少量统计,直到父进程调用等待接口。这段状态常称僵尸。僵尸不再执行,也不占用原地址空间,但会占用进程标识和记录。
孤儿进程是父进程先退出、子进程仍运行的情形。系统会把它交给指定的收养者处理,确保最终有人回收退出状态。僵尸和孤儿不是同一概念。
调度评价指标¶
设作业到达时间为 \(a\),首次运行时间为 \(s\),完成时间为 \(f\),实际 CPU 服务时间为 \(b\):
- 周转时间关注从提交到完成。
- 响应时间关注第一次得到反馈。
- 等待时间关注在就绪队列中损失的时间。
- 吞吐量关注单位时间完成多少工作。
- 公平性和截止期限又是不同目标。
只报告平均值可能掩盖长尾。交互系统经常更关心高分位延迟,而不是平均响应时间。
经典调度策略¶
先来先服务¶
FCFS 简单、开销低,但一个长作业可让许多短作业等待,形成护航效应。它适合到达顺序具有业务意义或任务长度相近的场景,不适合强交互负载。
最短作业优先¶
若已知服务时间,SJF 可以最小化一组作业的平均等待时间。现实中未来运行时间通常未知,只能用历史行为估计;长作业还可能长期饥饿。
时间片轮转¶
RR 让就绪任务轮流运行一个时间片。时间片太长时接近 FCFS;太短时响应更快,但上下文切换比例增大。
若时间片为 \(q\),切换成本为 \(c\),忽略其他因素时,CPU 用于有效工作的比例上界近似为:
增大 \(q\) 提高该比例,却可能损害交互响应。这是典型吞吐—延迟权衡。
优先级与多级反馈队列¶
优先级调度允许重要任务先运行,但低优先级任务可能饥饿。老化机制会随等待时间提升优先级。
多级反馈队列尝试从行为推断任务类型:短时间主动阻塞的交互任务保留较高优先级,持续占用 CPU 的任务逐步下移。它不需要提前知道作业长度,但参数多,可能被工作负载行为影响。
公平调度¶
公平调度不一定按固定队列轮转,而是追踪任务获得的加权 CPU 服务,优先运行“欠服务”的任务。权重、睡眠唤醒、最小粒度和多核负载均衡都会影响实际行为。
实时调度¶
实时系统的“实时”指满足时间约束,不只是运行得快。
- 硬实时:错过截止期限可能导致系统失败。
- 软实时:错过期限会降低质量,但并非灾难。
对一组周期任务,任务 \(i\) 的执行时间为 \(C_i\)、周期为 \(T_i\),总利用率为:
\(U\le 1\) 只是单核可调度的必要直觉之一,并不自动保证所有调度策略都能满足期限;阻塞、抢占成本和任务相位也要计入分析。
多核调度的新问题¶
- 每核运行队列降低锁竞争,却需负载均衡。
- 任务迁移利用空闲核心,却损害缓存与 NUMA 局部性。
- 同时多线程共享核心资源,两个逻辑 CPU 并非两个完整物理核心。
- 高优先级任务等待低优先级持锁者会发生优先级反转,可用优先级继承等协议缓解。
调度不是独立模块;它和锁、内存放置、功耗及热管理都有关联。
为什么需要 IPC¶
进程地址空间隔离后,普通指针不能跨进程直接传递。IPC 为通信建立受控通道,同时规定数据边界、缓冲、阻塞和权限。
管道¶
管道提供字节流,适合有亲缘关系或由 shell 连接的单向数据流:
它简单且自带背压:缓冲区满时写者可能阻塞。局限是消息边界通常不保留,复杂拓扑和随机访问不方便。
消息队列与套接字¶
消息传递保留逻辑消息或通过协议组织字节流。Unix 域套接字适合本机进程,网络套接字可跨机器。内核可实施权限与缓冲管理,但数据复制、序列化和协议处理有成本。
共享内存¶
共享内存把同一物理页映射到多个进程,避免为大数据反复复制。它只解决“双方都能看到字节”,不自动解决同步、一致性和对象生命周期。
没有同步协议的共享内存只是更快地制造竞态。
信号与事件通知¶
信号适合异步通知少量事件,不适合承载大数据。处理器能调用的函数和可安全访问的状态受限制,复杂逻辑通常应转移到正常控制流中完成。
IPC 选择表¶
| 需求 | 优先考虑 | 主要代价 |
|---|---|---|
| shell 风格流式连接 | 管道 | 单向、无随机访问 |
| 本机结构化请求/响应 | Unix 域套接字或消息队列 | 序列化、复制、协议 |
| 跨主机通信 | 网络套接字 | 网络故障与部分失败 |
| 本机大块高吞吐数据 | 共享内存 | 同步与生命周期复杂 |
| 简单异步通知 | 信号/事件对象 | 信息量小、处理受限 |
背压与故障¶
任何有界通道都必须决定满时怎么办:阻塞生产者、丢弃、覆盖旧数据、扩容或向上游传播失败。无限缓冲只是把问题推迟到内存耗尽。
跨进程通信还要处理对端退出、半关闭、重复消息和超时。跨机器时故障模型更复杂:无法仅凭“没收到响应”区分请求未执行、执行中还是响应丢失。
自测¶
- 为什么就绪任务和阻塞任务的优化方向不同?
fork后立即exec时,写时复制节省了什么?- 时间片越短为什么不一定越公平或响应越好?
- 共享内存为何仍需要 IPC 协议?
- 如何区分僵尸进程和孤儿进程?
参考思路
就绪任务需要 CPU,阻塞任务在等待事件。写时复制避免复制随后会被 exec 丢弃的地址空间。过短时间片增加切换和缓存成本。共享页不定义同步、消息边界或所有权。僵尸已经退出等待回收,孤儿仍可运行但原父进程已退出。