wait(2)

NAZWA
wait, waitpid, waitid - oczekuje na zmianę stanu procesu

BIBLIOTEKA
Standardowa biblioteka C (libc, -lc)

SKŁADNIA
#include <sys/wait.h>

pid_t wait(int *_Nullable wstatus);
pid_t waitpid(pid_t pid, int *_Nullable wstatus, int options);

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
                /* Jest to interfejs glibc i POSIX; zob. WERSJE, aby
                   uzyskać informacje o surowym wywołaniu systemowym.  */

    Wymagane ustawienia makr biblioteki glibc (patrz feature_test_macros(7)):

waitid():
    Od glibc 2.26:
        _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
    glibc 2.25 i wcześniejsze:
        _XOPEN_SOURCE
            || /* Od glibc 2.12: */ _POSIX_C_SOURCE >= 200809L
            || /* glibc <= 2.19: */ _BSD_SOURCE

OPIS
Wszystkie te wywołania systemowe służą do oczekiwania na zmianę statusu potomka procesu wywołującego oraz do uzyskania informacji o potomku, którego stan uległ zmianie. Za zmianę stanu uważa się: zakończenie potomka, zatrzymanie potomka przez sygnał, wznowienie potomka przez sygnał. W przypadku zakończonego potomka, wykonanie oczekiwania pozwala systemowi na zwolnienie zasobów związanych z potomkiem; gdyby oczekiwanie nie zostało wykonane, to potomek pozostałby w stanie „zombie” (zob. UWAGI poniżej).

Jeśli stan potomka już uległ zmianie, to te wywołania niezwłocznie powrócą. W przeciwnym przypadku zablokują, do momentu zmiany stanu potomka lub przerwania wywołania przez procedurę obsługi sygnału (zakładając, że wywołania systemowe nie są automatycznie uruchamiane ponownie ze względu na znacznik SA_RESTART sigaction(2)). W pozostałej części niniejszego podręcznika, potomek, którego stan uległ zmianie, ale na którego jeszcze nie poczekano za pomocą opisywanych wywołań systemowych, będzie określany terminem oczekiwalnego.

  wait() i waitpid()
Wywołanie systemowe wait() wstrzymuje wykonanie wątku wywołującego, dopóki nie zakończy się jeden z jego potomków. Wywołanie wait(&wstatus) jest równoważne:

    waitpid(-1, &wstatus, 0);

Wywołanie systemowe waitpid() wstrzymuje wykonanie wątku wywołującego, dopóki potomek określony argumentem pid nie zmieni stanu. Domyślnie, waitpid() czeka jedynie na zatrzymanie potomka, lecz to zachowanie można zmienić argumentem options, jak opisano niżej.

Wartość pid może wynosić:

< -1
    oznacza oczekiwanie na dowolnego potomka, którego identyfikator grupy procesów jest równy modułowi wartości (wartości bezwzględnej) pid.

-1  oznacza oczekiwanie na dowolnego potomka.

0   oznacza oczekiwanie na dowolnego potomka, którego identyfikator grupy procesów jest równy identyfikatorowi procesu wołającego, w momencie wywołania waitpid().

> 0 oznacza oczekiwanie na potomka, którego PID jest równy wartości pid.

Wartość options jest sumą (OR) zera lub więcej spośród następujących stałych:

WNOHANG
    oznacza natychmiastowy powrót z funkcji, jeśli potomek nie zakończył pracy.

WUNTRACED
    powraca również, jeśli potomek został zatrzymany (lecz nie śledzony za pomocą ptrace(2)). Status potomków śledzonych, które są zatrzymane jest zapewniany nawet gdy nie podano tej opcji.

WCONTINUED (od Linuksa 2.6.10)
    powraca również, jeśli zatrzymany potomek został wznowiony sygnałem SIGCONT.

(Poniższe opcje dotyczą wyłącznie Linuksa.)

