Exit与Wait的同步

exit (子进程死) 和 wait (父进程收尸) 是典型的生产者-消费者模型。子进程生产“僵尸”,父进程消费“僵尸”。它们必须通过锁和唤醒机制完美配合,否则会导致父进程永久阻塞或子进程无法释放。

1. Wait 的逻辑 (消费者)

wait 的核心是一个无限循环,寻找状态为 ZOMBIE 的子进程。

  1. 持有锁: acquire(&p->lock)。必须锁住自己,因为 sleep 需要它。
  2. 扫描子进程: 遍历进程表,查找自己的子进程。
    • 情况 A (找到僵尸):
      • 提取退出码 (xstate)。
      • 释放子进程资源 (freeproc)。
      • 释放锁,返回 PID。
    • 情况 B (有子进程但没死):
      • 继续等待: sleep(p, &p->lock)
      • 关键: 父进程在自己的地址 (p) 上睡眠,等待子进程来唤醒。
    • 情况 C (没儿子): 返回 -1。

2. Exit 的逻辑 (生产者)

当子进程决定结束生命时:

  1. 持有锁: acquire(&p->lock)
  2. 变更状态: p->state = ZOMBIE
  3. 唤醒父进程: wakeup(p->parent)
    • 这就是父进程在 wait 中等待的信号。
  4. 过继孤儿: 如果自己有子进程,把它们过继给 initproc (PID 1),并唤醒 initproc (以防有僵尸需要回收)。
  5. 调度: sched()。跳进调度器,永远不再返回。

3. 锁的防死锁作用

为什么 wait 在 check 子进程时不需要获取子进程的锁?或者说,这里是如何避免 Lost Wakeup 的?

  • 其实 xv6 在 wait 扫描子进程列表时,需要获取子进程的锁来检查 np->parent
  • 原子性: wait 持有自己的锁进入 sleepexit 持有自己的锁进行 wakeup
  • 注意: xv6 的实现中,exit 会获取 p->lock,而 wakeup 需要获取目标进程的锁。这里存在复杂的锁顺序规则以避免死锁。