wait → 不是(已回收)setsid() 脱离终端、后台运行、无控制终端 → 守护进程;还连着终端 → 不是一句话:"僵尸是子死了没人收尸,孤儿是父先死了孩子被 init 收养,守护是主动脱离终端独立后台跑。"
三者本质是进程与父进程关系的不同状态:僵尸=子死未收尸;孤儿=父先死子被收养;守护=主动脱离终端后台运行。
| 僵尸 Zombie | 孤儿 Orphan | 守护 Daemon | |
|---|---|---|---|
| 谁死了 | 子进程死,父活着 | 父死,子活着 | 都没死 |
| 状态本质 | 子进程残留 PCB 未被回收 | 子进程被 init 收养继续运行 | 主动脱离终端后台运行 |
| 谁管它 | 等父进程 wait 回收 | init 收养 | 自己独立 |
| 危害 | 占 PCB,父不回收会泄漏 | 无害 | 无害 |
| 是否"死了" | 子已死 | 父已死 | 谁都没死 |
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 父进程 wait()/waitpid() | 父主动回收子进程状态信息 | 父进程有子进程生命周期管理 |
| 双 fork | 子 fork 孙,自己立刻退出,孙变孤儿被 init 自动回收 | 父进程不想管子进程回收 |
| signal(SIGCHLD, SIG_IGN) | 告诉内核忽略退出状态,内核自动回收 | 父进程不关心子进程退出 |
fork() → 子进程,父进程退出(脱离终端控制)setsid() → 创建新会话,成为 session leader,脱离控制终端fork() → 再 fork 一次,孙子进程运行,父进程退出(确保不是 session leader,防止重新获取终端)chdir("/") → 改工作目录,防止占用可卸载的文件系统umask(0) → 重置文件权限掩码close(0/1/2) → 关闭标准输入输出,重定向到 /dev/null两个 fork 的目的:第一次 fork + setsid 让进程成为会话 leader 脱离终端;第二次 fork 让进程不再是会话 leader(只有 leader 能重新获取控制终端),彻底断绝与终端的联系。
fork() 后子进程调 _exit(0) 退出,父进程一直没调 waitpid() → 僵尸进程(子死、父活、未回收)daemon()/setsid() 脱离终端后台运行 → 守护进程(关键看 setsid,跟父死不死无关)Ctrl+Z 挂起程序再 bg 后台 → 不是守护进程(还在终端会话里,能收到 SIGHUP,终端关了进程也会被杀)| 相邻概念 | 关系 | 孤立理解会犯的错 |
|---|---|---|
| 进程 vs 线程 | 僵尸/孤儿是进程特有,线程共享地址空间不存在"收养" | 把僵尸概念套到线程上 |
| exit() vs pthread_exit | exit 产生僵尸可能,pthread_exit 不变僵尸 | 混淆两者效果 |
| 信号 SIGCHLD | 父进程收到 SIGCHLD 应调 wait 回收,可设 SIG_IGN 跳过 | 不处理 SIGCHLD 堆积僵尸 |
| IPC 通信 | 守护进程常作为服务端接收 IPC 连接 | 把守护进程当普通子进程 |
| RTOS 任务 | RTOS 无进程/僵尸概念(任务退出 OS 自动回收) | 把 Linux 进程概念搬进 RTOS |
2026-08-26
每主题一页,复习时只翻本目录。