Proces nadrzędny

W informatyce proces nadrzędny to proces, który stworzył jeden lub więcej procesów podrzędnych .

Systemy typu Unix

W systemach operacyjnych typu Unix każdy proces z wyjątkiem procesu 0 (swapper) jest tworzony, gdy inny proces wykonuje wywołanie systemowe fork() . Proces, który wywołał rozwidlenie, jest procesem nadrzędnym , a nowo utworzony proces jest procesem potomnym . Każdy proces (z wyjątkiem procesu 0) ma jeden proces nadrzędny, ale może mieć wiele procesów potomnych.

Jądro systemu operacyjnego identyfikuje każdy proces za pomocą jego identyfikatora procesu. Proces 0 to specjalny proces tworzony podczas uruchamiania systemu; po rozwidleniu procesu potomnego (proces 1), proces 0 staje się procesem wymiany (czasami znanym również jako „ bezczynne zadanie ”). Proces 1 , znany jako init , jest przodkiem każdego innego procesu w systemie.

Linuks

W jądrze Linuksa , w którym istnieje bardzo niewielka różnica między procesami a wątkami POSIX , istnieją dwa rodzaje procesów nadrzędnych, a mianowicie rzeczywisty nadrzędny i nadrzędny. Rodzic to proces, który otrzymuje SIGCHLD po zakończeniu procesu potomnego, podczas gdy prawdziwy rodzic to wątek, który faktycznie utworzył ten proces potomny w środowisku wielowątkowym. W przypadku normalnego procesu obie te wartości są takie same, ale w przypadku wątku POSIX, który działa jako proces, te dwie wartości mogą być różne.

Procesy zombie

System operacyjny utrzymuje tabelę, która łączy każdy proces za pomocą jego identyfikatora procesu (ogólnie określanego jako „ pid ”) z danymi niezbędnymi do jego funkcjonowania. Podczas życia procesu takie dane mogą obejmować segmenty pamięci przydzielone procesowi, argumenty, z którymi został wywołany, zmienne środowiskowe , liczniki dotyczące wykorzystania zasobów, identyfikator użytkownika, identyfikator grupy i zestaw grup oraz być może inne rodzaje informacji.

Gdy proces zakończy swoje wykonywanie, wywołując wyjście (nawet jeśli niejawnie, wykonując polecenie powrotu z funkcji main ) lub odbierając sygnał , który powoduje jego nagłe zakończenie, system operacyjny zwalnia większość zasobów i informacji związanych z ten proces, ale nadal przechowuje dane o wykorzystaniu zasobów i statusie zakończenia kodu, ponieważ proces nadrzędny może być zainteresowany wiedzą, czy ten proces potomny wykonał się pomyślnie (przy użyciu standardowych funkcji do dekodowania kodu stanu zakończenia) i ile zasobów systemowych zużył podczas wykonywania.

Domyślnie system zakłada, że ​​proces nadrzędny rzeczywiście jest zainteresowany takimi informacjami w momencie przerwania procesu przez dziecko, dlatego wysyła do rodzica sygnał SIGCHLD , aby ostrzec, że są jakieś dane o dziecku do zebrania. Takie zbieranie odbywa się poprzez wywołanie funkcji z wait (albo sam wait albo jeden z jego krewnych, taki jak waitpid , waitid lub wait4 ). Zaraz po utworzeniu tej kolekcji system udostępnia ostatnie bity informacji o procesie potomnym i usuwa jego identyfikator pid z tabeli procesów. Jeśli jednak proces nadrzędny ociąga się z gromadzeniem danych potomka (lub w ogóle tego nie robi), system nie ma innego wyjścia, jak tylko przechowywać dane pid i zakończenia potomka w tabeli procesów przez czas nieokreślony.

Taki zakończony proces, którego dane nie zostały zebrane, jest nazywany procesem zombie lub po prostu zombie w żargonie UNIX. Nazwa jest humorystyczną analogią ze względu na traktowanie zakończonego procesu jako „już nieżyjącego” lub „martwego” - ponieważ tak naprawdę przestał działać - oraz martwego procesu, który wciąż „wciela się” w procesy „świata żywych” - proces stół - który w rzeczywistości jest zatem „nieumarłym” lub „zombie”.

