假设我有一个进程,它只生成一个子进程。现在,当父进程由于某种原因(正常或不正常,通过kill, ^C,断言失败或其他任何原因)退出时,我希望子进程死亡。如何正确地做到这一点?
关于stackoverflow的一些类似问题:
(前面问过)当父进程退出的时候,我怎么能让子进程退出呢? (稍后问)用fork()创建的子进程会在父进程被杀死时自动被杀死吗?
关于Windows的stackoverflow也有类似的问题:
如何在Windows中自动销毁子进程? 当父进程被杀死时,杀死子进程
假设我有一个进程,它只生成一个子进程。现在,当父进程由于某种原因(正常或不正常,通过kill, ^C,断言失败或其他任何原因)退出时,我希望子进程死亡。如何正确地做到这一点?
关于stackoverflow的一些类似问题:
(前面问过)当父进程退出的时候,我怎么能让子进程退出呢? (稍后问)用fork()创建的子进程会在父进程被杀死时自动被杀死吗?
关于Windows的stackoverflow也有类似的问题:
如何在Windows中自动销毁子进程? 当父进程被杀死时,杀死子进程
当前回答
为了完整起见。在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);
}
其他回答
我认为一种快速而又不那么简单的方法是在子节点和父节点之间创建一个管道。当父节点退出时,子节点将收到一个SIGPIPE。
我认为不可能保证只使用标准POSIX调用。就像现实生活一样,一旦孩子被孕育出来,它就有了自己的生命。
父进程可以捕获大多数可能的终止事件,并尝试在此时终止子进程,但总有一些无法捕获。
例如,没有进程可以捕获SIGKILL。当内核处理这个信号时,它将杀死指定的进程,而不通知该进程。
扩展一下类比——唯一的另一种标准方式是,当孩子发现自己不再有父母时自杀。
使用prctl(2)有一种linux独有的方法——请参阅其他答案。
如果你发送一个信号到pid 0,使用for实例
kill(0, 2); /* SIGINT */
该信号被发送到整个进程组,从而有效地杀死了子进程。
你可以很容易地测试它,比如:
(cat && kill 0) | python
如果你然后按^D,你会看到文本“Terminated”,这表明Python解释器确实已经被杀死,而不是因为stdin被关闭而退出。
如果你无法修改子进程,你可以尝试以下方法:
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,并继续杀死后台的子进程。
在POSIX中,exit(), _exit()和_exit()函数被定义为:
如果该进程是控制进程,则SIGHUP信号应发送给控制终端的前台进程组中属于呼叫进程的每个进程。
因此,如果您安排父进程作为其进程组的控制进程,那么当父进程退出时,子进程应该得到一个SIGHUP信号。我不确定当父节点崩溃时是否会发生这种情况,但我认为确实会发生。当然,对于非崩溃的情况,它应该可以正常工作。
请注意,您可能必须阅读大量的小字——包括基本定义(Definitions)部分,以及exit()和setsid()和setpgrp()的系统服务信息——才能了解完整的情况。(我也是!)