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

209

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


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


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

Павел Хайдан
источник

Ответы:

189

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

prctl(PR_SET_PDEATHSIG, SIGHUP);

Смотрите man 2 prctlподробности.

Редактировать: это только для Linux

qrdl
источник
5
Это плохое решение, потому что родитель, возможно, уже умер. Расовое состояние. Правильное решение: stackoverflow.com/a/17589555/412080
Максим Егорушкин
16
Называть ответ плохим не очень хорошо - даже если он не отвечает условиям гонки. Смотрите мой ответ о том, как использовать prctl()в условиях гонки бесплатно. Кстати, ответ Максима неверен.
maxschlepzig
4
Это просто неправильный ответ. Он отправит сигнал дочернему процессу в тот момент, когда умирает поток, вызвавший fork, а не когда родительский процесс умирает.
Лотар
2
@ Лотар Было бы приятно увидеть какое-то доказательство. man prctlговорит: Установите для сигнала смерти родительского процесса вызывающего процесса значение arg2 (либо значение сигнала в диапазоне 1..maxsig, либо 0 для сброса). Это сигнал, который вызывающий процесс получит, когда его родитель умирает. Это значение очищается для потомка fork (2) и (начиная с Linux 2.4.36 / 2.6.23) при выполнении двоичного файла set-user-ID или set-group-ID.
qrdl
1
@maxschlepzig Спасибо за новую ссылку. Похоже, предыдущая ссылка недействительна. Кстати, спустя годы все еще нет API для настройки параметров на родительской стороне. Какая жалость.
Rox
68

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

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

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

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

Schof
источник
6
Отличное решение. Продолжайте вызывать getppid () до тех пор, пока он не вернет 1, а затем завершите работу. Это хорошо, и теперь я тоже им пользуюсь. Хотя решение не было бы неплохим. Спасибо, Шоф.
neoneye
10
Просто для информации, в Solaris, если вы находитесь в зоне, gettpid()она не становится 1, а получает pidпланировщик зоны (процесс zsched).
Патрик Шлютер
4
Если кому-то интересно, в Android-системах pid кажется 0 (процесс System pid) вместо 1, когда родитель умирает.
Руи Маркес
2
Чтобы иметь более надежный и независимый от платформы способ сделать это, перед fork () - просто getpid () и, если getppid () от child отличается, выйдите.
Себастьян
2
Это не работает, если вы не контролируете дочерний процесс. Например, я работаю над командой, которая переносит find (1), и я хочу убедиться, что находка уничтожена, если оболочка по какой-то причине умрет.
Лукретиэль
34

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

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

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

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

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

Фил Рутшман
источник
2
Хорошо, но пять системных вызовов и sh, порожденные десятью строками кода, позволяют мне немного скептически относиться к этой части кода.
Олеяд
+1. Вы можете избежать ввода dup2и ввода stdin, используя read -uфлаг для чтения из определенного дескриптора файла. Я также добавил setpgid(0, 0)в дочерний элемент, чтобы предотвратить его выход при нажатии ^ C в терминале.
Грег Хьюгилл
Порядок аргументов dup2()вызова неверен. Если вы хотите использовать в pipes[0]качестве стандартного ввода, вы должны написать dup2(pipes[0], 0)вместо dup2(0, pipes[0]). Это dup2(oldfd, newfd)где вызов закрывает ранее открытый newfd.
maxschlepzig
@Oleiade, я согласен, тем более что порожденный sh делает еще одну форк для выполнения реального дочернего процесса ...
maxschlepzig
16

В 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 ...

Обратите внимание, что хранение идентификатора родительского процесса до разветвления и его тестирование в дочернем процессе 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).

maxschlepzig
источник
Я не понимаю, «этот тест можно упростить, если мы уверены, что дедушка всегда инициирует процесс». Когда родительский процесс умирает, процесс становится дочерним по отношению к процессу init (pid 1), а не дочерним по отношению к деду, верно? Таким образом, тест всегда кажется правильным.
Йоханнес Шауб -
интересно, спасибо. хотя я не вижу, что это имеет отношение к бабушке и дедушке.
Йоханнес Шауб - Lit
1
@ JohannesSchaub-litb, вы не можете всегда предполагать, что бабушка или родитель процесса будет init(8)процессом ... единственное, что вы можете предположить, это то, что, когда родительский процесс умирает, это то, что его родительский идентификатор изменится. Это на самом деле происходит один раз в жизни процесса .... и когда умирает родитель процесса. Есть только одно главное исключение, это касается init(8)детей, но вы защищены от этого, как init(8)никогда exit(2)(ядро в этом случае паникует)
Луис Колорадо,
1
К сожалению, если дочерний процесс разветвляется из потока, а затем завершается, дочерний процесс получит SIGTERM.
Rox
14

Ради полноты. В 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);
}
neoneye
источник
Вы можете сделать это с немного более приятным API, используя источники отправки с DISPATCH_SOURCE_PROC и PROC_EXIT.
русский епископ
По какой-то причине, это вызывает мой Mac в панике. Запуск процесса с этим кодом с вероятностью 50% может привести к его зависанию, заставляя фанатов вращаться со скоростью, о которой я никогда не слышал, чтобы они работали раньше (супер быстро), а затем Mac просто отключается. БУДЬТЕ ОЧЕНЬ ОСТОРОЖНЫ С ЭТИМ КОДОМ .
Qix - МОНИКА БЫЛА ПОВРЕЖДЕНА
Похоже, на моем MacOS дочерний процесс завершается автоматически после выхода из родительского процесса. Я не знаю почему.
Йи Лин Лю
@YiLinLiu iirc Я пользовался NSTaskили posix spawn. Смотрите startTaskфункцию в моем коде здесь: github.com/neoneye/newton-commander-browse/blob/master/Classes/...
neoneye
11

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

