Як змусити дитину померти після виходу з батьків?


209

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


Деякі подібні запитання щодо stackoverflow:


Деякі подібні запитання щодо stackoverflow для Windows :

Відповіді:


189

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

prctl(PR_SET_PDEATHSIG, SIGHUP);

Детальніше man 2 prctlдивіться.

Редагувати: Це лише Linux


5
Це невдале рішення, оскільки батько, можливо, вже помер. Стан гонки. Правильне рішення: stackoverflow.com/a/17589555/412080
Максим Єгорушкін

16
Називати відповідь поганою не дуже приємно - навіть якщо це не стосується перегонів. Дивіться мою відповідь про те, як використовувати prctl()у вільних умовах гонки. До речі, відповідь, пов’язана Максимом, невірна.
maxschlepzig

4
Це просто помилковий антисер. Він буде надсилати сигнал дочірньому процесу в той момент, коли помирає нитка, яка викликала вилку, а не тоді, коли батьківський процес гине.
Лотар

2
@Lothar Було б добре бачити якісь докази. man prctlговорить: Встановіть сигнал смерті батьківського процесу виклику процесу на arg2 (або значення сигналу в діапазоні 1..maxsig, або 0 для очищення). Це сигнал, який отримає процес виклику, коли його батько помре. Це значення очищається для дочірньої вилки (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, запропоновані в інших місцях на цій сторінці.


6
Відмінне рішення. Постійно викликайте getppid (), поки він не поверне 1, а потім вийде. Це добре, і зараз я його також використовую. Хоча було б непоганим рішенням. Дякую, Шоф.
neoneye

10
Щойно для інформації, на Solaris, якщо ви знаходитесь в зоні, gettpid()значення не стає 1, але отримує pidпланові зони (процес zsched).
Патрік Шлютер

4
Якщо хтось цікавиться, в Android системах pid здається 0 (процес System pid) замість 1, коли батько помирає.
Rui Marques

2
Щоб мати більш надійний та незалежний від платформи спосіб зробити це, перед fork () - просто, просто getpid (), а якщо getppid () від дитини, вийдіть.
Себастьян

2
Це не працює, якщо ви не контролюєте процес дитини. Наприклад, я працюю над командою, яка обгортає find (1), і я хочу переконатися, що знахідка вбита, якщо обгортка вмирає з якоїсь причини.
Лукретьєль

34

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

У вашому випадку це може бути не можливо, але мило, коли це працює.


Дуже приємне рішення, дякую! Наразі прийнятий - більш загальний, але ваш - більш портативний.
Paweł Hajdan

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
Приємно, але п’ять системних дзвінків, і ш, породжений у десяти рядках кодів, дозволяє мені трохи скептично ставитись до цього виступу коду.
Oleiade

+1. Ви можете уникнути dup2і перейняти stdin, скориставшись read -uпрапором для читання з конкретного дескриптора файлу. Я також додав a setpgid(0, 0)у дитині, щоб запобігти його виходу при натисканні ^ C у терміналі.
Грег Хьюгілл

Порядок аргументу dup2()виклику неправильний. Якщо ви хочете використовувати pipes[0]як stdin, ви повинні писати 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).


Я не розумію, "Цей тест може бути спрощений, якщо ми впевнені, що бабуся і дідусь завжди є процесом init". Коли батьківський процес помирає, процес стає дитиною процесу init (pid 1), а не дитиною бабусі і дідуся, правда? Тож тест завжди здається правильним.
Йоханнес Шауб - ліб


цікаво, спасибі хоча я не бачу, що це стосується бабусі та дідуся.
Йоханнес Шауб - ліб

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

Це можна зробити за допомогою трохи приємнішого 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 під час читання - ці умови можуть бути виявлені.


1
Я виявив, що це не відбулося надійно, принаймні, на OS X.
Schof

Я прийшов сюди, щоб додати цю відповідь. Це, безумовно, рішення, яке я використав би для цієї справи.
Dolda2000

обережність: 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);
}

Існує два невеликих застереження з цим методом:

  • Якщо ви навмисно вбите проміжний процес, то дитина не буде вбита, коли батько помер.
  • Якщо дитина виходить перед батьком, то проміжний процес спробує вбити початковий дочірній під, який тепер міг би посилатися на інший процес. (Це може бути виправлено за допомогою більшої кількості коду в проміжному процесі.)

В бік фактичного коду, який я використовую, знаходиться в 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, якщо хтось із них помер. Саме так я вбивав дітей, що вилили (), без необхідності проміжного процесу.
Алексіс Вілке

Я думаю, що ваш другий застереження помиляється. Pid дитини - це ресурс, що належить його батькові, і його не можна звільнити / повторно використати, поки батько (проміжний процес) не чекає на нього (або припиняється і дозволяє init чекати на нього).
R .. GitHub СТОП ДОПОМОГАЙТЕ

7

Я не вірю, що можна гарантувати використання лише стандартних дзвінків POSIX. Як і в реальному житті, коли дитина народжується, вона має своє власне життя.

