进程 — 操作系统卖的那件叫隔离的商品
一句话总结
进程是操作系统出售的一种“让程序以为自己独占一台计算机”的幻觉,其核心是独立地址空间。
为什么需要了解这些
一台机器运行多个程序必须保证两点:一个程序的错误不能破坏其他程序的内存;程序无需关心自己何时暂停、何时恢复。进程同时提供这两种抽象。每个进程都拥有从地址 0 开始的虚拟地址空间,短暂借用 CPU 后再归还,却察觉不到这一过程。
工作原理
操作系统为每个进程维护一个 PCB(Process Control Block),大致包含:
- 进程标识符与状态(创建、就绪、运行、等待、结束)
- 程序计数器与寄存器值——恢复执行时需要还原的内容
- 内存管理信息——页表位置
- 打开的文件列表、所有者和调度信息
上下文切换会把当前进程的寄存器保存到 PCB,再恢复下一个进程的寄存器。移动几十个寄存器本身很快,真正成本发生在随后:新进程开始运行时,缓存和 TLB 中前一进程的数据失效,引发大量未命中。这种间接成本远大于直接成本。
Linux 创建进程的方式也很有特点。fork() 完整复制父进程,随后 exec() 用新程序覆盖它。实际复制采用写时复制(copy-on-write):父子共享物理页,直到一方写入时才复制该页。在立即调用 exec() 的常见模式中,几乎不会发生实际复制。
进程状态转换中常被混淆的是僵尸进程。子进程结束后,若父进程尚未通过 wait() 收取退出状态,只剩 PCB 的进程就成为僵尸。它几乎不占内存,却占用进程表项;大量积累后将无法创建新进程。反之,父进程先退出时,子进程成为孤儿,由 init(或容器中的 1 号进程)收养。容器直接把应用作为 1 号进程时容易积累僵尸,因为 1 号进程负责回收孤儿,而普通应用通常没有相应代码。
实际工作中的表现
在 ps 输出的 STAT 列看到 Z,就是上述状态。容器镜像加入 tini 等小型 init,或启用运行时 init 选项,是标准做法。D 状态(不可中断睡眠)也值得认识:进程通常正在等待磁盘或网络文件系统 I/O,即使用 kill -9 也无法立即终止。
后续测验将确认什么
确认你能解释为什么 fork 看似昂贵却通常很便宜,以及上下文切换的真正成本在哪里。