MarkR
источник
1
Я обнаружил, что это не произошло надежно, по крайней мере на OS X.
Schof
Я пришел сюда, чтобы добавить этот ответ. Это определенно решение, которое я бы использовал для этого случая.
Dolda2000 30.01.15
Внимание: systemd отключает SIGPIPE по умолчанию в службах, которыми он управляет, но вы все равно можете проверить закрытие канала. См freedesktop.org/software/systemd/man/systemd.exec.html под IgnoreSIGPIPE
jdizzle
11

Вдохновленный другим ответом здесь, я придумал следующее решение для всех 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, если один из них умер. Так я обычно убивал своих детей fork () без необходимости промежуточного процесса.
Алексис Вилке
Я думаю, что ваша вторая оговорка не так. Pid дочернего элемента - это ресурс, принадлежащий его родительскому элементу, и он не может быть освобожден / использован повторно до тех пор, пока родительский элемент (промежуточный процесс) не ожидает его (или не завершается и не позволяет init ожидать его).
R .. GitHub ОСТАНОВИТЬ ПОМОЩЬ ЛЬДУ
7

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

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

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

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

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

Альнитак
источник
6

Как уже отмечали другие люди, полагаться на родительский 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)
    ;

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

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

user2168915
источник
К сожалению, это решение не является надежным. Что если родительский процесс умирает до того, как вы получите начальное значение? Ребенок никогда не выйдет.
dgatwood
@dgatwood, что ты имеешь в виду?!? Первый getpid()делается в родительском до вызова fork(). Если родитель умирает до этого, ребенка не существует. Может случиться так, что ребенок некоторое время живет с родителем.
Алексис Вилке
В этом несколько надуманном примере это работает, но в реальном коде за форком почти всегда следует exec, и новый процесс должен начинаться заново с запроса PPID. В промежутке между этими двумя проверками, если родитель уйдет, ребенок понятия не имеет. Кроме того, вы вряд ли сможете контролировать как родительский, так и дочерний код (иначе вы можете просто передать PPID в качестве аргумента). Таким образом, в качестве общего решения, этот подход не очень хорошо работает. И реально, если бы UNIX-подобная ОС вышла без init, равного 1, столько всего сломалось бы, что я не могу представить, чтобы кто-нибудь делал это в любом случае.
dgatwood
pass parent pid является аргументом командной строки при выполнении exec для child.
Nish
2
Голосование на полной скорости безумно.
maxschlepzig
4

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

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

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

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

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

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

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

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

Yefei
источник
SIGPIPE не отправляется при закрытии канала, он отправляется только тогда, когда ребенок пытается написать в него.
Алькаро
3

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

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

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

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

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

Смотрите также:

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

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

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

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

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

Джонатан Леффлер
источник
3
Хм. Документация является расплывчатой ​​и противоречивой по этому вопросу, но кажется, что родительский процесс должен быть ведущим процессом для сеанса, а не только группой процессов. Лидирующим процессом для сессии всегда был вход в систему, и заставить мой процесс вступить во владение в качестве ведущего процесса для новой сессии в настоящее время я не мог.
Шоф
2
SIGHUP эффективно отправляется дочерним процессам, только если выходящий процесс является оболочкой входа в систему. opengroup.org/onlinepubs/009695399/functions/exit.html «Завершение процесса напрямую не прекращает его дочерние элементы . Отправка сигнала SIGHUP, как описано ниже, косвенно завершает дочерние процессы / в некоторых обстоятельствах /».
Роб К
1
@Rob: правильно - это то, о чем я тоже говорил, что только при некоторых обстоятельствах дочерний процесс получает SIGHUP. И говорить о том, что отправка SIGHUP - это только оболочка входа в систему, слишком упрощенно, хотя это наиболее распространенный случай. Если процесс с несколькими дочерними элементами устанавливает себя в качестве процесса управления для себя и своих дочерних элементов, то SIGHUP будет (удобно) отправляться его дочерним элементам, когда мастер умирает. OTOH, процессы редко приводят к такому большому количеству проблем - так что я больше придираюсь, чем поднимаю действительно значительную болтовню.
Джонатан Леффлер
2
Я дурачился с ним пару часов и не мог заставить его работать. Было бы неплохо справиться со случаем, когда у меня есть демон с несколькими детьми, которым все должны умереть, когда родитель выходит.
Роб К
1

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

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

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

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

(cat && kill 0) | python

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

Торбьерн Фрицзон
источник
1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" радостно печатает 4 вместо уничтоженных
Камил Шот
1

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

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

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

РЕДАКТИРОВАТЬ 130118:

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

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

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

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

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

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

всевозможный
источник
Мне нужно отредактировать по-другому. Можно использовать prctl, чтобы процесс стал процессом инициализации для всех его детей, внуков, правнуков и т. Д.
Всевышний
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

Я нашел 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.Use 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(.....);
    */
osexp2003
источник
0

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

Хитрость заключается в следующем:

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

Туда:

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

Недостатки:

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

Несмотря на то, что прошло 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();
}
Идо Ран
источник
0

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

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

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

Я передал родительский pid с использованием среды дочернему элементу, а затем периодически проверял, существует ли / proc / $ ppid от дочернего элемента.

Сергей Кирьянов
источник