Exit与Wait的同步
exit (子进程死) 和 wait (父进程收尸) 是典型的生产者-消费者模型。子进程生产“僵尸”,父进程消费“僵尸”。它们必须通过锁和唤醒机制完美配合,否则会导致父进程永久阻塞或子进程无法释放。
1. Wait 的逻辑 (消费者)
wait 的核心是一个无限循环,寻找状态为 ZOMBIE 的子进程。
- 持有锁:
acquire(&p->lock)。必须锁住自己,因为sleep需要它。 - 扫描子进程: 遍历进程表,查找自己的子进程。
- 情况 A (找到僵尸):
- 提取退出码 (
xstate)。 - 释放子进程资源 (
freeproc)。 - 释放锁,返回 PID。
- 提取退出码 (
- 情况 B (有子进程但没死):
- 继续等待:
sleep(p, &p->lock)。 - 关键: 父进程在自己的地址 (
p) 上睡眠,等待子进程来唤醒。
- 继续等待:
- 情况 C (没儿子): 返回 -1。
- 情况 A (找到僵尸):
2. Exit 的逻辑 (生产者)
当子进程决定结束生命时:
- 持有锁:
acquire(&p->lock)。 - 变更状态:
p->state = ZOMBIE。 - 唤醒父进程:
wakeup(p->parent)。- 这就是父进程在
wait中等待的信号。
- 这就是父进程在
- 过继孤儿: 如果自己有子进程,把它们过继给
initproc(PID 1),并唤醒initproc(以防有僵尸需要回收)。 - 调度:
sched()。跳进调度器,永远不再返回。
3. 锁的防死锁作用
为什么 wait 在 check 子进程时不需要获取子进程的锁?或者说,这里是如何避免 Lost Wakeup 的?
- 其实 xv6 在
wait扫描子进程列表时,需要获取子进程的锁来检查np->parent。 - 原子性:
wait持有自己的锁进入sleep。exit持有自己的锁进行wakeup。 - 注意: xv6 的实现中,
exit会获取p->lock,而wakeup需要获取目标进程的锁。这里存在复杂的锁顺序规则以避免死锁。