Jeśli wstatus nie wynosi NULL, to wait() i waitpid() przechowują informacje o statusie w int, na którą on wskazuje. Liczbę tę można sprawdzić następującymi makrami (które jako argument przyjmują nie wskaźnik do niej, jak czynią to wait() i waitpid(), lecz samą liczbę!):

WIFEXITED(wstatus)
    zwraca prawdę, jeśli potomek zakończył się w sposób normalny tj. wywołując exit(3) lub _exit(2) albo powracając z main().

WEXITSTATUS(wstatus)
    zwraca status zakończenia potomka. Składa się on z ośmiu najmniej znaczących bitów argumentu status, które potomek podał w wywołaniu do exit(3) lub _exit(2) albo argumentu powrotnego z main(). Makro to może być przetwarzane tylko jeśli WIFEXITED zwróciło prawdę.

WIFSIGNALED(wstatus)
    zwraca prawdę, jeśli potomek został zakończony sygnałem.

WTERMSIG(wstatus)
    zwraca numer sygnału, który spowodował zakończenie procesu potomnego. Makro to powinno być używane tylko po zwróceniu prawdy przez WIFSIGNALED.

WCOREDUMP(wstatus)  // POSIX.1-2024
    zwraca prawdę, jeśli potomek dokonał zrzutu pamięci (zob. core(5)). Makra tego należy używać tylko wówczas, gdy WIFSIGNALED zwróciło prawdę.

WIFSTOPPED(wstatus)  // POSIX.1-2024
    zwraca prawdę, jeśli proces potomny został zatrzymany po dostarczeniu sygnału; jest to możliwe tylko jeśli wywołanie wykonano z użyciem WUNTRACED lub gdy potomek jest śledzony (zob. ptrace(2)).

WSTOPSIG(wstatus)
    zwraca numer sygnału, który spowodował zatrzymanie potomka. Makro to może być użyte tylko, gdy WIFSTOPPED zwróciło prawdę.

WIFCONTINUED(wstatus)
    (od Linuksa 2.6.10) zwraca prawdę, jeśli proces potomny został wznowiony, ze względu na otrzymanie SIGCONT.

  waitid()
Wywołanie systemowe waitid() (dostępne od Linuksa 2.6.9) udostępnia precyzyjniejszą kontrolę nad stanem potomka, na którego się czeka.

Argumenty idtype i id wybierają potomka (potomków), na którego się oczekuje, jak poniżej:

idtype == P_PID
    Oczekuje na potomka, którego PID jest równy id.

idtype == P_PIDFD (od Linuksa 5.4)
    Oczekuje na potomka, do którego odnosi się deskryptor pliku PID podany w id (więcej informacji o deskryptorach pliku PID w podręczniku pidfd_open(2)).

idtype == P_PGID
    Oczekuje na dowolnego potomka, którego identyfikator grupy jest równy id. Od Linuksa 5.4, jeśli id wynosi zero, to oczekuje na dowolnego potomka z tej samej grupy procesów, co grupa procesu wywołującego w momencie wywołania.

idtype == P_ALL
    Oczekuje na dowolnego potomka; id jest ignorowane.

Zmianę statusu potomka, na którego się oczekuje, podaje się za pomocą sumy (OR) jednego lub kilku poniższych znaczników w options:

WEXITED
    Oczekuje na potomka, który został zakończony.

WSTOPPED
    Oczekuje na potomka, który został zakończony za pomocą dostarczenia sygnału.

WCONTINUED
    Oczekuje na (poprzednio zatrzymanego) potomka, który został wznowiony sygnałem SIGCONT.

Ponadto, w options można zsumować poniższe znaczniki:

WNOHANG
    Tak jak w waitpid().

WNOWAIT
    Pozostawia potomka w stanie oczekiwalnym; późniejszym wywołaniem oczekiwania można następnie pobrać informacje o stanie potomka.

Po pomyślnym powrocie, waitid() uzupełnia następujące pola struktury siginfo_t, na którą wskazuje infop:

si_pid
    Identyfikator procesu potomnego.

si_uid
    Rzeczywisty identyfikator użytkownika potomka (pole to nie jest ustawione w większości innych implementacji).

si_signo
    Zawsze ustawione na SIGCHLD.

si_status
    Status zakończenia potomka, taki jak podany do _exit(2) (lub exit(3)) lub sygnał powodujący zakończenie, zatrzymanie lub kontynuowanie potomka. Pole si_code może posłużyć do określenia sposobu interpretacji tego pola.

si_code
    Ustawione na jeden z: CLD_EXITED (potomek wywołał _exit(2)); CLD_KILLED (potomek zabity sygnałem); CLD_DUMPED (potomek zabity sygnałem, zrzucił pamięć); CLD_STOPPED (potomek zatrzymany sygnałem); CLD_TRAPPED (śledzony potomek wyzwolił pułapkę) lub CLD_CONTINUED (potomek kontynuował po otrzymaniu SIGCONT).

Jeśli w options podano WNOHANG i nie było potomków w stanie oczekiwalnym, to waitid() zwróci bezzwłocznie 0, natomiast stan struktury siginfo_t, na którą wskazuje infop zależy od implementacji. Aby (w sposób przenośny) rozróżnić ten przypadek od sytuacji, gdy potomek był w stanie oczekiwalnym, należy wyzerować pole si_pid przed wywołaniem i sprawdzić, czy wartość pola jest niezerowa, po powrocie wywołania.

POSIX.1-2008 Technical Corrigendum 1 (2013) dodaje wymaganie, że gdy w options podano WNOHANG i nie było potomków w stanie oczekiwalnym, to waitid() powinno wyzerować pola si_pid i si_signo struktury. W Linuksie i innych implementacjach, które przestrzegają tego wymagania, nie ma potrzeby zerowania pola si_pid, przed wywołaniem waitid(). Obecnie, nie wszystkie implementacje przestrzegają jednak tego wymagania POSIX.1.

WARTOŚĆ ZWRACANA
wait(): po pomyślnym zakończeniu zwraca identyfikator procesu zakończonego potomka, w razie błędu zwraca -1.

waitpid(): po pomyślnym zakończeniu, zwraca identyfikator procesu, którego stan uległ zmianie; jeśli podano WNOHANG i jeden lub więcej z potomków podanych w pid istnieje, lecz nie zmienił jeszcze stanu, zwracane jest 0. W przypadku niepowodzenia, zwracane jest -1.

waitid(): zwraca 0 po pomyślnym zakończeniu lub gdy podano WNOHANG i żaden z potomków podanych w id nie zmienił jeszcze stanu; w przypadku błędu zwracane jest -1.

W przypadku niepowodzenia, wszystkie wywołania ustawiają errno, wskazując błąd.

BŁĘDY

EAGAIN
    Deskryptor pliku PID podany w id jest nieblokujący, a proces do którego się odnosi nie zakończył się.

ECHILD
    (dla wait()) Proces wywołujący nie ma żadnych potomków, na których jeszcze się nie czeka.

ECHILD
    (dla waitpid() lub waitid()) Proces o zadanym pid (waitpid()) lub idtype i id (waitid()) nie istnieje lub nie jest potomkiem procesu wywołującego (może się to zdarzyć również w przypadku potomka, który ustawił akcję obsługi sygnału SIGCHLD na SIG_IGN; zob. także: wątki w rozdziale Uwagi linuksowe).

EINTR
    Nie ustawiono WNOHANG, a został przechwycony niezablokowany sygnał lub SIGCHLD; zob. signal(7).

EINVAL
    Argument options był niepoprawny.

ESRCH
    (dla wait() lub waitpid()) pid jest równe INT_MIN.

WERSJE

  Różnice biblioteki C/jądra
wait() jest w rzeczywistością funkcją biblioteczną, która (w glibc) jest zaimplementowana jako wywołanie do wait4(2).

Na niektórych architekturach, nie ma wywołania systemowego waitpid(); zamiast tego interfejs ten jest zaimplementowany jako opakowująca funkcja biblioteki C, która wywołuje wait4(2).

Surowe wywołanie systemowe waitid() przyjmuje piąty argument, typu struct rusage *. Jeśli argument ten nie wynosi NULL, to jest używany do zwrócenia informacji o użyciu zasobów potomka, w ten sam sposób jak wait4(2). Więcej szczegółów w podręczniku getrusage(2).

STANDARDY
POSIX.1-2024.

HISTORIA
SVr4, 4.3BSD, POSIX.1-2001.

UWAGI
Potomek który się zakończy, ale na którego nie oczekiwano staje się „zombie”. Jądro przechowuje minimalny zbiór informacji o procesach zombie (PID, status zakończenia, informacje o użyciu zasobów), aby pozwolić później procesowi macierzystemu na wykonanie oczekiwania, aby pozyskać informacje o potomku. Dopóki zombie nie zostanie usunięty z systemu poprzez oczekiwanie, będzie zajmował miejsce w tablicy procesów jądra, a jeśli ta tablica się wypełni, nie będzie można tworzyć nowych procesów. Jeśli proces macierzysty zakończy się, to ewentualni potomkowie „zombie”, zostaną zaadoptowani przez init(1), (lub przez najbliższy proces dorzynający (ang. subreaper), według definicji użycia operacji PR_SET_CHILD_SUBREAPER prctl(2)); init(1) automatycznie wykona odczekanie, w celu usunięcia zombie.

POSIX.1-2001 określa, że jeśli jako dyspozycję sygnału SIGCHLD ustawiono na SIG_IGN lub dla SIGCHLD ustawiono znacznik SA_NOCLDWAIT (zob. sigaction(2)), to kończony potomek nie staje się zombie, a wywołanie wait() lub waitpid() zablokuje, dopóki wszyscy potomkowie nie zakończą się, a następnie zawiedzie z errno ustawionym na ECHILD. (Pierwotny standard POSIX pozostawiał zachowanie ustawienia SIGCHLD na SIG_IGN nieokreślonym. Proszę zauważyć, że choć domyślną dyspozycją SIGCHLD jest „ignorowanie”, to wyraźne ustawienie dyspozycji na SIG_IGN daje inne zachowanie w stosunku do potomków procesu zombie).

Linux 2.6 jest zgodny z wymaganiami POSIX. Jednak Linux 2.4 (i wcześniejsze) nie są: jeśli wywołanie wait() lub waitpid() jest wykonywane z ignorowaniem SIGCHLD, zachowuje się ono tak, jakby SIGCHLD nie były ignorowane, to znaczy, wywołanie zostaje zablokowane do chwili zakończenia następnego potomka, a następnie zwraca identyfikator procesu i status tego potomka.

  Uwagi linuksowe
Pod Linuksem, wątek zarządzany przez jądro nie jest uruchamiany inaczej niż zwykły proces. Zamiast tego wątek jest po prostu procesem stworzonym przez wywołanie dostępnej tylko pod Linuksem funkcji systemowej clone(2). Inne funkcje, jak na przykład przenośne pthread_create(3) są zaimplementowane przez wywołania funkcji clone(2). W wersjach Linuksa poprzedzających 2.4, wątek był po prostu specyficznym przypadkiem procesu. W związku z tym nie mógł on czekać na potomków innego wątku nawet w przypadku, gdy ten drugi wątek należał do tej samej grupy wątków. Jednakże, POSIX zaleca taką funkcjonalność, więc począwszy od Linuksa 2.4 wątek może (i domyślnie będzie) czekać na potomków innych wątków należących do tej samej grupy wątków.

Następujące, specyficzne dla Linuksa opcje w options są przeznaczone dla potomków utworzonych za pomocą clone(2); mogą być one również, od Linuksa 4.7, używane z waitid():

__WCLONE
    Oczekuje tylko na potomków typu „clone”. Jeśli opcja ta zostanie pominięta oczekiwanie będzie występować tylko na potomków typu „nie-clone”. (Potomek typu „clone” to taki, który po zakończeniu nie dostarcza swojemu procesowi macierzystemu sygnału lub dostarcza sygnał inny niż SIGCHLD.) Opcja ta jest ignorowana, jeśli ustawiona jest również opcja __WALL.

