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

183

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


Некоторые похожие вопросы о stackoverflow:


Некоторые похожие вопросы о stackoverflow для Windows:

Теги:
process
fork

23 ответа

172
Лучший ответ

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

prctl(PR_SET_PDEATHSIG, SIGHUP);

Подробнее см. man 2 prctl.

Изменить: это Linux-only

  • 0
    Да, для Linux. Я не думаю, что есть POSIX способ сделать это.
  • 0
    @qrdl Что такое эквивалент FreeBSD?
Показать ещё 8 комментариев
60

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

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

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

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

  • 6
    Отличное решение. Продолжайте вызывать getppid (), пока он не вернет 1, а затем выйдите. Это хорошо, и теперь я тоже им пользуюсь. Хотя решение было бы неплохим. Спасибо, Шоф.
  • 10
    Просто для информации, в Solaris, если вы находитесь в зоне, gettpid() не становится 1, а получает pid планировщика зоны (процесс zsched ).
Показать ещё 7 комментариев
32

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

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

  • 0
    Очень хорошее решение, спасибо! В настоящее время принятый является более общим, но ваш более переносимым.
  • 1
    Огромная проблема с работой над родителем заключается в том, что вы меняете родительский процесс. В случае сервера, который должен работать «навсегда», это не вариант.
28

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

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 от чтения и продолжит убивать фоновый дочерний процесс.

  • 0
    Вау круто! Портативное решение без опроса! :)
  • 2
    Хорошо, но пять системных вызовов и sh, порожденные десятью строками кода, позволяют мне немного скептически относиться к этой части кода.
Показать ещё 3 комментария
15

Для полноты. На 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);
}
  • 0
    Вы можете сделать это с немного более приятным API, используя источники отправки с DISPATCH_SOURCE_PROC и PROC_EXIT.
10

В 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() устраняет условие расы между 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).

  • 0
    Я не понимаю, «этот тест можно упростить, если мы уверены, что дедушка всегда инициирует процесс». Когда родительский процесс умирает, процесс становится дочерним по отношению к процессу init (pid 1), а не дочерним по отношению к деду, верно? Таким образом, тест всегда кажется правильным.
  • 1
Показать ещё 4 комментария
10

Есть ли у дочернего процесса канал в/из родительского процесса? Если это так, вы получите SIGPIPE при записи или получите EOF при чтении - эти условия могут быть обнаружены.

  • 1
    Я обнаружил, что это не произошло надежно, по крайней мере на OS X.
  • 0
    Я пришел сюда, чтобы добавить этот ответ. Это определенно решение, которое я бы использовал для этого случая.
9

Вдохновленный другим ответом здесь, я придумал следующее решение 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)
  • 0
    Обратите внимание, что некоторое время назад, в IRIX, я использовал схему родитель / потомок, где у меня был канал между ними, и чтение из канала генерировало SIGHUP, если один из них умер. Таким способом я убивал своих детей fork () без необходимости промежуточного процесса.
  • 0
    Я думаю, что ваша вторая оговорка не так. Pid дочернего элемента - это ресурс, принадлежащий его родительскому элементу, и он не может быть освобожден / использован повторно до тех пор, пока родительский элемент (промежуточный процесс) не ожидает его (или не завершается и не позволяет init ожидать его).
7

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

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

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

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

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

4

Как отмечали другие люди, полагаясь на родительский pid, чтобы стать 1, когда родительские выходы не переносятся. Вместо того, чтобы ждать определенного идентификатора родительского процесса, просто подождите, пока 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)
    ;

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

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

  • 0
    К сожалению, это решение не является надежным. Что если родительский процесс умирает до того, как вы получите начальное значение? Ребенок никогда не выйдет.
  • 0
    @dgatwood, что ты имеешь в виду?!? Первый getpid() выполняется в родительском getpid() перед вызовом fork() . Если родитель умирает до этого, ребенка не существует. Может случиться так, что ребенок некоторое время живет с родителем.
Показать ещё 3 комментария
4

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

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

  • 0
    Или ребенок может открыть файл блокировки в отдельном потоке в режиме блокировки, и в этом случае это может быть довольно хорошим и чистым решением. Вероятно, у него есть некоторые ограничения переносимости.
3

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

  • Передайте stdin pipe дочернему - вам не нужно записывать какие-либо данные в поток.
  • Ребенок читает бесконечно с stdin до EOF. EOF сигнализирует, что родитель ушел.
  • Это безопасный и переносимый способ обнаружения, когда родитель ушел. Даже если родительский сбой, ОС закроет канал.

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

  • 0
    Это работает и на Windows?
  • 0
    @SebastianJylanki Я не помню, пытался ли я, но это, вероятно, работает, потому что примитивы (потоки POSIX) довольно стандартны для разных ОС.
3

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

  • 0
    SIGPIPE не отправляется при закрытии канала, он отправляется только тогда, когда ребенок пытается написать в него.
2

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

Предположим, что у вас есть два дескриптора файла сокета fd1, fd2. Теперь fork() создадим дочерний процесс, который наследует fds. В родителе вы закрываете fd2, а у ребенка вы закрываете fd1. Теперь каждый процесс может 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, но я действительно не тестировал.

