假设我有一个进程,它恰好产生了一个子进程。现在,当父进程出于某种原因(正常或异常,通过kill,^ C,断言失败或其他任何原因)退出时,我希望子进程死亡。



关于stackoverflow的一些类似问题:


(如前所述)如何导致子进程在退出时退出

(稍后问)当父级被杀死时,使用fork()创建的子进程会自动被杀死吗?



有些相似Windows的stackoverflow问题:




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


#1 楼

孩子可以通过在SIGHUP系统调用中指定选项PR_SET_PDEATHSIG来要求内核在父母去世时发出prctl()(或其他信号),如下所示: >
编辑:这仅适用于Linux

评论


这是一个糟糕的解决方案,因为父母可能已经死亡。比赛条件。正确的解决方案:stackoverflow.com/a/17589555/412080

– Maxim Egorushkin
2015年12月22日17:49



称呼不佳不是很好-即使不能解决比赛条件。参见我关于如何以无竞争条件的方式使用prctl()的答案。顺便说一句,Maxim链接的答案不正确。

–maxschlepzig
16年4月29日在18:36

这只是一个错误的答案。它将在调用fork的线程死亡时而不是在父进程死亡时将信号发送给子进程。

–乐透
16年10月10日在1:01

@Lothar很高兴看到某种证明。 man prctl说:将调用进程的父进程终止信号设置为arg2(信号值在1..maxsig范围内,或在0范围内以清除)。这是调用进程在其父进程死亡时将获得的信号。执行set-user-ID或set-group-ID二进制文件时,将为fork(2)和(自Linux 2.4.36 / 2.6.23起)的子代清除此值。

– qrdl
16 Dec 10'在19:46



@maxschlepzig感谢您的新链接。好像上一个链接无效。顺便说一句,多年之后,仍然没有用于在父端设置选项的api。太遗憾了。

– rox
17-4-27的3:47



#2 楼

我正在尝试解决相同的问题,并且由于我的程序必须在OS X上运行,所以仅Linux解决方案对我不起作用。

我得出的结论与其他人相同在此页面上-当父母去世时,没有通知POSIX的方式通知孩子。因此,我仔细考虑了下一个最好的事情-进行子轮询。

(由于某种原因)父进程死亡时,子进程的父进程变为进程1。如果子进程只是简单地定期轮询,可以检查其父级是否为1。如果是,则应退出该子级。

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

评论


优秀的解决方案。继续调用getppid()直到返回1,然后退出。这很好,我现在也使用它。但是,非政治解决方案将是不错的选择。谢谢Schof。

–霓虹灯
2010-4-11的11:58

只是为了提供信息,在Solaris上,如果您位于区域中,则gettpid()不会变为1,而是获取区域调度程序的pid(进程zsched)。

–帕特里克·施吕特(PatrickSchlüter)
2010-10-14 13:32

如果有人想知道的话,当父母去世时,在Android系统中,pid似乎是0(进程System pid)而不是1。

–瑞·马克斯(Rui Marques)
2012年10月2日17:38

要拥有更健壮且与平台无关的方式,请在fork()之前,简单地使用getpid(),如果child的getppid()不同,则退出。

–塞巴斯蒂安
17年8月10日在13:42

如果您不控制子进程,则此方法不起作用。例如,我正在处理包装find(1)的命令,并且我想确保如果包装由于某种原因死亡,则查找被杀死。

– Lucretiel
18年6月19日在2:03

#3 楼

过去,我是通过在“子”中运行“原始”代码,在“父”中运行“生成的”代码来实现这一点的(也就是说,您在fork()之后反转了测试的通常意义)。然后将SIGCHLD捕获在“生成的”代码中...

在您的情况下可能无法实现,但在可行时很可爱。

评论


非常好的解决方案,谢谢!当前接受的是更通用的,但您的是更可移植的。

–PawełHajdan
08年11月12日在20:47

在父级中执行工作的最大问题是您正在更改父级流程。如果服务器必须“永远”运行,则不是一种选择。

– Alexis Wilke
15年3月24日在4:34

#4 楼

如果您无法修改子进程,则可以尝试以下操作:

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

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


这将从启用了作业控制的Shell进程中运行子进程。子进程在后台生成。外壳程序等待换行符(或EOF),然后杀死子进程。

当父进程死亡时,无论原因为何,它都会关闭管道的末端。子外壳将从读取的文件中获取EOF,然后继续杀死后台的子进程。

评论


很好,但是有五个系统调用,以及十行代码产生的嘘声让我对这段代码的性能有些怀疑。

–欢乐节
2014年1月17日9:53

+1。您可以通过使用read -u标志从特定文件描述符中进行读取来避免dup2并接管stdin。我还在子级中添加了setpgid(0,0),以防止在终端中按^ C时退出。

– Greg Hewgill
2014年5月1日0:09



dup2()调用的参数顺序错误。如果要使用管道[0]作为标准输入,则必须编写dup2(管道[0],0)而不是dup2(0,管道[0])。它是isdup2(oldfd,newfd),其中调用将关闭先前打开的newfd。

–maxschlepzig
16年4月30日在8:23

@Oleiade,我同意,特别是因为生成的sh只是另一个fork来执行真正的子进程...

–maxschlepzig
16年4月30日在8:26

#5 楼

在Linux下,您可以在子级中安装父级死亡信号,例如:

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() != ppid_before_fork)
        exit(1);
    // continue child execution ...


请注意,将父进程ID存储在fork之前,然后在子级中对其进行测试prctl()消除了prctl()与调用该子进程的退出之间的竞争状态。

还要注意,在自己创建的新子级中清除了该子级的父级死亡信号。它不受execve()的影响。

如果可以肯定负责采用所有孤儿的系统进程具有PID 1,则可以简化该测试:

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() == 1)
        exit(1);
    // continue child execution ...


依靠init并具有PID 1的系统进程是不可移植的。 POSIX.1-2008指定:


调用进程的所有现有子进程和僵尸进程的父进程ID必须设置为实现定义的系统的进程ID。处理。也就是说,这些过程应由特殊的系统过程继承。



传统上,采用所有孤立变量的系统过程是PID 1,即init-这是所有过程的始祖。 br />
在诸如Linux或FreeBSD的现代系统上,另一个进程可能具有该角色。例如,在Linux上,进程可以调用prctl(PR_SET_CHILD_SUBREAPER, 1)来将其自身设置为继承其任何后代的所有孤儿的系统进程(请参见Fedora 25上的示例)。

评论


我不理解“如果可以确定祖父母始终是初始化过程,则可以简化该测试”。当父进程死亡时,该进程将成为init进程的子进程(pid 1),而不是祖父母的子进程,对吗?因此测试似乎总是正确的。

– Johannes Schaub-小人
16-11-26在23:53

@ JohannesSchaub-litb,不必为PID 1-POSIX指定:调用进程的所有现有子进程和僵尸进程的父进程ID必须设置为实现定义的系统进程的进程ID 。即,这些过程应由特殊的系统过程继承。例如,当在Gnome终端的Fedora 25系统上运行时,特殊的系统进程具有PID!= 1:gist.github.com/gsauthof/8c8406748e536887c45ec14b2e476cbc

–maxschlepzig
16-11-27在9:44



有趣,谢谢。但是,我看不到与祖父母有什么关系。

– Johannes Schaub-小人
16年11月28日在7:37

@ JohannesSchaub-litb,您不能始终假定进程的祖父母将是init(8)进程...。您唯一可以假设的是,当父进程死亡时,其父ID将会改变。实际上,这实际上在流程的生命周期中发生一次。。。。只有一个主要例外,它是针对init(8)子级的,但您会受到保护,因为init(8)永远不会退出(2)(在这种情况下,内核会出现恐慌)

–路易斯·科罗拉多
17年8月8日在8:31

不幸的是,如果子进程从线程派生,然后线程退出,则子进程将获得SIGTERM。

– rox
17年5月4日13:40

#6 楼

为了完整起见。在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);
}


评论


您可以通过使用带有DISPATCH_SOURCE_PROC和PROC_EXIT的调度源,使用稍微好一点的API来执行此操作。

–russbishop
18年7月7日在4:44

无论出于何种原因,这都导致Mac出现恐慌。使用此代码运行进程有50%的机会会冻结,从而导致风扇以我从未听说过的速度旋转(超快),然后Mac会关闭。请务必谨慎使用此代码。

– Qix-蒙尼卡(MS)被盗
19年5月12日在17:08

在我的macOS上,子进程似乎在父进程退出后自动退出。我不知道为什么

–刘奕琳
19年6月28日在2:10

@YiLinLiu iirc我使用了NSTask或posix生成器。请参阅我的代码中的startTask函数:github.com/neoneye/newton-commander-browse/blob/master/Classes/…

–霓虹灯
19年6月28日在8:24

#7 楼

