终端即使在SIGKILLed时也会与所有孩子一起倒下,但正常的流程也不会这样做

时间:2014-05-12 06:18:04

标签: c linux unix operating-system systems-programming

我希望在我的程序中产生与bash(终端)相同的效果,当我们使用SIGKILL杀死它时。因为我们知道我们无法在我们的程序中处理SIGKILL,所以当我杀死我的程序时,它的子程序被分配给init进程,没有办法处理它,这样我就可以杀死所有的子进程然后杀死父进程本身。虽然我们杀死终端时,即使我们用SIGKILL杀死我们的终端,所有通过它创建的进程都会被杀死。

为此,我做了一些研究,发现了以下帖子: [https://unix.stackexchange.com/questions/54963/how-can-terminal-emulators-kill-their-children-after-recieving-a-sigkill][1]

帖子有点令人困惑,但我发布的帖子仍然是,如果您要杀的进程是进程组的进程负责人,那么它的所有子进程都会被杀死。

为简单起见,我在下面的程序中实现了测试是否如此:

int main()
{
    printf("Curent PID: %u\n", getpid());

    // make a new session
    pid_t pid = setsid();

    printf("New session ID: %u\n", pid);

    pid = fork();
    switch(pid)
    {
            case -1:
                    perror("UNable to fork the process\n");
                    exit(EXIT_FAILURE);
            case 0:
                    // child process
                    while (1)
                    {
                            sleep(1);
                    }
                    break;
    }

    while (1)
    {
            printf("Process Leader running\n");
            sleep(1);
    }

return 0;

}

在我杀死父进程后运行上述程序后,子进程没有被杀死。我还修改了上面的程序,使它不属于任何tty,我想可能是进程领导者不应该与任何tty相关联。我通过以下方式做到了:

创建正常流程(父流程)

在上述父进程中创建子进程

此阶段的流程层次结构如下:TERMINAL - >父母过程 - >儿童过程

终止父进程。

子进程现在变成了孤儿,并被init进程接管。

调用setsid()函数在新会话中运行该进程并拥有一个新组。 然后重复相同的上述代码。

当我杀死进程领导时,孩子们仍在那里。 也许我没有在unix.stackexchange上发布那篇文章,或者它是LINUX中的嘲讽行为。 我可以通过捕获每个TERMINATING SIGNAL(如SIGTERM,SIGHUP等)实现的一种方法是处理它们并在这些信号处理程序中写入逻辑以先杀死孩子。但仍然在SIGKILL上我无能为力。

此外,我有兴趣知道,如果父级进程不会影响子进程,即使父级是进程领导者或其他什么,那么即使我们向其发送SIGKILL,bash(终端)如何设法杀死所有子进程。是否为LINUX内核中的终端编写了一些额外的逻辑。

如果有一种方法可以在使用SIGKILL信号杀死父级时杀死所有子进程,我也很高兴知道这一点。

3 个答案:

答案 0 :(得分:2)

kill的手册页说:

  

负PID值可用于选择整个过程组

在我理解杀死整组进程时,你必须发送负PID。

另一种机制导致杀死终端杀死其子进程。从终端运行的进程将stdin / stdout连接到终端。终止终端时,关闭这些连接并向这些进程发送信号(SIG_HUP)。通常的程序不处理此信号,默认终止。

答案 1 :(得分:1)

玛丽安的建议是非常正确的,非常值得研究,但如果你选择遵循这条路线,你可能会最终实现所谓的人质技巧"。

人质技巧包括产生人工子进程的根进程,该进程将所有时间都停留在停止状态。这个"人质"将在第一个子进程之前立即生成,该进程在您的(多进程)程序中真正起作用。

人质进程成为其自己的进程组的领导者,然后进入一个循环,在该循环中,它通过" raise(SIGSTOP)"来停止。如果它一直持续,它会检查它的父母是否已经终止(即它是否已被重新父母或无法用空信号(ESRCH)向其父母发信号)。如果父母已经终止,那么人质应该终止,否则它应该重新暂停另一个"加注(SIGSTOP)"。

你需要注意竞争条件:例如:对于重新父母测试,请注意将hostage的parent-process-id缓存为来自" getpid()"的返回值。在" fork()" -ing人质之前,还要制作" setpgid()"调用" fork()"的下游在父母和孩子。然后,如果某人"杀死(。,SIGKILL)并且是人质,你需要考虑你做了什么!

是的,您可以在父级中放置一个SIGCHLD处理程序来重新生成它,但这需要非常小心以保持人质进程组身份的连续性;也许在SIGKILL时还有其他子进程,替换人质应该进入原始进程组,可能没有,并且原始进程组已经蒸发。

即使你做对了,你也放了一个" fork()"如果您的主进程使用多个线程,则在异步信号处理程序(SIGCHLD)中调用可能会打开另一个蠕虫病毒。

由于这些困难,我建议不要使用人质技巧,除非子进程运行您无法控制的代码(并且即使在那时也要认真考虑复杂性和可维护性的成本)。如果您可以控制子进程的代码,那么使用" pipe()"就会简单得多。

您在父进程中创建管道并管理文件描述符,以确保父进程是唯一的编写器,并且每个子进程都为读取端分配一个文件描述符。如果这样做,那么父进程的终止(无论是由于SIGKILL还是由于任何其他原因)都会在最后一个编写器终止时通过管道读取端的EoF条件传递给子进程。

如果你想特别对待SIGKILL,那么你可以在管道上使用协议,父进程发送终止消息,告知孩子在所有正常终端上的终止状态和可捕获的致命信号,并让孩子们去如果管道的读取端发送没有先前终止消息的EoF,则推断父进程被SIGKILL杀死。

答案 2 :(得分:0)

在Linux上prctl(PR_SET_PDEATHSIG ...将安排一个进程在其父级死亡时接收信号,此设置将保留在exec上,但不会被子进程继承。