См. также:

  • unix(7) справочных страниц Linux, unix(4) для FreeBSD, poll(2), socketpair(2), socket(7) в Linux.
  • 0
    Очень круто, мне действительно интересно, если это имеет какие-либо проблемы с надежностью, хотя. Вы проверяли это в производстве? С разными приложениями?
  • 0
    @ Актау, я использовал Python-эквивалент этого трюка в программе для Linux. Я нуждался в этом, потому что рабочая логика ребенка заключается в том, чтобы «сделать все возможное для очистки после выхода из родительского режима и затем выйти из него». Тем не менее, я действительно не уверен насчет других платформ. Фрагмент C работает на Linux и FreeBSD, но это все, что я знаю ... Кроме того, есть случаи, когда вы должны быть осторожны, например, снова разветвление родителя или отказ родителя от fd перед настоящим выходом (таким образом, создается временное окно для состояние гонки).
Показать ещё 1 комментарий
1

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

Я пытаюсь использовать 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();
}
1

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

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

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

EDIT 130118:

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

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

  • 1
    PID equals 1 ожидание недействительно. Новый родитель может быть другим PID. Вы должны проверить, изменяется ли он от исходного родителя (getpid () перед fork ()) на нового родителя (getppid () в потомке, который не равен getpid () при вызове перед fork ()).
1

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

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

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

Вы можете легко протестировать его с помощью:

(cat && kill 0) | python

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

  • 1
    (echo -e "print(2+2)\n" & kill 0) | sh -c "python -" радостно печатает 4 вместо завершенных
1

В POSIX функции exit(), _exit() и _exit() определены как:

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

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

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

  • 3
    Хм. Документация является расплывчатой и противоречивой по этому вопросу, но кажется, что родительский процесс должен быть ведущим процессом для сеанса, а не только группой процессов. Лидирующим процессом для сеанса всегда был вход в систему, и заставить мой процесс вступить во владение как ведущий процесс для нового сеанса в настоящее время был за пределами моих возможностей.
  • 2
    SIGHUP эффективно отправляется дочерним процессам, только если выходящий процесс является оболочкой входа в систему. opengroup.org/onlinepubs/009695399/functions/exit.html «Завершение процесса напрямую не прекращает его дочерние элементы . Отправка сигнала SIGHUP, как описано ниже, косвенно завершает дочерние процессы / в некоторых обстоятельствах /».
Показать ещё 2 комментария
0

Другим способом сделать это, что является конкретным Linux, является создание родителя в новом пространстве имен PID. Тогда он будет PID 1 в этом пространстве имен, и когда он выйдет из него, все его дети будут немедленно убиты с помощью SIGKILL.

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

См. clone (2), pid_namespaces (7) и unshare (2).

0

Исторически, из UNIX v7 система процессов обнаружила сиротность процессов, проверяя родительский идентификатор процесса. Как я уже сказал, исторический процесс init(8) - это особый процесс только по одной причине: он не может умереть. Он не может умереть, потому что алгоритм ядра, связанный с назначением нового идентификатора родительского процесса, зависит от этого факта. когда процесс выполняет свой вызов exit(2) (посредством системного вызова процесса или внешней задачи, отправляя ему сигнал или тому подобное), ядро ​​переназначает всех дочерних элементов этого процесса идентификатором процесса инициализации как своего родительского идентификатора процесса. Это приводит к самому простому испытанию и наиболее переносимому способу узнать, получил ли процесс сирота. Просто проверьте результат системного вызова getppid(2) и, если он является идентификатором процесса процесса init(2), тогда процесс получил сироту перед системным вызовом.

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

  • во-первых, у нас есть возможность изменить процесс init на любой пользовательский процесс, поэтому как мы можем гарантировать, что процесс init всегда будет родителем всех сиротских процессов? Ну, в системном коде exit есть явная проверка, чтобы увидеть, является ли процесс, выполняющий вызов, процессом init (процесс с pid равным 1), и если это произойдет, ядро ​​паники (оно не должно быть способным чтобы поддерживать иерархию процессов), поэтому для процесса init не разрешается вызов exit(2).
  • Во-вторых, есть условие гонки в базовом тесте, вышеописанном. Идентификатор процесса инициализации считается исторически 1, но это не оправдывается подходом POSIX, который указывает (как показано в другом ответе), что для этой цели зарезервирован только идентификатор системного процесса. Практически ни одна реализация posix не делает этого, и вы можете предположить, что в исходных системах, основанных на unix, наличие 1 в качестве ответа системного вызова getppid(2) достаточно, чтобы предположить, что процесс является сиротой. Другой способ проверить - сделать getppid(2) сразу после fork и сравнить это значение с результатом нового вызова. Это просто не работает во всех случаях, так как оба вызова не являются атомарными вместе, и родительский процесс может умереть после 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)
0

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

Трюк:

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

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

  • если процесс A умирает: процесс B получает SIGPIPE и умирает
  • если процесс B умирает: процесс Ожидание() возвращает и умирает, процесс C получает SIGHUP (потому что когда лидер сеанса сеанса с подключенным терминалом умирает, все процессы в группе процессов переднего плана получают SIGHUP)
  • если процесс C умирает: процесс B получает SIGCHLD и умирает, поэтому процесс A умирает

Недостатки:

  • процесс C не может обрабатывать SIGHUP
  • процесс C будет запущен на другом сеансе
  • процесс C не может использовать API группы сеанса/процесса, потому что он нарушит хрупкую настройку
  • создание терминала для каждой такой операции не самая лучшая идея когда-либо
0

Я нашел 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();
      });
    }

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

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

0

Если родитель умирает, PPID сирот меняется на 1 - вам нужно только проверить свой собственный PPID. В некотором смысле, это опрос, упомянутый выше. вот кусок оболочки для этого:

check_parent () {
      parent=`ps -f|awk '$2=='$PID'{print $3 }'`
      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
  • 0
    Это вопрос C?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню