Как заставить дочерний процесс умереть после выхода родителя?

Предположим, у меня есть процесс, который порождает ровно один дочерний процесс. Теперь, когда родительский процесс выходит по какой-либо причине (обычно или ненормально, убивая, утверждая неудачу или что-то еще), я хочу, чтобы дочерний процесс умер. Как это сделать правильно?


аналогичный вопрос по stackoverflow:


некоторые аналогичные вопросы по stackoverflow для окна:

23 ответов


ребенок может попросить ядро доставить SIGHUP (или другой сигнал), когда родитель умирает, указав параметр PR_SET_PDEATHSIG на prctl() syscall так:

prctl(PR_SET_PDEATHSIG, SIGHUP);

посмотреть man 2 prctl для сведения.

Edit: это только для Linux


Я пытаюсь решить ту же проблему, и поскольку моя программа должна работать на OS X, решение только для Linux не сработало для меня.

Я пришел к тому же выводу, что и другие люди на этой странице-нет совместимого с POSIX способа уведомления ребенка, когда родитель умирает. Поэтому я kludged до следующего лучшая вещь -- имея опроса ребенка.

когда родительский процесс умирает (по любой причине) родительский процесс ребенка становится процесс 1. Если ребенок просто периодически , он может проверить, является ли его родитель 1. Если это так, ребенок должен выйти.

Это не здорово, но это работает, и это проще, чем решения для опроса TCP-сокетов/lockfile, предложенные в другом месте на этой странице.


Я достиг этого в прошлом, запустив "исходный" код в "дочернем" и "порожденном" коде в "Родительском" (то есть: вы отменяете обычный смысл теста после fork()). Затем ловушка SIGCHLD в" порожденном " коде...

может быть невозможно в вашем случае, но мило, когда это работает.


Если вы не можете изменить дочерний процесс, вы можете попробовать что-то вроде следующего:

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 */

это работает ребенок в процессе раковины с контроля включена. Дочерний процесс порождается в фоновом режиме. Оболочка ждет новой строки (или EOF), а затем убивает ребенка.

когда родитель умирает-независимо от причины-он закроет свой конец трубы. Дочерняя оболочка получит EOF от чтения и продолжит убивать backgrounded дочерний процесс.


для полноты картины. На 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 при записи или получите EOF при чтении - эти условия могут быть обнаружены.


под Linux, вы можете установить родительский сигнал смерти ребенка, например:

#include <sys/prctl.h>
#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 ...

обратите внимание, что сохранение идентификатора родительского процесса перед вилкой и тестирование его в дочернем после 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 указывает:

идентификатор родительского процесса всех существующих дочерних процессов и зомби-процессов вызывающего процесса должен быть установлен на идентификатор процесса реализация-определенный системный процесс. То есть эти процессы наследуются специальным системным процессом.

традиционно системный процесс принятия всех сирот является PID 1, т. е. init-который является предком всех процессов.

на современных системах, таких как Linux или FreeBSD другой процесс может иметь эту роль. Например, в Linux процесс может вызывать prctl(PR_SET_CHILD_SUBREAPER, 1) утвердиться как системный процесс, который наследует всех сирот от любого из своих потомков (ср. Ан пример на Fedora 25).


вдохновленный другим ответом здесь, я придумал следующее решение all-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)

Я не верю, что можно гарантировать, что использование только стандартных вызовов POSIX. Как и в реальной жизни, когда ребенок рождается, у него есть своя жизнь.

Это is возможно, что родительский процесс поймает большинство возможных событий завершения и попытается убить дочерний процесс в этот момент, но всегда есть некоторые, которые не могут быть пойманы.

например, ни один процесс не может поймать SIGKILL. Когда ядро обрабатывает этот сигнал, оно убивает указанный процесс без уведомления этого процесса вообще.

существует Linux-единственный способ сделать это с prctl(2) - см. другие ответы.


установить ловушка обработчик чтобы поймать SIGINT, который убивает ваш процесс ребенка, если он все еще жив, хотя другие плакаты верны, что он не поймает SIGKILL.

открыть .lockfile с эксклюзивным доступом и иметь дочерний опрос на нем, пытаясь открыть его - если открытие успешно, дочерний процесс должен выйти


как указывали другие люди, полагаясь на родительский pid, чтобы стать 1, когда родительский выход не переносится. Вместо того, чтобы ждать определенного идентификатора родительского процесса, просто дождитесь изменения идентификатора:

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)
    ;

добавьте микро-сон по желанию, если вы не хотите опрашивать на полной скорости.

этот параметр кажется мне проще, чем использовать канал или полагаться на сигналы.


Я думаю, что быстрый и грязный способ-создать трубу между ребенком и родителем. Когда родитель выходит, дети получат SIGPIPE.


Это решение сработало для меня:

  • Pass stdin pipe to child - вам не нужно записывать какие-либо данные в поток.
  • ребенок читает бесконечно от stdin до EOF. EOF сигнализирует, что родитель ушел.
  • Это надежный и портативный способ определить, когда родитель ушел. Даже если родительский сбой, OS закроет канал.

Это было для процесса рабочего типа, существование которого имело смысл только тогда, когда родитель был живой.


некоторые плакаты уже упоминали трубы и kqueue. На самом деле вы также можете создать пару связанных сокеты домена Unix на socketpair() звонок. Тип сокета должен быть SOCK_STREAM.

предположим, у вас есть два дескриптора файлов сокетов fd1, fd2. Теперь fork() для создания дочернего процесса, который унаследует fds. В Родительском вы закрываете fd2 и в ребенка закрыть фр1. Теперь каждый процесс может poll() оставшийся открытым fd на своем конце для POLLIN событие. Пока каждая сторона явно не close() его fd в течение нормальной жизни, вы можете быть уверены, что POLLHUP флаг должен указывать на прекращение другого (независимо от того, чистый или нет). После уведомления об этом событии ребенок может решить, что делать (например, умереть).

#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);
    }
}

вы можете попробовать скомпилировать вышеуказанный код доказательства концепции и запустить его в терминале, таком как ./a.out &. У вас есть примерно 100 секунд, чтобы экспериментировать с убийством родительского PID различными сигналы, или он просто выйдет. В любом случае вы должны увидеть сообщение "child: parent hung up".

по сравнению с методом, использующим SIGPIPE обработчик, этот метод не требует пробуя write() звонок.

этот метод тоже симметричного, т. е. процессы могут использовать один и тот же канал для мониторинга существовании друг друга.

это решение вызывает только функции POSIX. Я пробовал это в Linux и FreeBSD. Думаю, это должно сработать. на других Униксах, но я не проверял.

Читайте также:

  • unix(7) из Linux man pages,unix(4) для FreeBSD, poll(2), socketpair(2), socket(7) на Linux.

под POSIX на exit(), _exit() и _Exit() функции определяются для:

  • если процесс является управляющим процессом, сигнал SIGHUP должен быть отправлен каждому процессу в группе процессов переднего плана управляющего терминала, принадлежащего вызывающему процессу.

таким образом, если вы организуете родительский процесс для управления его группой процессов, ребенок должен получить сигнал SIGHUP, когда родитель выходит. Я не совсем уверен, что это происходит, когда родитель падает, но я думаю, что это так. Конечно, для случаев без сбоев он должен работать нормально.

обратите внимание, что вам, возможно, придется прочитать довольно много мелкого шрифта, включая раздел базовых определений (определений), а также информацию о системных службах для exit() и setsid() и setpgrp() - чтобы получить полную картину. (Я тоже!)


Если вы отправляете сигнал на pid 0, используя, например,

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

этот сигнал отправляется всей группе процессов, таким образом, эффективно убивая ребенка.

вы можете проверить его легко с чем-то вроде:

(cat && kill 0) | python

Если вы затем нажмете ^D, вы увидите текст "Terminated" как указание на то, что интерпретатор Python действительно был убит, а не просто вышел из-за закрытия stdin.


в случае, если это имеет отношение к кому-либо еще, когда я порождаю экземпляры JVM в разветвленных дочерних процессах из C++, единственный способ заставить экземпляры JVM завершиться должным образом после завершения родительского процесса-это сделать следующее. Надеюсь, кто-то может предоставить обратную связь в комментариях, если это не лучший способ, чтобы сделать это.

1) Вызов prctl(PR_SET_PDEATHSIG, SIGHUP) на раздвоенном дочернем процессе, как предложено перед запуском приложения Java через execv и

2) Добавьте крюк выключения к приложение Java, которое опрашивает, пока его Родительский PID не равен 1, а затем делает жесткий Runtime.getRuntime().halt(0). Опрос выполняется путем запуска отдельной оболочки, которая запускает (см.: Как найти мой PID в Java или JRuby на Linux?).

редактировать 130118:

похоже, что это не было надежным решением. Я все еще пытаюсь немного понять нюансы того, что происходит, но я все еще иногда получаю сиротские процессы JVM при запуске этих приложения в сеансах screen/SSH.

вместо опроса для PPID в Java-приложении я просто попросил крюк выключения выполнить очистку с последующей жесткой остановкой, как указано выше. Затем я убедился, что вызываю waitpid в Родительском приложении C++ на порожденном дочернем процессе, когда пришло время завершить все. Это кажется более надежным решением, поскольку дочерний процесс гарантирует его завершение, в то время как родитель использует существующие ссылки, чтобы убедиться, что его дочерние элементы завершаются. Сравнивать это к предыдущему решению, в котором родительский процесс прекращался, когда ему хотелось,и дети пытались выяснить, были ли они сиротами до прекращения.


несмотря на то, что прошло 7 лет, я только что столкнулся с этой проблемой, поскольку я запускаю приложение SpringBoot, которое должно запустить webpack-dev-server во время разработки и должно убить его, когда процесс бэкэнда останавливается.

Я пытаюсь использовать Runtime.getRuntime().addShutdownHook но он работал на Windows 10, но не на Windows 7.

