如何实现非父子进程间的进程终止事件驱动通知?
问题概述
需要监控一个非父子关系的进程,当该进程无论正常终止还是崩溃(如被kill -9)时,监控进程能收到事件通知,且要求采用事件驱动方式,避免轮询/proc/<PID>目录。
已尝试方案的问题复盘
- 对
/proc/<PID>目录使用epoll():epoll无法直接监听目录,此方案不可行。 - 考虑
inotify:误以为其不支持系统文件/目录,但实际可监控/proc下的资源(后续会说明),但之前未尝试。 - 临时文件机制:进程崩溃时无法主动清理文件,无法触发终止通知。
- 子进程等待机制(如
waitpid()):仅适用于父子进程关系,不符合场景。 - Python
NamedTemporaryFile():进程终止后文件未自动删除,无法依赖此判断进程状态。 - 监听
/proc/<PID>下可打开文件的epoll事件:会产生大量无效事件,不符合需求。
可行事件驱动方案
方案1:使用pidfd_open() + epoll(推荐,Linux 5.3+)
这是最直接且高效的方案,Linux 5.3及以上版本提供了pidfd_open()系统调用,可创建一个与目标PID绑定的文件描述符(PID FD),之后通过epoll监听该FD的EPOLLIN事件:
- 当被监控进程终止时,PID FD会触发
EPOLLIN事件,监控进程可通过read()读取退出状态(或直接感知事件)。 - 无论进程是正常退出、崩溃还是被强制杀死,都会触发事件,完全满足需求。
示例代码(C语言)
#include <sys/epoll.h> #include <sys/syscall.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #define PIDFD_OPEN 438 // 对应系统调用号,不同架构可能有差异 int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pid>\n", argv[0]); exit(1); } pid_t target_pid = atoi(argv[1]); // 创建PID文件描述符 int pidfd = syscall(PIDFD_OPEN, target_pid, 0); if (pidfd == -1) { perror("pidfd_open failed"); exit(1); } // 初始化epoll int epfd = epoll_create1(0); if (epfd == -1) { perror("epoll_create1 failed"); close(pidfd); exit(1); } struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = pidfd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, pidfd, &ev) == -1) { perror("epoll_ctl failed"); close(pidfd); close(epfd); exit(1); } printf("Monitoring PID %d...\n", target_pid); struct epoll_event events[1]; int n = epoll_wait(epfd, events, 1, -1); if (n == -1) { perror("epoll_wait failed"); } else { printf("Process %d has terminated\n", target_pid); // 可选:读取退出状态 siginfo_t info; if (syscall(SYS_pidfd_getfd, pidfd, &info, 0) == 0) { printf("Exit status: %d\n", info.si_status); } } close(pidfd); close(epfd); return 0; }
Python 实现思路
可通过ctypes调用pidfd_open系统调用,结合select或asyncio监听事件,示例框架:
import ctypes import select libc = ctypes.CDLL("libc.so.6") PIDFD_OPEN = 438 def pidfd_open(pid): return libc.syscall(PIDFD_OPEN, pid, 0) target_pid = 12345 pidfd = pidfd_open(target_pid) epoll = select.epoll() epoll.register(pidfd, select.EPOLLIN) print(f"Monitoring PID {target_pid}...") events = epoll.poll() for fd, event in events: if fd == pidfd and event & select.EPOLLIN: print(f"Process {target_pid} terminated") epoll.unregister(pidfd) epoll.close() libc.close(pidfd)
方案2:使用inotify监控/proc/<PID>目录(兼容旧Linux版本)
虽然/proc是虚拟文件系统,但inotify可以监控其下的目录删除事件:
- 创建
inotify实例,添加对/proc/<PID>目录的IN_DELETE_SELF事件监听。 - 当被监控进程终止时,
/proc/<PID>目录会被删除,触发IN_DELETE_SELF事件,监控进程即可收到通知。
注意事项
- 需要确保监控时
/proc/<PID>目录存在(可先检查一次),避免添加监听失败。 - 部分系统中
inotify对/proc的支持可能有差异,但大多数主流发行版(如Ubuntu、CentOS)都支持。
示例代码(C语言)
#include <sys/inotify.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF_LEN 1024 int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pid>\n", argv[0]); exit(1); } char proc_path[256]; snprintf(proc_path, sizeof(proc_path), "/proc/%s", argv[1]); int fd = inotify_init(); if (fd == -1) { perror("inotify_init failed"); exit(1); } int wd = inotify_add_watch(fd, proc_path, IN_DELETE_SELF); if (wd == -1) { perror("inotify_add_watch failed"); close(fd); exit(1); } printf("Monitoring %s...\n", proc_path); char buf[BUF_LEN]; ssize_t len = read(fd, buf, BUF_LEN); if (len == -1) { perror("read failed"); } else { struct inotify_event *event = (struct inotify_event *)buf; if (event->mask & IN_DELETE_SELF) { printf("Process %s has terminated\n", argv[1]); } } inotify_rm_watch(fd, wd); close(fd); return 0; }
方案3:使用ptrace(不推荐,侵入性强)
通过ptrace附加到目标进程,当进程终止时会触发SIGCHLD信号给监控进程。但此方案存在明显缺点:
- 会暂停目标进程,需要调用
ptrace(PTRACE_CONT)恢复,影响进程运行。 - 需要额外权限(如
CAP_SYS_PTRACE),且可能与其他监控工具冲突。 - 侵入性强,仅在上述方案不可用时考虑。
总结
- 优先选择**
pidfd_open()+epoll**方案,高效且无侵入性,是Linux内核提供的原生解决方案。 - 若系统版本较低,可采用**
inotify监控/proc/<PID>**目录的方式,实现事件驱动的进程终止通知。
内容的提问来源于stack exchange,提问作者dbeberman
相关产品推荐
相关产品推荐

