## 僵尸/孤儿/守护进程 ## 判别规则(核心,唯一要记的) 1. **看"谁死了 + 谁收尸"**:子进程已退出、父进程还活着且**没 wait 回收** → **僵尸**;父进程已 `wait` → 不是(已回收) 2. **看"父先死 + 子还活"**:父进程先退出、子进程还活着 → **孤儿**(被 init 收养继续跑) 3. **看"是否脱离终端"**:主动 `setsid()` 脱离终端、后台运行、无控制终端 → **守护进程**;还连着终端 → 不是 一句话:**"僵尸是子死了没人收尸,孤儿是父先死了孩子被 init 收养,守护是主动脱离终端独立后台跑。"** ## 抽象描述(一句话本质) 三者本质是**进程与父进程关系**的不同状态:僵尸=子死未收尸;孤儿=父先死子被收养;守护=主动脱离终端后台运行。 ## 易混对比 | | 僵尸 Zombie | 孤儿 Orphan | 守护 Daemon | |---|---|---|---| | 谁死了 | 子进程死,父活着 | 父死,子活着 | 都没死 | | 状态本质 | 子进程残留 PCB 未被回收 | 子进程被 init 收养继续运行 | 主动脱离终端后台运行 | | 谁管它 | 等父进程 wait 回收 | init 收养 | 自己独立 | | 危害 | 占 PCB,父不回收会泄漏 | 无害 | 无害 | | 是否"死了" | 子已死 | 父已死 | 谁都没死 | ## 僵尸进程三个解法 | 方法 | 原理 | 适用场景 | |------|------|---------| | 父进程 wait()/waitpid() | 父主动回收子进程状态信息 | 父进程有子进程生命周期管理 | | 双 fork | 子 fork 孙,自己立刻退出,孙变孤儿被 init 自动回收 | 父进程不想管子进程回收 | | signal(SIGCHLD, SIG_IGN) | 告诉内核忽略退出状态,内核自动回收 | 父进程不关心子进程退出 | ## 守护进程创建步骤(标准流程) 1. `fork()` → 子进程,父进程退出(脱离终端控制) 2. `setsid()` → 创建新会话,成为 session leader,脱离控制终端 3. `fork()` → 再 fork 一次,孙子进程运行,父进程退出(确保不是 session leader,防止重新获取终端) 4. `chdir("/")` → 改工作目录,防止占用可卸载的文件系统 5. `umask(0)` → 重置文件权限掩码 6. `close(0/1/2)` → 关闭标准输入输出,重定向到 `/dev/null` **两个 fork 的目的**:第一次 fork + setsid 让进程成为会话 leader 脱离终端;第二次 fork 让进程**不再是会话 leader**(只有 leader 能重新获取控制终端),彻底断绝与终端的联系。 ## 变体验证(3 题,全过=学会) 1. 父进程 `fork()` 后子进程调 `_exit(0)` 退出,父进程一直没调 `waitpid()` → **僵尸进程**(子死、父活、未回收) 2. 陷阱题:父进程 alive,子进程用 `daemon()/setsid()` 脱离终端后台运行 → **守护进程**(关键看 setsid,跟父死不死无关) 3. 陷阱题:终端按 `Ctrl+Z` 挂起程序再 `bg` 后台 → **不是守护进程**(还在终端会话里,能收到 SIGHUP,终端关了进程也会被杀) ## 口述要点(面试怎么讲) - **结论先行**:僵尸=子死未收尸,孤儿=父死被 init 收养,守护=脱离终端后台跑 - **僵尸为什么存在**:子进程退出后内核保留其 PCB 信息供父进程查询(exit code/资源使用情况),父进程不调 wait 回收则 PCB 一直占着。解法:wait 回收 / 双 fork / signal(SIGCHLD, SIG_IGN) - **孤儿怎么处理**:被 init(PID 1)收养,init 等其执行完后调 wait 回收,不会变僵尸。注意用词:孤儿是"被收养**继续运行**",不是被"回收清除"(那是僵尸的处理) - **为什么需要 setsid**:创建新会话,成为 session leader,脱离原来的控制终端;终端关了不影响守护进程(不收 SIGHUP)。直接 fork 的子进程仍连着终端,会随终端关闭被杀 - **易错点**:以为守护进程必须父进程先死(其实关键看 setsid);ctrl+Z+bg 当成守护(其实只是作业管理,没脱离终端) ## 关系网络 | 相邻概念 | 关系 | 孤立理解会犯的错 | |---|---|---| | 进程 vs 线程 | 僵尸/孤儿是进程特有,线程共享地址空间不存在"收养" | 把僵尸概念套到线程上 | | exit() vs pthread_exit | exit 产生僵尸可能,pthread_exit 不变僵尸 | 混淆两者效果 | | 信号 SIGCHLD | 父进程收到 SIGCHLD 应调 wait 回收,可设 SIG_IGN 跳过 | 不处理 SIGCHLD 堆积僵尸 | | IPC 通信 | 守护进程常作为服务端接收 IPC 连接 | 把守护进程当普通子进程 | | RTOS 任务 | RTOS 无进程/僵尸概念(任务退出 OS 自动回收) | 把 Linux 进程概念搬进 RTOS | ## 学习日期 2026-08-26 --- _每主题一页,复习时只翻本目录。_