子进程是否具有往返于父进程的管道?如果是这样,则在写入时会收到SIGPIPE,或者在读取时会得到EOF-可以检测到这些情况。

评论


我发现这至少在OS X上并没有可靠地发生。

– Schof
2010年1月10日,下午1:31

我来这里是为了添加这个答案。这绝对是我在这种情况下使用的解决方案。

– Dolda2000
15年1月30日在7:07

注意点:systemd默认在其管理的服务中禁用SIGPIPE,但是您仍然可以检查管道是否闭合。请参阅IgnoreSIGPIPE下的freedesktop.org/software/systemd/man/systemd.exec.html

– jdizzle
6月2日13:44



#8 楼

受到另一个答案的启发,我提出了以下全POSIX解决方案。通常的想法是在父级和子级之间创建一个中间过程,该过程具有一个目的:注意父级去世并明确杀死子级。

当代码存在时,这种解决方案非常有用。

int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
    close(p[1]); // close write end of pipe
    setpgid(0, 0); // prevent ^C in parent from stopping this process
    child = fork();
    if (child == 0) {
        close(p[0]); // close read end of pipe (don't need it here)
        exec(...child process here...);
        exit(1);
    }
    read(p[0], 1); // returns when parent exits for any reason
    kill(child, 9);
    exit(1);
}


使用此方法有两个小警告:


如果您故意杀死中间进程,那么当父进程死亡时,该子进程将不会被杀死。
如果子进程在父进程之前退出,那么中间进程将尝试杀死原始的子进程pid,现在可以引用另一个处理。 (这可以在中间过程中用更多代码修复。)

顺便说一句,我正在使用的实际代码在Python中。这是出于完整性:

def run(*args):
    (r, w) = os.pipe()
    child = os.fork()
    if child == 0:
        os.close(w)
        os.setpgid(0, 0)
        child = os.fork()
        if child == 0:
            os.close(r)
            os.execl(args[0], *args)
            os._exit(1)
        os.read(r, 1)
        os.kill(child, 9)
        os._exit(1)
    os.close(r)


评论


请注意,前一阵子,在IRIX下,我使用了一个父子方案,我在两者之间都有一个管道,如果其中一个死亡,则从管道中读取会生成SIGHUP。那是我过去杀死中间人的孩子的方式,而无需中间过程。

– Alexis Wilke
2015年3月24日4:11



我认为您的第二个警告是错误的。子进程的pid是属于其父进程的资源,在父进程(中间进程)等待它(或终止并让init等待它)之前,它不能被释放/重用。

–R .. GitHub停止帮助ICE
17-2-28在0:28

#9 楼

我认为无法保证仅使用标准POSIX调用。就像现实生活一样,一旦产生了一个孩子,它就有了自己的生活。

父进程有可能捕获大多数可能的终止事件,并在那一刻试图杀死该子进程。 ,但是总有一些无法捕获。例如,没有进程可以捕获SIGKILL。当内核处理此信号时,它将杀死指定的进程,而不会通知该进程。

要扩大类比-唯一的其他标准方式是让孩子在自杀时自杀发现它不再具有父级。

使用prctl(2)只能通过Linux进行操作-请参阅其他答案。

#10 楼

正如其他人指出的那样,在父级退出时依靠父级pid变为1是不可移植的。不必等待特定的父进程ID,而只需等待ID更改即可:

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;


如果您不想轮询,则根据需要添加微睡眠全速运行。

与使用管道或依靠信号相比,此选项对我来说似乎更简单。

评论


不幸的是,该解决方案并不可靠。如果父进程在获得初始值之前死亡,该怎么办?孩子永远不会离开。

–加特伍德
15年3月18日在20:36

@dgatwood,您什么意思?!?第一个getpid()在调用fork()之前在父级中完成。如果父母在此之前去世,则该子女不存在。可能发生的情况是孩子在父母身边住了一段时间。

– Alexis Wilke
15年3月24日在4:29

在这个有些人为设计的示例中,它可以工作,但是在实际代码中,fork总是紧随其后的是exec,新过程必须通过请求其PPID重新开始。在两次检查之间的时间里,如果父母离开了,孩子将一无所知。另外,您不太可能同时控制父代码和子代码(否则,您可以仅将PPID作为参数传递)。因此,作为一般解决方案,该方法效果不佳。实际上,如果在不将init设为1的情况下出现类似UNIX的操作系统,那么很多东西都会崩溃,以至于我无法想象有人这样做。

–加特伍德
2015年3月30日4:40在

为子级执行exec时,传递parent pid是命令行参数。

– Nish
16-2-17在11:25

