Załóżmy, że mam proces, który odradza dokładnie jeden proces potomny. Teraz, gdy proces nadrzędny kończy działanie z jakiegokolwiek powodu (normalnie lub nienormalnie, przez zabicie, ^ C, stwierdzenie niepowodzenia lub cokolwiek innego) chcę, aby proces potomny umarł. Jak to zrobić poprawnie?
Kilka podobnych pytań na temat stackoverflow:
- (zapytano wcześniej) Jak mogę zakończyć proces potomny, gdy rodzic to zrobi?
- (zapytano później) Czy procesy potomne tworzone za pomocą fork () są automatycznie zabijane po zabiciu rodzica?
Kilka podobnych pytań na temat stackoverflow dla Windows :
prctl()
w sposób wolny od wyścigów. Przy okazji odpowiedź połączona przez Maxima jest nieprawidłowa.man prctl
mówi: Ustaw sygnał śmierci procesu nadrzędnego procesu wywołującego na arg2 (wartość sygnału z zakresu 1..maks. lub 0, aby usunąć). Jest to sygnał, który otrzyma proces wywoływania, gdy jego rodzic umrze. Ta wartość jest usuwana dla potomka rozwidlenia (2) i (od Linuksa 2.4.36 / 2.6.23) podczas wykonywania pliku binarnego set-user-ID lub set-group-ID.Próbuję rozwiązać ten sam problem, a ponieważ mój program musi działać w systemie OS X, rozwiązanie tylko dla systemu Linux nie zadziałało.
Doszedłem do tego samego wniosku, co inni ludzie na tej stronie - nie ma zgodnego z POSIX sposobu powiadamiania dziecka o śmierci rodzica. Więc wymyśliłem kolejną najlepszą rzecz - przeprowadzenie ankiety dla dzieci.
Kiedy proces nadrzędny umiera (z dowolnego powodu) proces nadrzędny dziecka staje się procesem 1. Jeśli dziecko po prostu okresowo odpytuje, może sprawdzić, czy jego rodzic ma 1. Jeśli tak, dziecko powinno wyjść.
To nie jest świetne, ale działa i jest łatwiejsze niż rozwiązania do sondowania gniazd / blokad plików TCP sugerowane gdzie indziej na tej stronie.
źródło
gettpid()
nie zmienia się na 1, ale pobierapid
harmonogram (proceszsched
) strefy .Osiągnąłem to w przeszłości, uruchamiając „oryginalny” kod w „dziecku” i „spawnowany” kod w „rodzicu” (to znaczy: odwracasz zwykły sens testu po
fork()
). Następnie pułapkę SIGCHLD w „spawnowanym” kodzie ...Może nie być możliwe w twoim przypadku, ale urocze, gdy działa.
źródło
Jeśli nie możesz zmodyfikować procesu potomnego, możesz spróbować czegoś takiego:
Spowoduje to uruchomienie potomka z poziomu procesu powłoki z włączoną kontrolą zadań. Proces potomny pojawia się w tle. Powłoka czeka na nową linię (lub EOF), a następnie zabija dziecko.
Kiedy rodzic umiera - bez względu na przyczynę - zamyka koniec rury. Powłoka podrzędna pobierze EOF z odczytu i zabije proces potomny w tle.
źródło
dup2
przejęcia stdin, używającread -u
flagi do odczytu z określonego deskryptora pliku. Dodałem równieżsetpgid(0, 0)
dziecko, aby zapobiec jego wyjściu po naciśnięciu ^ C w terminalu.dup2()
połączenia jest niepoprawna. Jeśli chcesz użyćpipes[0]
jako standard, musiszdup2(pipes[0], 0)
zamiast pisaćdup2(0, pipes[0])
. Todup2(oldfd, newfd)
tam połączenie zamyka poprzednio otwarte newfd.W systemie Linux możesz zainstalować nadrzędny sygnał śmierci u dziecka, np .:
Należy pamiętać, że przechowywanie identyfikatora procesu nadrzędnego przed rozwidleniem i testowanie go w potomku po
prctl()
wyeliminowaniu warunku wyścigu międzyprctl()
zakończeniem procesu, który wywołał dziecko.Należy również pamiętać, że sygnał śmierci rodzica dziecka jest usuwany przez nowo utworzone dzieci. Nie ma na to wpływu
execve()
.Ten test można uprościć, jeśli mamy pewność, że proces systemowy odpowiedzialny za przyjęcie wszystkich sierot ma PID 1:
Jednak poleganie na tym procesie systemowym
init
i posiadanie PID 1 nie jest przenośne. POSIX.1-2008 określa :Tradycyjnie proces systemowy przyjmujący wszystkie sieroty to PID 1, tj. Init - który jest przodkiem wszystkich procesów.
W nowoczesnych systemach, takich jak Linux czy FreeBSD, inny proces może mieć taką rolę. Na przykład w systemie Linux proces może zostać wywołany,
prctl(PR_SET_CHILD_SUBREAPER, 1)
aby ustanowić się jako proces systemowy, który dziedziczy wszystkie sieroty każdego z jego potomków (por. Przykład w Fedorze 25).źródło
init(8)
procesem ... jedyne, co możesz założyć, to to, że kiedy proces nadrzędny umiera, to jego identyfikator nadrzędny zmieni się. Zdarza się to raz w życiu procesu ... i to wtedy, gdy umiera rodzic procesu. Jest tylko jeden główny wyjątek od tej zasady i dotyczyinit(8)
dzieci, ale jesteś chroniony przed tym, jakinit(8)
nigdyexit(2)
(w tym przypadku jądra panikuje)Dla kompletności. W systemie macOS możesz użyć kqueue:
źródło
NSTask
lub posix spawn. ZobaczstartTask
funkcję w moim kodzie tutaj: github.com/neoneye/newton-commander-browse/blob/master/Classes/…Czy proces potomny ma potok do / z procesu macierzystego? Jeśli tak, otrzymasz SIGPIPE podczas pisania lub otrzymasz EOF podczas czytania - te warunki można wykryć.
źródło
Zainspirowany inną odpowiedzią tutaj, wymyśliłem następujące rozwiązanie all-POSIX. Ogólna idea polega na utworzeniu pośredniego procesu między rodzicem a dzieckiem, który ma jeden cel: Powiadom, kiedy rodzic umiera, i wyraźnie zabij dziecko.
Ten rodzaj rozwiązania jest przydatny, gdy nie można zmodyfikować kodu w obiekcie potomnym.
Istnieją dwie małe zastrzeżenia dotyczące tej metody:
Nawiasem mówiąc, faktyczny kod, którego używam, znajduje się w Pythonie. Tutaj jest dla kompletności:
źródło
Nie sądzę, że można zagwarantować, że używa się tylko standardowych wywołań POSIX. Podobnie jak w prawdziwym życiu, po odrodzeniu dziecko ma swoje własne życie.
To jest możliwe, proces nadrzędny złapać większość możliwych zdarzeń terminacji i próbować zabić procesu potomnego w tym momencie, ale zawsze jest pewne, że nie można złapać.
Na przykład żaden proces nie może złapać
SIGKILL
. Kiedy jądro obsługuje ten sygnał, zabije określony proces bez żadnego powiadomienia o tym procesie.Aby rozszerzyć analogię - jedynym innym standardowym sposobem jest popełnienie samobójstwa przez dziecko, gdy stwierdzi, że nie ma już rodzica.
Można to zrobić tylko na Linuksie
prctl(2)
- zobacz inne odpowiedzi.źródło
Jak zauważyli inni ludzie, poleganie na pid rodzica, który ma wartość 1, gdy rodzic wychodzi, jest nieprzenośne. Zamiast czekać na określony identyfikator procesu nadrzędnego, wystarczy poczekać na zmianę identyfikatora:
Dodaj mikro-sen według potrzeb, jeśli nie chcesz sondować z pełną prędkością.
Ta opcja wydaje mi się prostsza niż używanie potoku lub poleganie na sygnałach.
źródło
getpid()
odbywa się u rodzica przed wywołaniemfork()
. Jeśli rodzic umrze wcześniej, dziecko nie istnieje. To, co może się zdarzyć, to dziecko przez jakiś czas żyjące z rodzicem.Zainstaluj moduł obsługi pułapek, aby złapać SIGINT, który zabija proces potomny, jeśli nadal żyje, chociaż inne plakaty mają rację, że nie złapie SIGKILL.
Otwórz plik .lock z wyłącznym dostępem i poproś, aby sondowanie potomne próbowało go otworzyć - jeśli otwarcie się powiedzie, proces potomny powinien wyjść
źródło
To rozwiązanie działało dla mnie:
Dotyczyło to procesu typu robotniczego, którego istnienie miało sens tylko wtedy, gdy rodzic żył.
źródło
Myślę, że szybkim i brudnym sposobem jest stworzenie rury między dzieckiem a rodzicem. Kiedy rodzic wyjdzie, dzieci otrzymają SIGPIPE.
źródło
Niektóre plakaty wspominały już o fajkach i
kqueue
. W rzeczywistości możesz również stworzyć parę połączonych gniazd domeny Unix przezsocketpair()
połączenie. Typ gniazda powinien byćSOCK_STREAM
.Załóżmy, że masz dwa deskryptory plików gniazda fd1, fd2. Teraz,
fork()
aby utworzyć proces potomny, który odziedziczy fds. W rodzicu zamykasz Fd2, au dziecka zamykasz Fd1. Teraz każdy proces możepoll()
pozostać otwarty fd na swoim końcu dlaPOLLIN
zdarzenia. Tak długo, jak każda strona nie ma wyraźnieclose()
swojego fd podczas normalnego życia, możesz być całkiem pewien, żePOLLHUP
flaga powinna wskazywać zakończenie drugiej strony (bez względu na to, czy jest czyste czy nie). Po powiadomieniu o tym zdarzeniu dziecko może zdecydować, co robić (np. Umrzeć).Możesz spróbować skompilować powyższy kod proof-of-concept i uruchomić go w podobnym terminalu
./a.out &
. Masz około 100 sekund na eksperymentowanie z zabijaniem macierzystego PID różnymi sygnałami, albo po prostu wyjdzie. W obu przypadkach powinien zostać wyświetlony komunikat „dziecko: rodzic rozłączył się”.W porównaniu z metodą używającą modułu
SIGPIPE
obsługi ta metoda nie wymaga próbywrite()
wywołania.Ta metoda jest również symetryczna , tzn. Procesy mogą używać tego samego kanału do wzajemnego monitorowania swojego istnienia.
To rozwiązanie wywołuje tylko funkcje POSIX. Próbowałem tego w systemie Linux i FreeBSD. Myślę, że to powinno działać na innych Uniksach, ale tak naprawdę nie testowałem.
Zobacz też:
unix(7)
stron man Linux,unix(4)
FreeBSD,poll(2)
,socketpair(2)
,socket(7)
w systemie Linux.źródło
Zgodnie z POSIX , to
exit()
,_exit()
i_Exit()
funkcje są zdefiniowane:Jeśli więc ustawisz, że proces nadrzędny jest procesem kontrolnym dla jego grupy procesów, dziecko powinno otrzymać sygnał SIGHUP, gdy rodzic wyjdzie. Nie jestem absolutnie pewien, że tak się stanie, gdy rodzic się zawiesi, ale myślę, że tak się dzieje. Z pewnością w przypadku spraw niezwiązanych z awariami powinno działać dobrze.
Należy pamiętać, że może trzeba przeczytać sporo drobnym drukiem - w tym dziale Baza Definicje (Definicje), jak również informacje systemowe Usługi dla
exit()
asetsid()
isetpgrp()
- aby uzyskać pełny obraz. (Więc mógłbym!)źródło
Jeśli wysyłasz sygnał do pid 0, używając na przykład
sygnał ten jest wysyłany do całej grupy procesów, co skutecznie zabija dziecko.
Możesz to łatwo przetestować za pomocą czegoś takiego:
Jeśli następnie naciśniesz ^ D, zobaczysz tekst
"Terminated"
jako wskazówkę, że interpreter Pythona rzeczywiście został zabity, a nie tylko zakończony z powodu zamknięcia standardowego wejścia.źródło
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -"
szczęśliwie drukuje 4 zamiast TerminatedW przypadku, gdy ma to znaczenie dla kogokolwiek innego, kiedy spawnuję instancje JVM w rozwidlonych procesach potomnych z C ++, jedynym sposobem, aby uzyskać prawidłowe zakończenie instancji JVM po zakończeniu procesu nadrzędnego, było wykonanie następujących czynności. Mam nadzieję, że ktoś może przekazać opinię w komentarzach, jeśli nie byłby to najlepszy sposób.
1) Wywołaj
prctl(PR_SET_PDEATHSIG, SIGHUP)
rozwidlony proces potomny zgodnie z sugestią przed uruchomieniem aplikacji Java za pomocąexecv
, i2) Dodaj hak zamykający do aplikacji Java, która odpytuje, aż jej nadrzędny PID wyniesie 1, a następnie wykonaj trudne
Runtime.getRuntime().halt(0)
. Odpytywanie odbywa się poprzez uruchomienie osobnej powłoki, która uruchamiaps
polecenie (patrz: Jak znaleźć mój PID w Javie lub JRuby w Linuksie? ).EDYCJA 130118:
Wygląda na to, że nie było to solidne rozwiązanie. Wciąż trochę próbuję zrozumieć niuanse tego, co się dzieje, ale czasami czasami otrzymywałem osierocone procesy JVM podczas uruchamiania tych aplikacji w sesjach screen / SSH.
Zamiast odpytywania o PPID w aplikacji Java, po prostu kazałem hakowi zamykającemu wykonać czyszczenie, po czym nastąpiło twarde zatrzymanie, jak powyżej. Potem upewniłem się, że wywołałem
waitpid
w aplikacji nadrzędnej C ++ w odrodzonym procesie potomnym, kiedy nadszedł czas, aby zakończyć wszystko. Wydaje się, że jest to bardziej niezawodne rozwiązanie, ponieważ proces potomny zapewnia jego zakończenie, podczas gdy rodzic używa istniejących referencji, aby upewnić się, że jego potomki się kończą. Porównaj to z poprzednim rozwiązaniem, w którym proces nadrzędny zakończył się, gdy tylko zechce, a dzieci próbowały dowiedzieć się, czy zostały osierocone przed zakończeniem.źródło
PID equals 1
Czekanie nie jest ważna. Nowym rodzicem może być inny PID. Powinieneś sprawdzić, czy zmienia się z pierwotnego elementu nadrzędnego (getpid () przed rozwidleniem ()) na nowego elementu nadrzędnego (getppid () w dziecku nie jest równy getpid () po wywołaniu przed rozwidleniem ()).Innym sposobem na to, specyficznym dla Linuksa, jest utworzenie elementu nadrzędnego w nowej przestrzeni nazw PID. Będzie to PID 1 w tej przestrzeni nazw, a kiedy wyjdzie, wszystkie dzieci zostaną natychmiast zabite
SIGKILL
.Niestety, aby utworzyć nową przestrzeń nazw PID, musisz mieć
CAP_SYS_ADMIN
. Ale ta metoda jest bardzo skuteczna i nie wymaga żadnej realnej zmiany dla rodzica ani dzieci poza początkowym uruchomieniem rodzica.Zobacz clone (2) , pid_namespaces (7) i unshare (2) .
źródło
Jeśli rodzic umiera, PPID sierot zmienia się na 1 - wystarczy tylko sprawdzić swój własny PPID. W pewnym sensie jest to odpytywanie, wspomniane powyżej. oto kawałek skorupy do tego:
źródło
Znalazłem 2 rozwiązania, oba nie są idealne.
1. Zabij wszystkie dzieci zabijając (-pid) po otrzymaniu sygnału SIGTERM.
Oczywiście to rozwiązanie nie obsługuje „zabicia -9”, ale działa w większości przypadków i jest bardzo proste, ponieważ nie musi pamiętać wszystkich procesów potomnych.
W ten sam sposób możesz zainstalować program obsługi wyjścia tak jak powyżej, jeśli gdzieś wywołasz proces.exit. Uwaga: Ctrl + C i nagła awaria zostały automatycznie przetworzone przez system operacyjny w celu zabicia grupy procesów, więc nie więcej tutaj.
2. Użyj chjj / pty.js, aby spawnować proces z podłączonym terminalem kontrolnym.
Kiedy zabijesz obecny proces, a nawet zabijesz -9, wszystkie procesy potomne również zostaną automatycznie zabite (przez system operacyjny?). Wydaje mi się, że ponieważ bieżący proces ma inną stronę terminala, więc jeśli aktualny proces umrze, proces potomny otrzyma SIGPIPE i tak umrze.
źródło
Udało mi się stworzyć przenośne rozwiązanie bez odpytywania z 3 procesami, nadużywając kontroli terminali i sesji. To jest masturbacja mentalna, ale działa.
Sztuką jest:
W ten sposób:
Niedociągnięcia:
źródło
Mimo, że minęło 7 lat, właśnie natknąłem się na ten problem, ponieważ uruchamiam aplikację SpringBoot, która musi uruchamiać webpack-dev-server podczas programowania i musi go zabić po zatrzymaniu procesu zaplecza.
Próbuję użyć,
Runtime.getRuntime().addShutdownHook
ale działało to w systemie Windows 10, ale nie w systemie Windows 7.Zmieniłem go, aby używał dedykowanego wątku, który czeka na zakończenie procesu lub dla
InterruptedException
którego wydaje się działać poprawnie w obu wersjach systemu Windows.źródło
Historycznie, od UNIX v7, system procesów wykrył osierocenie procesów poprzez sprawdzenie nadrzędnego identyfikatora procesu. Jak mówię, historycznie
init(8)
proces systemowy jest procesem specjalnym tylko z jednego powodu: nie może umrzeć. Nie może umrzeć, ponieważ algorytm jądra zajmujący się przypisywaniem nowego identyfikatora procesu nadrzędnego zależy od tego faktu. kiedy proces wykonuje swojeexit(2)
wywołanie (za pomocą wywołania systemowego procesu lub zadania zewnętrznego, wysyłając mu sygnał itp.), jądro ponownie przypisuje wszystkim elementom potomnym tego procesu identyfikator procesu inicjującego jako identyfikator procesu nadrzędnego. Prowadzi to do najłatwiejszego testu i najbardziej przenośnego sposobu sprawdzenia, czy proces został osierocony. Wystarczy sprawdzić wynikgetppid(2)
wywołania systemowego i czy jest to identyfikator procesuinit(2)
proces, a następnie proces został osierocony przed wywołaniem systemowym.Z tego podejścia wynikają dwa problemy, które mogą prowadzić do problemów:
init
procesu na dowolny proces użytkownika, więc w jaki sposób możemy zapewnić, że proces init zawsze będzie nadrzędny dla wszystkich procesów osieroconych? Cóż, wexit
kodzie wywołania systemowego jest wyraźne sprawdzenie, czy proces wykonujący wywołanie jest procesem init (proces z pid równym 1), a jeśli tak jest, jądro wpadnie w panikę (nie powinno już być w stanie utrzymać hierarchia procesów), więc proces inicjujący nie może wykonywaćexit(2)
połączenia.1
, że identyfikator procesu początkowego jest , ale nie jest to uzasadnione w podejściu POSIX, który stwierdza (ujawniony w innej odpowiedzi), że tylko identyfikator procesu systemu jest zarezerwowany do tego celu. Prawie żadna implementacja posixa tego nie robi i można założyć w oryginalnych systemach uniksowych, że1
jako odpowiedź nagetppid(2)
wywołanie systemowe wystarczy, aby założyć, że proces jest osierocony. Innym sposobem sprawdzenia jest wykonaniegetppid(2)
tuż po rozwidleniu i porównanie tej wartości z wynikiem nowego wywołania. To po prostu nie działa we wszystkich przypadkach, ponieważ oba wywołania nie są atomowe razem, a proces nadrzędny może umrzeć pofork(2)
pierwszymgetppid(2)
wywołaniu systemowym i przed nim .parent id only changes once, when its parent does an
Wyjście procesu (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) `, ale możesz bezpiecznie założyć, że te procesy również nie mają rodzica (z wyjątkiem sytuacji, gdy w systemie zastąpisz proces init)źródło
Przekazałem rodzicowi pid za pomocą środowiska dla dziecka, a następnie okresowo sprawdzałem, czy / proc / $ ppid istnieje od dziecka.
źródło