Я изменил его, чтобы использовать выделенный поток, который ждет завершения процесса или 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();
}

Если родитель умирает, 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

Я нашел 2 решения, оба не идеальны.

1.Убейте всех детей с помощью kill (- pid) при получении сигнала SIGTERM.
Очевидно, что это решение не может справиться с "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();
      });
    }

таким же образом, вы можете установить обработчик "выхода", как указано выше, если вы вызываете процесс.где-то выход. Примечание: Ctrl+C и внезапный сбой автоматически обрабатываются ОС для убить группу процесса, так что больше не здесь.

2.Использовать chjj / pty.js чтобы породить свой процесс с помощью управляющего терминала.
Когда вы убиваете текущий процесс в любом случае даже kill -9, все дочерние процессы будут автоматически убиты тоже (ОС?). Я предполагаю, что, поскольку текущий процесс удерживает другую сторону терминала, поэтому, если текущий процесс умирает, дочерний процесс получит 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(.....);
    */


мне удалось сделать портативное решение без опроса с 3 процессами, злоупотребляя терминальным управлением и сеансами. Это ментальная мастурбация, но работает.

фокус в том, что:

  • процесс B выделяет виртуальный терминал для этого нового сеанс
  • процесс B устанавливает обработчик SIGCHLD, чтобы умереть, когда ребенок выходит
  • процесс B устанавливает обработчик SIGPIPE
  • процесс B разветвляется на процесс c
  • процесс C делает все, что ему нужно (например, exec () s немодифицированный двоичный файл или запускает любую логику)
  • процесс B записывает в трубу P (и блокирует таким образом)
  • процесс ожидания()на процесс B и выходит, когда он умирает

таким образом:

  • если процесс a умирает: процесс B получает SIGPIPE и умирает
  • если процесс B умирает: процесс a wait () возвращается и умирает, процесс C получает вздох (потому что, когда руководитель сеанса сеанса с подключенным терминалом умирает, все процессы в группе процессов переднего плана получают вздох)
  • если процесс C умирает: процесс B получает SIGCHLD и умирает, поэтому процесс a умирает

недостатки:

  • процесс C не может обрабатывать SIGHUP

исторически, в Unix В7, системный процесс обнаружил orphanity процессов путем проверки процесса' ID родителя. Как я уже сказал, исторически init(8) системный процесс-это особый процесс только по одной причине: он не может умереть. Он не может умереть, потому что алгоритм ядра для назначения нового идентификатора родительского процесса зависит от этого факта. когда процесс выполняет свой exit(2) вызов (посредством системного вызова процесса или внешней задачи как отправка сигнала или тому подобное) ядра переназначает все дочерние элементы этого процесса id процесса инициализации в качестве идентификатора родительского процесса. Это приводит к наиболее простой тест, и наиболее портативный способ узнать, если процесс получил сироту. Просто проверьте результат getppid(2) системный вызов и если это идентификатор процесса init(2) process затем процесс стал сиротой перед системным вызовом.

из этого подхода вытекают два вопроса, которые могут привести к проблемам:

  • во-первых, у нас есть возможность изменения the init процесс для любого пользовательского процесса, так как мы можем гарантировать, что процесс init всегда будет родительским для всех сиротских процессов? Ну, в exit код системного вызова существует явная проверка, чтобы увидеть, является ли процесс, выполняющий вызов, процессом инициализации (процесс с pid, равным 1), и если это так, ядро паникует (оно больше не должно поддерживать иерархию процессов), поэтому процесс инициализации не может делать exit(2) звонок.
  • второй, в базовом тесте, показанном выше, есть условие гонки. Идентификатор процесса Init предполагается исторически как 1, но это не гарантируется подходом POSIX, который утверждает (как показано в другом ответе), что для этой цели зарезервирован только идентификатор процесса системы. Почти никакая реализация posix не делает этого, и вы можете предположить, что в исходных Unix-производных системах, имеющих 1 в ответ getppid(2) системного вызова достаточно, чтобы предположить, что процесс является сиротой. Другой способ проверить-сделать getppid(2) сразу после вилки и сравните это значение с результатом нового вызова. Это просто не работает во всех случаях, так как оба вызова не являются атомарными вместе, и родительский процесс может умереть после fork(2) и до первого!--2--> системный вызов. Процессparent id only changes once, when its parent does anвыход(2)call, so this should be enough to check if thegetppid(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 ofinit (8)`, но вы можете смело предположить, что эти процессы также не имеют родителя (за исключением случаев, когда вы заменяете в системе процесс init)

другой способ сделать это, специфичный для Linux, - создать родителя в новом пространстве имен PID. Затем это будет PID 1 в этом пространстве имен, и когда он выйдет из него, все его дети будут немедленно убиты с SIGKILL.

к сожалению, для создания нового пространства имен PID вы должны иметь CAP_SYS_ADMIN. Но этот метод очень эффективен и не требует реальных изменений для родителя или детей после первоначального запуска родителя.

посмотреть клон(2), pid_namespaces(7) и unshare (2).