全速轮询是疯狂的。

–maxschlepzig
16年4月30日在8:34

#11 楼

此解决方案对我有用:


将stdin管道传递给子进程-您不必将任何数据写入流中。
子进程会从stdin无限期读取直到EOF。一个EOF信号表明父母已经离开。
这是一种万无一失的便携式方法,可以检测父母何时离开。即使父进程崩溃,OS也会关闭管道。

这是一个工作程序类型的进程,其存在仅在父进程还活着时才有意义。

评论


@SebastianJylanki我不记得我是否尝试过,但是它可能有效,因为原语(POSIX流)在操作系统之间是相当标准的。

– joonas.fi
18年4月18日在12:50

#12 楼

安装一个陷阱处理程序来捕获SIGINT,如果它仍然存在,它会杀死您的子进程,尽管其他发布者也正确地认为它不会捕获SIGKILL。

打开具有互斥访问权限的.lockfile,并对其进行子轮询以尝试打开它-如果打开成功,则子进程应退出

评论


或者,孩子可以以阻塞模式在单独的线程中打开锁文件,在这种情况下,这可能是一个非常不错的解决方案。不过,它可能具有一些可移植性限制。

–吉恩
17年5月5日14:48



#13 楼

我认为一种快速而肮脏的方法是在孩子和父母之间建立一条管道。父母退出后,孩子将获得SIGPIPE。

评论


SIGPIPE不会在管道关闭时发送,仅在子级尝试写入时才发送。

–阿尔卡罗
17年3月31日在11:03

#14 楼

一些海报已经提到管道和kqueue。实际上,您还可以通过socketpair()调用创建一对连接的Unix域套接字。套接字类型应为SOCK_STREAM。让我们假设您有两个套接字文件描述符fd1,fd2。现在fork()创建子进程,它将继承fds。在父级中,您关闭fd2,在子级中,您关闭fd1。现在,每个进程都可以自己结束poll()事件的剩余打开fd。只要双方在正常生命周期中没有明确表示其fd,就可以肯定地确定POLLIN标志应指示对方的终结(无论是否清洁)。接到此事件的通知后,孩子可以决定要做什么(例如,死掉)。

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}


您可以尝试编译以上概念验证代码,并在类似close()的终端上运行它。您大约有100秒的时间来尝试通过各种信号杀死父PID,否则它就会退出。无论哪种情况,您都应该看到消息“子代:父母挂断了”。

与使用POLLHUP处理程序的方法相比,此方法不需要尝试./a.out &调用。

此方法也是对称的,即进程可以使用同一通道来监视彼此的存在。

此解决方案仅调用POSIX函数。我在Linux和FreeBSD上尝试过。我认为它应该可以在其他Unix上运行,但是我尚未真正测试过。

另请参见:Linux手册页SIGPIPEwrite()适用于Linux上的FreeBSD,unix(7)unix(4)poll(2)


评论


非常酷,我真的很想知道这是否存在任何可靠性问题。您在生产中测试过吗?使用不同的应用程序?

–阿克套
13年4月8日在20:30

@Aktau,我一直在Linux程序中使用等效于该技巧的Python。我之所以需要它,是因为孩子的工作逻辑是“在父母退出后尽力而为,然后也退出”。但是,我真的不确定其他平台。 C片段可以在Linux和FreeBSD上运行,但这就是我所知道的...此外,在某些情况下,您应该格外小心,例如父级再次分叉,或者父级在真正退出之前放弃了fd(因此为比赛条件)。

–马聪
13年4月9日在11:34

@Aktau-这将是完全可靠的。

–恶毒
17年6月6日在18:09

#15 楼

在POSIX下,将exit()_exit()_Exit()函数定义为:如果该过程是控制过程,则应将SIGHUP信号发送到该过程的前台过程组中的每个过程。

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

请注意,您可能必须阅读很多精美的文字-包括“基本定义(定义)”部分以及“系统” exit()setsid()setpgrp()的服务信息-以获取完整的图片。 (我也会!)

评论


嗯该文档对此含糊不清且相互矛盾,但是看来父流程必须是会话的主导流程,而不仅仅是流程组。会话的主要流程始终是登录,而让我的流程代替新会话的主要流程目前超出了我的能力范围。

– Schof
2010年1月10日,下午1:33

仅当退出进程是登录shell时,SIGHUP才有效地发送到子进程。 “ opengroup.org/onlinepubs/009695399/functions/exit.html”进程的终止不会直接终止其子进程。如下所述,发送SIGHUP信号在某些情况下会间接终止子进程。