__WALL (od Linuksa 2.4)
    Oczekuje na procesy potomne niezależnie od ich typu („clone” lub „non-clone”).

__WNOTHREAD (od Linuksa 2.4)
    Nie oczekuje na procesy potomne innych wątków w obrębie tej samej grupy wątków. Było to w Linuksie domyślne przed wersją 2.4.

Od Linuksa 4.7, znacznik __WALL jest automatycznie dorozumiany, gdy potomek jest śledzony (ptraced).

USTERKI
Zgodnie z POSIX.1-2008, aplikacje wywołujące waitid() muszą upewnić się, że infop wskazuje na strukturę siginfo_t (tj. jest nie to pusty wskaźnik). W Linuksie, jeśli infop wynosi NULL, to waitid() kończy się powodzeniem, zwracając identyfikator procesu oczekiwanego potomka. Aplikacje powinny unikać polegania na tym niespójnym, niestandardowym i niepotrzebnym zachowaniu.

PRZYKŁADY
Poniższy program demonstruje użycie fork(2) i waitpid(). Program tworzy proces potomny. Jeśli nie poda się argumentów wiersza poleceń, potomek zawiesza swoje wykonanie za pomocą pause(2), aby pozwolić użytkownikowi wysyłać sygnały do potomka. W przeciwnym przypadku, gdy poda się argumenty wiersza poleceń, potomek bezzwłocznie wychodzi, używając liczby podanej w wierszu polecenia jako statusu zakończenia. Proces macierzysty wykonuje pętlę monitorującą potomka za pomocą waitpid() i używa makr W*() opisanych powyżej, do analizy wartości statusu oczekiwania.

Poniższa sesja powłoki demonstruje użycie programu:

    $ ./a.out &
    PID potomka to 32360
    [1] 32359
    $ kill -STOP 32360
    zatrzymany sygnałem 19
    $ kill -CONT 32360
    wznowiony
    $ kill -TERM 32360
    zabity sygnałem 15
    [1]+  Zakończony                    ./a.out
    $

  Kod źródłowy programu
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>

    int
    main(int argc, char *argv[])
    {
        int    wstatus;
        pid_t  cpid, w;

        cpid = fork();
        if (cpid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        }

        if (cpid == 0) {            /* Kod wykonywany przez potomka */
            printf("PID potomka to %jd\n", (intmax_t) getpid());
            if (argc == 1)
                pause();                    /* Oczekiwanie na sygnały */
            _exit(atoi(argv[1]));

        } else {                    /* Kod wykonywany przez rodzica */
            do {
                w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
                if (w == -1) {
                    perror("waitpid");
                    exit(EXIT_FAILURE);
                }

                if (WIFEXITED(wstatus)) {
                    printf("wyszedł, status=%d\n", WEXITSTATUS(wstatus));
                } else if (WIFSIGNALED(wstatus)) {
                    printf("zabity sygnałem %d\n", WTERMSIG(wstatus));
                } else if (WIFSTOPPED(wstatus)) {
                    printf("zatrzymany sygnałem %d\n", WSTOPSIG(wstatus));
                } else if (WIFCONTINUED(wstatus)) {
                    printf("wznowiony\n");
                }
            } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
            exit(EXIT_SUCCESS);
        }
    }

ZOBACZ TAKŻE
_exit(2), clone(2), fork(2), kill(2), ptrace(2), sigaction(2), signal(2), wait4(2), pthread_create(3), core(5), credentials(7), signal(7)

TŁUMACZENIE
Tłumaczenie niniejszej strony podręcznika: Przemek Borys <pborys@dione.ids.pl>, Andrzej Krzysztofowicz <ankry@green.mf.pg.gda.pl> i Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 (https://www.gnu.org/licenses/gpl-3.0.html) lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.