不能用简单fork+waitpid实现互相看护,因子进程间无父子关系,无法waitpid对方,且kill-9时另一方无法感知;真正的互相看护需通过unix域套接字心跳或prctl(pr_set_pdeathsig)配合kill(pid,0)探测并加文件锁防重复拉起。

为什么不能用简单的 fork + waitpid 实现互相看护
直接在父进程中 fork 两次、然后用 waitpid(-1, &status, 0) 监听任意子进程退出,看似可行,但会掉进两个坑:一是当一个子进程被 kill -9 时,另一个子进程无法感知(它不是父进程,没权限 waitpid);二是两个子进程彼此不是父子关系,getppid() 查到的都是原始父进程,无法形成闭环监控。真正的互相看护必须让每个子进程都具备“观察对方生死 + 自行拉起”的能力。
用 Unix domain socket 做进程间心跳检测
核心思路是让两个子进程通过本地 socket 持续交换心跳包,一旦读超时或连接断开,就判定对方已死,立即 fork 重启它。这比依赖信号或文件锁更可靠,也不受 SIGCHLD 传递限制。
- 启动时,主进程先创建一个
AF_UNIXsocket(路径如/tmp/watchdog.sock),并绑定监听 - 第一个子进程
fork后,连接该 socket 并进入循环:每 500ms 发一次"PING",同时阻塞recv等待对方响应;若recv返回 0 或 -1(且errno != EINTR),说明对端已退出,立刻execl重启另一个子进程 - 第二个子进程做同样事,只是初始连接顺序相反,或用
SOCK_DGRAM避免连接状态管理 - 注意:两个子进程要分别设置
sigaction(SIGPIPE, &sa, nullptr),否则发包时对端已死会触发SIGPIPE导致自身退出
用 prctl(PR_SET_PDEATHSIG) 让子进程感知父进程死亡
这个 Linux 特有接口能让子进程在“原父进程”退出时收到指定信号(如 SIGUSR1)。但它不适用于“互相看护”,因为两个子进程的父进程是同一个,不是彼此。不过可以借它实现“兜底保活”:每个子进程调用 prctl(PR_SET_PDEATHSIG, SIGUSR1),并在信号处理函数中检查对方是否还活着(比如通过 kill(pid_of_other, 0)),如果对方已不存在,就立即 fork 拉起。
- 必须在
fork后、exec前调用prctl,否则新进程可能已切换用户或权限丢失 -
kill(pid, 0)只能判断进程是否存在,不能区分是僵死还是运行中,需配合waitpid(pid, &s, WNOHANG)清理僵尸 - 若两个子进程都用此法,需加文件锁(
flock(/tmp/watchdog.lock))避免同时尝试拉起导致重复进程
实际部署时最易忽略的三个点
第一,子进程必须忽略 SIGHUP 和 SIGINT,否则终端关闭或 Ctrl+C 会误杀整个看护链;第二,重启逻辑里不要用 system(),它会继承当前环境并可能卡住,改用 fork + execv 更可控;第三,/tmp 下的 socket 或 lock 文件要在每次启动前 unlink,否则残留会导致新进程连接失败或锁竞争。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