– Rob K
10年7月6日在21:01

@Rob:正确-这也是我给的报价:只有在某些情况下,子进程才能获得SIGHUP。严格地说,这只是发送SIGHUP的登录shell,尽管这是最常见的情况。如果将具有多个子进程的进程设置为其自身及其子进程的控制进程,则在主进程死后,SIGHUP将(方便地)发送给其子进程。 OTOH,流程很少会遇到那么大的麻烦-因此,我挑剔的工作比提出一个真正重要的小问题要多。

–乔纳森·莱弗勒(Jonathan Leffler)
10年7月6日在22:36

我无所事事了几个小时,却无法正常工作。如果我有一个带有一些子进程的守护进程,当父进程退出时,所有子进程都需要死亡,这将很好地处理这种情况。

– Rob K
2010年8月17日在19:36

#16 楼

如果您向pid 0发送信号,例如使用

kill(0, 2); /* SIGINT */


该信号被发送到整个进程组,从而有效地杀死子进程。

您可以使用以下类似的代码轻松测试它:

(cat && kill 0) | python

如果按^ D,您将看到文本"Terminated"表示Python解释器确实被杀死了,而不是因为关闭stdin而退出了。

评论


(echo -e“ print(2 + 2)\ n”并杀死0)| sh -c“ python-”愉快地打印4而不是终止

–卡米尔·索特(Kamil Szot)
2014年4月20日在13:29

#17 楼

如果与其他任何人都相关,则当我从C ++派生分叉的子进程中的JVM实例时,使父实例完成后才能正确终止JVM实例的唯一方法是执行以下操作。如果不是这样做的最好方法,希望有人可以在评论中提供反馈。

1)按照建议在派生子进程上调用prctl(PR_SET_PDEATHSIG, SIGHUP),然后通过execv启动Java应用程序,然后

2)向Java应用程序添加一个关闭挂钩,该轮询将轮询直到其父PID等于1,然后执行硬Runtime.getRuntime().halt(0)。通过启动运行ps命令的单独外壳程序来完成轮询(请参阅:如何在Linux上的Java或JRuby中找到我的PID?)。

编辑130118:

这似乎不是一个可靠的解决方案。我仍在努力了解正在发生的细微差别,但是在屏幕/ SSH会话中运行这些应用程序时,有时仍会遇到孤立的JVM进程。

我没有在Java应用程序中轮询PPID,而是简单地让shutdown钩子执行了清理操作,然后如上所述进行了硬停止。然后,当需要终止所有操作时,请确保在生成的子进程上的C ++父应用程序中调用waitpid。这似乎是一个更可靠的解决方案,因为子进程确保其终止,而父进程使用现有引用来确保其子进程终止。将此与以前的解决方案进行比较,前一个解决方案让父进程在需要时终止,而让子进程尝试在终止之前弄清楚他们是否是孤儿。

评论


PID等于1等待无效。新的父对象可能是其他一些PID。您应该检查它是否从原始父对象(在fork()之前的getpid())更改为新的父对象(在fork()之前被调用的子代中的getppid()不等于getpid())。

– Alexis Wilke
15年3月24日在4:32

#18 楼

特定于Linux的另一种方法是在新的PID名称空间中创建父对象。然后,它将在该命名空间中成为PID 1,并且退出该子项时,所有子项都将立即被SIGKILL杀死。不幸的是,要创建新的PID命名空间,您必须具有CAP_SYS_ADMIN。但是,此方法非常有效,并且不需要对父项或子项进行任何实际更改,而要先启动父项。

请参见clone(2),pid_namespaces(7)和unshare(2) 。

评论


我需要以其他方式进行编辑。可以使用prctl使进程充当其所有子孙代,曾孙代等等的初始化进程。

–恶毒
1月28日7:44



#19 楼

如果父母去世,则孤儿的PPID更改为1-您只需要检查自己的PPID。
以某种方式,这就是上面提到的轮询。
这是为此的外壳程序:

check_parent () {
      parent=`ps -f|awk '=='$PID'{print  }'`
      echo "parent:$parent"
      let parent=$parent+0
      if [[ $parent -eq 1 ]]; then
        echo "parent is dead, exiting"
        exit;
      fi
}


PID=$$
cnt=0
while [[ 1 = 1 ]]; do
  check_parent
  ... something
done


#20 楼

我发现2个解决方案,都不都是完美的。

