假设我有一个进程,它只生成一个子进程。现在,当父进程由于某种原因(正常或不正常,通过kill, ^C,断言失败或其他任何原因)退出时,我希望子进程死亡。如何正确地做到这一点?


关于stackoverflow的一些类似问题:

(前面问过)当父进程退出的时候,我怎么能让子进程退出呢? (稍后问)用fork()创建的子进程会在父进程被杀死时自动被杀死吗?


关于Windows的stackoverflow也有类似的问题:

如何在Windows中自动销毁子进程? 当父进程被杀死时,杀死子进程


当前回答

在POSIX中,exit(), _exit()和_exit()函数被定义为:

如果该进程是控制进程,则SIGHUP信号应发送给控制终端的前台进程组中属于呼叫进程的每个进程。

因此,如果您安排父进程作为其进程组的控制进程,那么当父进程退出时,子进程应该得到一个SIGHUP信号。我不确定当父节点崩溃时是否会发生这种情况,但我认为确实会发生。当然,对于非崩溃的情况,它应该可以正常工作。

请注意,您可能必须阅读大量的小字——包括基本定义(Definitions)部分,以及exit()和setsid()和setpgrp()的系统服务信息——才能了解完整的情况。(我也是!)

其他回答

我正在尝试解决同样的问题,但由于我的程序必须运行在OS X上,所以只运行linux的解决方案对我不起作用。

我得到了与本页其他人相同的结论——当父母去世时,没有一种与posix兼容的方式来通知孩子。所以我想出了次好的办法——让孩子投票。

当父进程(由于任何原因)终止时,子进程将变成进程1。如果子进程只是定期轮询,它可以检查父进程是否为1。如果是,子进程应该退出。

这不是很好,但它可以工作,并且比本文其他地方建议的TCP套接字/锁文件轮询解决方案更容易。

在POSIX中,exit(), _exit()和_exit()函数被定义为:

如果该进程是控制进程,则SIGHUP信号应发送给控制终端的前台进程组中属于呼叫进程的每个进程。

因此,如果您安排父进程作为其进程组的控制进程,那么当父进程退出时,子进程应该得到一个SIGHUP信号。我不确定当父节点崩溃时是否会发生这种情况,但我认为确实会发生。当然,对于非崩溃的情况,它应该可以正常工作。

请注意,您可能必须阅读大量的小字——包括基本定义(Definitions)部分,以及exit()和setsid()和setpgrp()的系统服务信息——才能了解完整的情况。(我也是!)

如果你无法修改子进程,你可以尝试以下方法:

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(pipes[0], STDIN_FILENO); /* Use reader end as stdin (fixed per  maxschlepzig */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

这将在启用作业控制的shell进程中运行子进程。子进程在后台生成。shell等待换行符(或EOF),然后终止子进程。

当父进程死亡时——不管是什么原因——它将关闭管道的一端。子shell将从read中获得一个EOF,并继续杀死后台的子进程。

为了完整起见。在macOS上你可以使用kqueue:

void noteProcDeath(
    CFFileDescriptorRef fdref, 
    CFOptionFlags callBackTypes, 
    void* info) 
{
    // LOG_DEBUG(@"noteProcDeath... ");

    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    unsigned int dead_pid = (unsigned int)kev.ident;

    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example

    int our_pid = getpid();
    // when our parent dies we die as well.. 
    LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
    exit(EXIT_SUCCESS);
}


void suicide_if_we_become_a_zombie(int parent_pid) {
    // int parent_pid = getppid();
    // int our_pid = getpid();
    // LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);

    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

安装一个陷阱处理程序来捕获SIGINT,如果你的子进程还活着,它就会杀死它,尽管其他的帖子是正确的,它不会捕获SIGKILL。

以独占访问的方式打开一个.lockfile,并让子进程尝试打开它——如果打开成功,子进程应该退出