Це є можливим для батьківського процесу , щоб зловити найбільш можливе припинення подій, і спробувати вбити процес дитини в той момент, але завжди є деякі , які не можуть бути перехоплені.

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

Розширити аналогію - єдиний інший стандартний спосіб зробити це, щоб дитина покінчила життя самогубством, коли виявить, що у нього більше немає батьків.

Існує лише спосіб для Linux prctl(2)- див. Інші відповіді.


6

Як зазначали інші люди, покладаючись на батьківський під стає 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)
    ;

Додайте мікро-сон за бажанням, якщо ви не хочете опитуватись на повній швидкості.

Цей варіант мені здається більш простим, ніж використання труби або покладання сигналів.


На жаль, це рішення не є надійним. Що робити, якщо батьківський процес загине, перш ніж отримати початкове значення? Дитина ніколи не вийде.
dgatwood

@dgatwood, що ти маєш на увазі?!? Перший getpid()робиться у батьків перед викликом fork(). Якщо батько помирає до цього, дитини не існує. Що може статися, це дитина на деякий час живе батьком.
Алексіс Вілке

У цьому дещо надуманому прикладі він працює, але в реальному коді fork майже незмінно слідує exec, і новий процес повинен починатися заново, запитуючи його PPID. За час між цими двома чеками, якщо батько піде, дитина б не мала поняття. Крім того, ви навряд чи матимете контроль над батьківським та дочірнім кодом (інакше ви можете просто передати PPID як аргумент). Отже, як загальне рішення, такий підхід працює не дуже добре. І реально, якби ОС, схожа на UNIX, вийшла, не будучи ініціатором 1, стільки матеріалів би зламалося, що я не можу уявити, щоб хтось це робив.
dgatwood

передати батьківський pid - це аргумент командного рядка під час виконання програми для дитини.
Ніш

2
Опитування на повній швидкості є божевільним.
maxschlepzig

4

Встановіть оброблювач пасток, щоб зловити SIGINT, який вбиває ваш процес дитини, якщо він ще живий, хоча інші плакати правильні, що він не піймає SIGKILL.

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


Або дитина може відкрити файл блокування окремим потоком, в режимі блокування, і в цьому випадку це може бути досить приємним і чистим рішенням. Можливо, він має деякі обмеження щодо портативності.
Жан

4

Це рішення працювало для мене:

  • Передайте трубопровід stdin дитині - вам не потрібно записувати будь-які дані в потік.
  • Дитина читає нескінченно від stdin до EOF. EOF сигналізує, що батько пішов.
  • Це бездоганний і портативний спосіб виявити, коли батько пішов. Навіть якщо батьківський збій, ОС закриє трубку.

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


@SebastianJylanki Я не пам’ятаю, чи намагався я, але це, ймовірно, працює, тому що примітиви (потоки POSIX) досить стандартні для ОС.
joonas.fi

3

Я думаю, що швидкий і брудний спосіб - створити трубу між дитиною і батьком. Коли батько вийде, діти отримають ПІДПИС.


SIGPIPE не надсилається на трубі, вона надсилається лише тоді, коли дитина намагається написати їй.
Алькаро

3

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

Припустимо, у вас є два дескриптори файлів сокетів fd1, fd2. Тепер fork()створити дочірній процес, який успадкує фдс. У батьків ви закриваєте 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 різними сигналами, інакше він просто вийде. У будь-якому випадку ви повинні побачити повідомлення "дитина: батько повісив трубку".

Порівняно з методом, що використовує SIGPIPEобробник, цей метод не вимагає спробувати write()виклик.

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

Це рішення викликає лише функції POSIX. Я спробував це в Linux та FreeBSD. Я думаю, що це має працювати на інших Unixes, але я насправді не перевіряв.

Дивитися також:

  • unix(7)сторінок людини Linux, unix(4)для FreeBSD, poll(2), socketpair(2), socket(7)на Linux.

Дуже круто, мені дуже цікаво, чи є у цього питання щодо надійності. Ви перевірили це на виробництві? З різними додатками?
Актау

@Aktau, я використовував еквівалент Python цього трюку в програмі Linux. Мені це було потрібно, тому що робоча логіка дитини полягає в тому, щоб "зробити найкращі зусилля після виходу з батьків, а потім теж вийти". Однак я не впевнений в інших платформах. Фрагмент C працює на Linux і FreeBSD, але це все, що я знаю ... Також бувають випадки, коли вам слід бути обережними, наприклад, повторне розщеплення батьків або батьківський відмову від fd перед тим, як дійсно вийти (таким чином створивши вікно часу для стан перегонів).
Cong Ma

@Aktau - Це буде абсолютно надійно.
всезначний

1

У POSIX визначено exit(): _exit()та _Exit()функції:

  • Якщо процес є керуючим процесом, сигнал SIGHUP надсилається кожному процесу в групі процесів переднього плану контрольного терміналу, що належить до виклику.