1.收到SIGTERM信号后,用kill(-pid)杀死所有孩子。
显然,该解决方案无法处理“ kill -9”,但是它可以在大多数情况下工作并且非常简单,因为它不需要记住所有的子进程。


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }



通过相同的方式,您可以像上面一样安装“ exit”处理程序
注意:操作系统已自动处理Ctrl + C和突然崩溃,以杀死进程组,因此这里不再赘述。

2.使用chjj / pty.js会在连接了控制终端的情况下生成您的进程。
无论如何您杀死当前进程甚至杀死-9时,所有子进程也会被自动杀死(通过OS?)。我猜是因为当前进程占据了终端的另一侧,所以如果当前进程死亡,则子进程将获得SIGPIPE从而死亡。


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */



#21 楼

通过滥用终端控制和会话,我设法通过3个进程来制作了一个可移植的非轮询解决方案。这是精神上的自慰,但是有效。

诀窍是:


进程A已启动
进程A创建了管道P(从不读取从中)
进程A进入进程B
进程B创建一个新会话
进程B为该新会话分配一个虚拟终端
进程B安装SIGCHLD处理程序时,子进程退出
进程B设置SIGPIPE处理程序
进程B派生到进程C中
进程C进行所需的任何操作(例如exec()s未经修改的二进制文件或运行任何逻辑)
进程B向管道P写入(并以此方式阻塞)
进程A在进程B上等待并在其死时退出

这样:


如果进程A死亡:进程B获得SIGPIPE并死亡
如果进程B死亡:进程A的wait()返回并死亡,则进程C获得SIGHUP(因为与终端会话的会话负责人附加的模具,则前台过程组中的所有过程均会收到SIGHUP)
如果过程C死亡:过程B g设置了一个SIGCHLD并死亡,因此进程A死亡

短处:


进程C无法处理SIGHUP
进程C将在A中运行不同的会话
进程C无法使用会话/进程组API,因为它会破坏脆弱的设置
为每个这样的操作创建一个终端绝不是最好的主意


#22 楼

尽管已经过去了7年,但我刚遇到这个问题,因为我正在运行SpringBoot应用程序,该应用程序需要在开发过程中启动webpack-dev-server,并且需要在后端进程停止时将其杀死。

我尝试使用Runtime.getRuntime().addShutdownHook,但它在Windows 10上有效,但不适用于Windows7。

我已将其更改为使用专用线程来等待进程退出或InterruptedException正常运行在两个Windows版本上都正确。

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}


#23 楼

从历史上看,从UNIX v7开始,流程系统通过检查流程的父ID来检测流程的孤立性。就像我说的那样,从历史上看,init(8)系统进程是一个特殊的进程,仅出于一个原因:它不会死亡。它不能消亡,因为内核算法处理分配新的父进程ID取决于这一事实。当进程执行其exit(2)调用时(通过进程系统调用或通过外部任务向其发送信号等),内核将该进程的所有子进程重新分配init进程的ID作为其父进程ID。这将导致最简单的测试和最可移植的方式来了解进程是否已成为孤儿。只需检查getppid(2)系统调用的结果,如果它是init(2)进程的进程ID,则该进程在系统调用之前变得孤立。

这种方法出现了两个问题,这些问题可能导致出现问题:


首先,我们可以将init进程更改为任何用户进程,那么我们如何确保init进程始终是所有孤立进程的父级?好吧,在exit系统调用代码中,有一个显式检查以查看执行该调用的进程是否为init进程(pid等于1的进程),如果是这种情况,则内核会发生恐慌(它现在不再能够维护进程层次结构),因此不允许init进程执行exit(2)调用。
第二,上面暴露的基本测试中存在竞赛条件。过去假定Init进程的ID为1,但是POSIX方法并不能保证该声明(声明(如其他响应所述),仅系统的进程ID为此用途而保留。几乎没有posix实现可以做到这一点,并且您可以假设在原始的unix派生系统中,以1作为getppid(2)系统调用的响应足以假设该过程是孤立的。另一种检查方法是在派生之后创建getppid(2),并将该值与新调用的结果进行比较。这根本不能在所有情况下都起作用,因为两个调用都不是原子的,并且父进程可能在fork(2)之后和第一个getppid(2)系统调用之前死亡。进程parent id only changes once, when its parent does an exit(2)call, so this should be enough to check if the getppid(2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children of init(8)`,但是您可以安全地假设这些进程也没有父进程(除非在系统中替换了init进程)


#24 楼

我已将使用环境的父pid传递给孩子,
然后定期检查孩子中是否存在/ proc / $ ppid。