Procesy zombie mogą stwarzać problemy w systemach o ograniczonych zasobach lub tabelach procesów o ograniczonej wielkości, ponieważ tworzenie nowych, aktywnych procesów może być uniemożliwione przez brak zasobów wciąż używanych przez długotrwałe zombie.

Dlatego dobrą praktyką programistyczną w każdym programie, który może odradzać procesy potomne, jest posiadanie kodu zapobiegającego tworzeniu się długotrwałych zombie z ich oryginalnych potomków. Najbardziej oczywistym podejściem jest posiadanie kodu, który wywołuje wait lub jednego z jego krewnych gdzieś po utworzeniu nowego procesu. Jeśli oczekuje się, że program utworzy wiele procesów potomnych, które mogą być wykonywane asynchronicznie i kończyć się w nieprzewidywalnej kolejności, ogólnie dobrze jest utworzyć procedurę obsługi sygnału SIGCHLD , wywołując jedną z procedur oczekiwania -family w pętli, dopóki nie zostaną żadne niepobrane dane podrzędne. Proces nadrzędny może całkowicie zignorować zakończenie swoich procesów potomnych i nadal nie tworzyć zombie, ale wymaga to wyraźnego zdefiniowania procedury obsługi dla SIGCHLD poprzez wywołanie sigaction ze specjalną flagą opcji SA_NOCLDWAIT .

Procesy sieroce

Procesy osierocone to sytuacja odwrotna do procesów zombie, odnosząc się do przypadku, w którym proces nadrzędny kończy się przed procesami potomnymi, o których mówi się, że stają się „osierocone”. W przeciwieństwie do asynchronicznego powiadamiania dziecka do rodzica, które ma miejsce, gdy proces potomny zakończy działanie (za pośrednictwem SIGCHLD sygnał), procesy potomne nie są natychmiast powiadamiane o zakończeniu ich procesu nadrzędnego. Zamiast tego system po prostu redefiniuje pole „nadrzędny PID” w danych procesu potomnego, aby był to proces, który jest „przodkiem” każdego innego procesu w systemie, którego PID ma ogólnie wartość 1 (jeden) i którego nazwa jest tradycyjnie „init” (z wyjątkiem jądra Linuksa 3.4 i nowszych [więcej informacji poniżej]). Dlatego powiedziano, że init „adoptuje” każdy osierocony proces w systemie.

Dość powszechnym założeniem programistów nowych w systemie UNIX było to, że procesy potomne procesu kończącego zostaną przejęte przez bezpośredni proces nadrzędny tego procesu (stąd „dziadek” tych procesów potomnych). Takie założenie było błędne – chyba że, oczywiście, tym „dziadkiem” był sam init.

Po jądrze Linuksa 3.4 nie jest to już prawdą, w rzeczywistości procesy mogą wywołać wywołanie systemowe prctl() z opcją PR_SET_CHILD_SUBREAPER, w wyniku czego to one, a nie proces nr 1, staną się rodzicem któregokolwiek z ich osieroconych procesów potomnych. W ten sposób działają nowoczesne menedżery usług i narzędzia do nadzoru demonów, w tym systemd, upstart i menedżer usług nosh.

To jest streszczenie strony podręcznika, informujące, że:

Subreaper spełnia rolę init(1) dla swoich procesów potomnych. Kiedy proces zostaje osierocony (tj. jego bezpośredni rodzic kończy działanie), wówczas ten proces zostanie przypisany do najbliższego wciąż żyjącego przodka, podżniwiarza. Następnie wywołania getppid() w procesie osieroconym zwrócą teraz PID procesu podrzędnego, a kiedy sierota zakończy działanie, to proces podrzędny otrzyma sygnał SIGCHLD i będzie mógł czekać(2) na proces aby odkryć jego status zakończenia.