Отже, якщо ви домовитеся, щоб батьківський процес був контрольним процесом для його групи процесів, дитина повинна отримати сигнал SIGHUP, коли батько виходить. Я не зовсім впевнений, що трапляється, коли батьків виходить з ладу, але я думаю, що так і є. Звичайно, для випадків без збоїв це повинно працювати добре.

Зверніть увагу, що вам, можливо, доведеться прочитати досить дрібний друк - включаючи розділ Основні визначення (визначення), а також інформацію про системні послуги для exit()та setsid()та setpgrp()- щоб отримати повне уявлення. (Так би і я!)


3
Хм. Документація щодо цього розпливчаста і суперечлива, але виявляється, що батьківський процес повинен бути головним процесом сеансу, а не лише групою процесів. Процес провідного сеансу завжди був входом у систему, і отримання мого процесу перейти в якості ведучого для нового сеансу на даний момент було не в силах.
Шоф

2
SIGHUP ефективно надсилається до дочірніх процесів лише в тому випадку, якщо процес виходу є оболонкою для входу. opengroup.org/onlinepubs/009695399/functions/exit.html "Припинення процесу безпосередньо не припиняє його дітей. Відправлення сигналу SIGHUP, як описано нижче, побічно припиняє дітей / за деяких обставин /."
Роб К

1
@Rob: правильно - ось що, і цитата, яку я дав, теж говорить: що лише в деяких обставинах дитина отримує ЗАРАЗ. І суворо спростити, щоб сказати, що це лише оболонка входу, яка надсилає SIGHUP, хоча це найчастіший випадок. Якщо процес з декількома дітьми встановлює себе як контрольний процес для себе та своїх дітей, то СІДУТЬ (зручно) відправити своїм дітям, коли господар помре. ОТО, процеси рідко стикаються з такою великою проблемою, тому я більше забираю нітки, ніж піднімаю справді значну каламбуру.
Джонатан Леффлер

2
Я обдурив його пару годин і не зміг змусити його працювати. Було б чудово розглянути випадок, коли у мене є демон з деякими дітьми, яким всі повинні померти, коли батько виходить.
Роб К

1

Якщо ви надсилаєте сигнал на номер 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

Якщо це стосується будь-кого іншого, коли я створюю екземпляри JVM у роздвоєних дочірніх процесах із C ++, єдиним способом я могла змусити екземпляри 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в батьківському додатку C ++ про породжений дочірній процес, коли настав час все припинити. Це здається більш надійним рішенням, оскільки дочірній процес забезпечує його припинення, тоді як батько використовує існуючі посилання, щоб переконатися, що його діти припиняються. Порівняйте це з попереднім рішенням, яке припиняло батьківський процес коли завгодно, і діти намагалися розібратися, чи були вони сиротами перед тим, як закінчити.


1
PID equals 1Чекати не діє. Новим батьком може бути якийсь інший PID. Ви повинні перевірити, чи він змінюється від початкового батьківського (getpid () перед fork ()) до нового батьківського (getppid () у дитини, що не дорівнює getpid (), коли викликається перед fork ()).
Алексіс Вілке

1

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

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

Перегляньте клон (2) , простори імен pid (7) та скасувати поділу (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.Колять усіх дітей вбивством (-pid), коли отримано сигнал SIGTERM.
Очевидно, що це рішення не може впоратися з "убити -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

Мені вдалося зробити портативне не опитувальне рішення з 3-ма процесами, зловживаючи термінальним керуванням та сеансами. Це розумова мастурбація, але працює.

Хитрість:

  • процес A запускається
  • процес A створює трубку P (і ніколи з неї не читається)
  • обробляти вилки в процес B
  • процес B створює новий сеанс
  • процес B виділяє віртуальний термінал для цього нового сеансу
  • процес B встановлює обробник SIGCHLD для вмирання, коли дитина виходить
  • процес B встановлює обробник SIGPIPE
  • процес B вилки в процес C
  • Процес C робить все, що потрібно (наприклад, exec () з немодифікованим бінарним файлом або виконує будь-яку логіку)
  • процес B записує в трубу P (і таким чином блокує)
  • процес A (()) s процес B і закінчується, коли він гине

Цей шлях:

  • якщо процес A вмирає: процес B отримує SIGPIPE і гине
  • якщо процес B помирає: процес A's wait () повертається і вмирає, процес C отримує SIGHUP (тому що, коли керівник сеансу сеансу з приєднаним терміналом гине, всі процеси в передній план групи процесів отримують SIGHUP)
  • якщо процес C помирає: процес B отримує SIGCHLD і гине, тому процес A гине

Недоліки:

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

0

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

Я намагаюся використовувати, 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), і якщо це так, ядро ​​панікує (воно більше не зможе підтримувати ієрархія процесу), тому ініціальному процесу не дозволяється робити 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

Я передав дитині батьківський під, використовуючи середовище, потім періодично перевіряв, чи / proc / $ ppid існує у дитини.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.