子进程监听父进程退出事件(MacOS/Linux非轮询方案)
监听Detached子进程中父进程退出的无轮询方案
这个问题我之前也折腾过,detached子进程没法用常规的wait()/waitid()监听父进程退出——因为这类系统调用只能监听自己的直接子进程,而detached操作(比如调用setsid()或者后台化)会让子进程脱离原父进程的进程组,甚至被init/launchd收养,父子关系不复存在,这就是你用waitid()得到255退出码的核心原因。
下面分macOS和Linux给出无需轮询的可行方案:
Linux系统:利用/proc文件系统 + poll()
Linux的/proc文件系统会为每个进程维护一个专属目录,当进程退出时,对应的/proc/[PID]目录会被立即移除。我们可以打开父进程对应的/proc文件(比如/proc/[PID]/stat),然后用poll()监听这个文件描述符的状态变化——当父进程退出时,该fd会触发POLLHUP(挂起)事件,子进程就能立即收到通知。
示例代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <poll.h> #include <string.h> #define PARENT_PID 12345 // 替换为实际的父进程PID #define LOCK_FILE "/tmp/myapp.lock" void cleanup_lock() { printf("父进程已退出,开始清理锁文件...\n"); if (unlink(LOCK_FILE) == -1) { perror("删除锁文件失败"); } } int main() { char proc_path[64]; snprintf(proc_path, sizeof(proc_path), "/proc/%d/stat", PARENT_PID); // 打开父进程的/proc文件 int fd = open(proc_path, O_RDONLY); if (fd == -1) { perror("打开/proc文件失败(父进程可能已退出)"); cleanup_lock(); exit(EXIT_FAILURE); } // 配置poll监听 struct pollfd pfd = { .fd = fd, .events = POLLHUP | POLLERR }; printf("等待父进程%d退出...\n", PARENT_PID); // 无限等待事件触发 int ret = poll(&pfd, 1, -1); if (ret == -1) { perror("poll调用失败"); close(fd); exit(EXIT_FAILURE); } // 父进程退出,触发清理 if (pfd.revents & (POLLHUP | POLLERR)) { cleanup_lock(); } close(fd); return EXIT_SUCCESS; }
注意事项
- 如果父进程在子进程启动前就已经退出,
open()会直接失败,这时候可以在错误处理中直接执行锁文件清理。 - 无需担心PID复用问题:父进程退出后,
/proc/[PID]会被立即删除,fd的挂起事件会瞬间触发,不会等到新进程复用该PID。
macOS系统:利用kqueue的EVFILT_PROC事件
macOS没有/proc文件系统,但提供了kqueue机制,可以通过EVFILT_PROC事件监听任意进程的退出状态。只需指定父进程PID和NOTE_EXIT标志,kqueue就会在父进程退出时主动通知子进程,完全无需轮询。
示例代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/event.h> #include <sys/time.h> #include <string.h> #define PARENT_PID 12345 // 替换为实际的父进程PID #define LOCK_FILE "/tmp/myapp.lock" void cleanup_lock() { printf("父进程已退出,开始清理锁文件...\n"); if (unlink(LOCK_FILE) == -1) { perror("删除锁文件失败"); } } int main() { // 创建kqueue实例 int kq = kqueue(); if (kq == -1) { perror("创建kqueue失败"); exit(EXIT_FAILURE); } // 配置监听事件:监听父进程退出 struct kevent event; EV_SET(&event, PARENT_PID, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, NULL); if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { perror("注册监听事件失败(父进程可能已退出)"); cleanup_lock(); close(kq); exit(EXIT_FAILURE); } printf("等待父进程%d退出...\n", PARENT_PID); // 等待事件触发 struct kevent event_list[1]; int nev = kevent(kq, NULL, 0, event_list, 1, NULL); if (nev == -1) { perror("kevent等待失败"); close(kq); exit(EXIT_FAILURE); } // 确认是父进程退出事件 if (event_list[0].filter == EVFILT_PROC && !(event_list[0].flags & EV_ERROR)) { cleanup_lock(); } close(kq); return EXIT_SUCCESS; }
注意事项
- 监听其他进程需要适当的权限:同一用户下的进程无需额外权限,跨用户监听可能需要root权限。
- 如果父进程已经退出,
kevent()注册事件会失败,此时可以直接执行锁文件清理逻辑。
内容的提问来源于stack exchange,提问作者Alexander Mills
相关产品推荐
相关产品推荐

