跳转至

CH3 补充读物:Process Concepts & Operations(读懂 fork/exec/wait)

1. 这份读物怎么用

这份文档是给你课后独立看进程代码用的,不是课堂提纲。

读完后你应该能回答:

  1. 进程在这套内核里由什么数据结构表示。
  2. 进程状态怎么变化,谁触发变化。
  3. fork/exec/wait/exit 这四个动作怎样拼成完整生命周期。

2. 先建立一个最重要的区分:Program vs Process

把这个区分想清楚,后面就不容易乱:

  1. Program:静态文件(例如 fs.img 里的 /ls/sh/cat)。
  2. Process:运行中的执行实体(有 PID、状态、地址空间、打开文件等)。

在当前项目里:

  1. user/*.c 被编译成 ELF。
  2. tools/mkfsimg.py 把 ELF 打包进镜像。
  3. exec 把 ELF 装载成正在运行的进程。

3. 进程在代码里长什么样:struct proc

直接看:kernel/include/proc.h

这个结构体就是你要找的“PCB 对应物”,关键字段包括:

  1. pid/state/parent/xstate:进程身份与关系。
  2. context:调度切换用寄存器上下文。
  3. pagetable/sz/trapframe:用户地址空间与执行现场。
  4. ofile[]/cwd:打开文件和当前目录。

当你调试进程问题,几乎都会落回这些字段。

4. 进程状态:从概念图到真实代码

状态枚举在 enum procstate

  1. UNUSED
  2. USED
  3. SLEEPING
  4. RUNNABLE
  5. RUNNING
  6. ZOMBIE

你可以把它看成一张“可执行状态机”。

关键转换路径:

  1. scheduler():挑 RUNNABLE 进程运行。
  2. yield():运行进程主动让出 CPU。
  3. sleep():进入阻塞等待。
  4. wakeup():唤醒阻塞进程。
  5. proc_exit():进程结束进入 ZOMBIE
  6. wait():父进程回收子进程,释放资源。

5. fork:如何“克隆”出子进程

调用链:

  1. 用户态 fork()
  2. sys_fork
  3. kernel/core/proc.c:fork()

核心动作:

  1. allocproc() 分配新 struct proc
  2. uvmcopy() 复制父进程用户内存。
  3. 复制 trapframe,但子进程返回值设为 0
  4. 复制打开文件引用与工作目录。
  5. 设置 parent,把子进程置为 RUNNABLE

你要记住两个返回值语义:

  1. 父进程看到的是子 PID。
  2. 子进程看到的是 0

6. exec:不是新建进程,而是替换运行镜像

调用链:

  1. 用户态 exec()
  2. sys_exec
  3. kernel/core/proc.c:exec()

核心行为:

  1. 创建新页表。
  2. 装载目标 ELF。
  3. 重新搭建用户栈并拷贝参数。
  4. 替换当前进程的 pagetable/sz/epc/sp
  5. 释放旧地址空间。

所以 exec 后:

  1. PID 还是原来的。
  2. 运行程序变成新程序。
  3. 旧用户态镜像被替换。

7. waitexit:父子同步与回收闭环

这是最容易“概念懂了,代码没懂”的一段。

在这套实现里:

  1. 子进程 proc_exit(status) 后进入 ZOMBIE
  2. 子进程唤醒可能在等它的父进程。
  3. 父进程 wait(addr) 扫描子进程表。
  4. 找到 ZOMBIE 后取退出码并回收资源。

这就是“退出不等于立刻消失,必须被父进程回收”的实际机制。

8. orphan 问题是怎么处理的

如果父进程先退出,子进程会 orphan。

本项目在 proc_exit() 里做了标准处理:

  1. 把这些子进程重挂到 initproc
  2. 由 init 继续承担回收责任。

这一步是避免“没人回收导致永久悬挂”的关键。

9. 建议你亲手走一遍的观察链

以 shell 启动一条普通命令为例:

  1. shell 调 fork
  2. 子进程 exec 命令程序。
  3. 父进程 wait
  4. 子进程退出,父进程被唤醒并回收。

对照文件:

  1. user/sh.c
  2. kernel/core/syscall.c
  3. kernel/core/proc.c

10. 最常见误解

  1. fork 误解成“创建全新无关进程”。
  2. exec 误解成“再开一个进程”。
  3. wait 误解成“仅仅睡一会儿”。
  4. 忽略 ZOMBIE 的存在和意义。

11. 最小自测(不交作业,仅自检)

你可以自问这 6 个问题:

  1. struct proc 里哪几个字段最能体现“进程是动态实体”?
  2. 子进程为什么在 fork 后返回 0
  3. exec 为什么 PID 不变?
  4. wait 在没有子进程可回收时会怎样?
  5. ZOMBIE 为什么必须存在?
  6. orphan 最终由谁处理?

如果你能给出具体函数和字段位置,CH3 就基本掌握了。