WAIT(2) | Руководство программиста Linux | WAIT(2) |
wait, waitpid, waitid - ожидает смену состояния процесса
#include <sys/types.h> #include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); /* Это интерфейс glibc и POSIX; информацию по системному вызову ядра смотрите в ЗАМЕЧАНИЯ. */
Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):
waitid():
Данные системные вызовы используются для ожидания изменения состояния процесса-потомка вызвавшего процесса и получения информации о потомке, чьё состояние изменилось. Сменой состояния считается: прекращение работы потомка, останов потомка по сигналу, продолжение работы потомка по сигналу. Ожидание прекращения работы потомка позволяет системе освободить ресурсы, использовавшиеся потомком; если ожидание не выполняется, то прекративший работу потомок остаётся в системе в состоянии "зомби (zombie)" (см. ЗАМЕЧАНИЯ далее).
Если состояние потомка уже изменилось, то вызов сразу возвращает результат. В противном случае, работа приостанавливается до тех пор, пока не произойдёт изменение состояния потомка или обработчик сигналов не прервёт вызов (предполагается, что системные вызовы не перезапускаются автоматически из-за указания флага SA_RESTART в sigaction(2)). В оставшейся части страницы потомок, чьё состояние ещё не было получено одним из этих системных вызовов, называется ожидаемым (waitable).
Системный вызов wait() приостанавливает выполнение вызвавшей нити до тех пор, пока не прекратит выполнение один из её потомков. Вызов wait(&wstatus) эквивалентен:
waitpid(-1, &wstatus, 0);
Системный вызов waitpid() приостанавливает выполнение вызвавшей нити до тех пор, пока не изменится состояние потомка, заданного аргументом pid. По умолчанию waitpid() ожидает только прекращения работы потомка, но это можно изменить через аргумент options как описано далее.
Значением pid может быть:
Значение options создаётся путем битовой операции ИЛИ над следующими константами:
(Аргументы, имеющиеся только в Linux, см. далее.)
Если wstatus не равен NULL, то wait() и waitpid() сохраняют информацию о состоянии в переменной типа int, на которую указывает wstatus. Это целое число можно исследовать с помощью следующих макросов (они принимают в качестве аргумента само целое, а не указатель на него как wait() и waitpid()!):
Системный вызов waitid() (доступен, начиная с Linux 2.6.9) предоставляет более полный контроль над тем, какого изменения состояния нужно ждать у потомка.
Аргументы idtype и id определяют какого(их) потомков ждать:
Ожидаемые изменения состояния потомков задаются следующими флагами в options (объединяются через OR):
Дополнительно с помощью OR в options могут задаваться следующие флаги:
При успешном возврате, waitid() заполняет следующие поля в структуре siginfo_t, указываемой из infop:
Если в options указан флаг WNOHANG и нет потомков в состоянии ожидания, то waitid() сразу возвращает 0, а состояние структуры siginfo_t, на которую указывает infop, зависит от реализации. Чтобы (точно) отличать этот случай от того, что потомок был в ожидаемом состоянии, обнулите поле si_pid перед вызовом и проверьте ненулевое значение в этом поле после отработки вызова.
В POSIX.1-2008 Technical Corrigendum 1 (2013) добавлено требование, что при указании WNOHANG в options и нет потомков в состоянии ожидания, то вызов waitid() должен возвращать в структуре обнулённые поля si_pid и si_signo. В Linux и других реализациях придерживаются этого требования, поэтому не нужно обнулять поле si_pid перед вызовом waitid(). Однако в этом не все реализации следуют POSIX.1.
В случае успешного выполнения wait() возвращает ID процесса завершившегося потомка; при ошибке возвращается -1.
В случае успешного выполнения waitpid() возвращает ID процесса потомка, чьё состояние изменилось; если задан флаг WNOHANG и существует один или более потомков, заданных в pid, без изменённого состояния, то возвращается 0. При ошибке возвращается -1.
Вызов waitid() возвращает 0 в случае успешного выполнения или если задан флаг WNOHANG и пока не существует потомка(ов), указанного в pid,с изменённым состоянием. При ошибке возвращается -1.
Каждый из этих вызовов записывает в errno соответствующую причину ошибки.
SVr4, 4.3BSD, POSIX.1-2001.
Потомок, который завершился, но которого не ждали, становится «зомби» (zombie). Ядро поддерживает минимальный набор информации о процессах зомби (PID, состояние завершения, использованные ресурсы), чтобы позже позволить родителю выполнить процесс ожидания для получения информации о потомке. До тех пор, пока зомби не будет удалён из системы через процесс ожидания (wait), он занимает пространство (slot) в таблице процессов ядра, и если таблица заполнится, станет невозможно создавать новые процессы. Если родительский процесс завершает работу, то его потомки «зомби» (если есть) усыновляются процессом init(1) (или ближайшим «сборщиком», определённым посредством вызова prctl(2)с операцией PR_SET_CHILD_SUBREAPER); init(1) автоматически выполняет процедуру ожидания для удаления зомби.
В POSIX.1-2001 указано, что если для SIGCHLD указан флаг SIG_IGN или SA_NOCLDWAIT (смотрите sigaction(2)), то завершающиеся потомки не становятся зомби, а вызов wait() или waitpid() заблокирует выполнение до тех пор, пока все потомки не завершат работу, и затем завершится с ошибкой errno, равной ECHILD (в оригинальном стандарте POSIX такое значение настройки SIGCHLD в SIG_IGN не определено. Заметим, что хотя поведение SIGCHLD по умолчанию является «игнорирование», явная установка в SIG_IGN приводит другому обращению с потомками зомби).
Linux 2.6 соответствует данной спецификации. Однако, Linux 2.4 (и ранее) не соответствует: если вызов wait() или waitpid() сделан при игнорировании SIGCHLD, вызов работает как если бы SIGCHLD не игнорировался, то есть, вызов блокирует работу до тех пор, пока следующий потомок не завершит работу и затем возвращает ID процесса и состояние этого потомка.
В ядре Linux нити, управляемые ядром, устройством не отличаются от процесса. Нить — это просто процесс, который создан уникальным (существующим только в Linux) системным вызовом clone(2); другие процедуры, такие как переносимая версия pthread_create(3), также реализованы с помощью clone(2). До Linux 2.4, нить представляла собой специализированный вариант процесса, и, как следствие, нить не могла ждать потомков другой нити, даже когда последняя принадлежала той же группе нитей. Однако, в POSIX вписали такую функциональность, и, начиная с Linux 2.4, нить может, и по умолчанию будет ждать потомков других нитей в той же группе нитей.
Следующие значения options, присущие только Linux, используются для потомков, созданных с помощью clone(2); начиная с Linux 4.7, они также могут использоваться с waitid():
Начиная с Linux 4.7, в случае, когда потомок был вызван с помощью ptrace, флаг __WALL назначается автоматически.
В действительности, wait() — библиотечная функция, которая (в glibc) реализована через вызов wait4(2).
Для некоторых архитектур нет системного вызова waitpid(); его заменяет интерфейс, реализованный через обёрточную функцию библиотеки C, которая вызывает wait4(2).
Системный вызов ядра waitid() имеет пятый аргумент с типом struct rusage *. Если его значение не равно NULL, то он используется для возврата информации по используемым ресурсам в потомке, в том же виде что и wait4(2). Подробности смотрите в getrusage(2).
Согласно POSIX.1-2008, приложение, вызывающее waitid(), должно убедиться, что infop указывает на структуру siginfo_t (т. е., что это указатель не равен null). В Linux, если infop равно NULL, то waitid() выполняется успешно и возвращает ID процесса ожидавшегося потомка. Приложения не должны полагаться на это несогласованное, нестандартное и ненужное свойство.
В следующей программе показано использование fork(2) и waitpid(). Программа создаёт процесс потомок. Если программа запущена без параметров, то потомок приостанавливает выполнение с помощью pause(2), чтобы позволить пользователю послать сигнал потомку. Иначе, если в командной строке задан параметр, то потомок завершает работу сразу, используя переданное в параметре командной строки целое число как код завершения. Процесс родитель работает в цикле, следя за потомком с помощью waitpid(), и использует макросы W*(), описанные ранее, для анализа значения состояния ожидания.
Следующий сеанс работы в оболочке показывает работу с программой:
$ ./a.out & Child PID is 32360 [1] 32359 $ kill -STOP 32360 stopped by signal 19 $ kill -CONT 32360 continued $ kill -TERM 32360 killed by signal 15 [1]+ Done ./a.out $
#include <sys/wait.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char *argv[]) { pid_t cpid, w; int wstatus; cpid = fork(); if (cpid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (cpid == 0) { /* Код, выполняемый потомком */ printf("Child PID is %ld\n", (long) getpid()); if (argc == 1) pause(); /* Ожидание сигналов */ _exit(atoi(argv[1])); } else { /* Код, выполняемый родителем */ do { w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED); if (w == -1) { perror("waitpid"); exit(EXIT_FAILURE); } if (WIFEXITED(wstatus)) { printf("exited, status=%d\n", WEXITSTATUS(wstatus)); } else if (WIFSIGNALED(wstatus)) { printf("killed by signal %d\n", WTERMSIG(wstatus)); } else if (WIFSTOPPED(wstatus)) { printf("stopped by signal %d\n", WSTOPSIG(wstatus)); } else if (WIFCONTINUED(wstatus)) { printf("continued\n"); } } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); exit(EXIT_SUCCESS); } }
_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)
2018-04-30 | Linux |