You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

子进程监听父进程退出事件(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:46:53