7.4 ОЖИДАНИЕ ЗАВЕРШЕНИЯ ВЫПОЛНЕНИЯ ПРОЦЕССА

7.4 ОЖИДАНИЕ ЗАВЕРШЕНИЯ ВЫПОЛНЕНИЯ ПРОЦЕССА

Процесс может синхронизировать продолжение своего выполнения с моментом завершения потомка, если воспользуется системной функцией wait. Синтаксис вызова функции:

pid = wait(stat_addr);

где pid — значение кода идентификации (PID) прекратившего свое существование потомка, stat_addr — адрес переменной целого типа, в которую будет помещено возвращаемое функцией exit значение, в пространстве задачи.

main() {

 int child;

 if ((child = fork()) == 0)  {

  printf("PID потомка %d ", getpid());

  pause(); /* приостанов выполнения до получения сигнала */

 }

 /* родитель */

 printf("PID потомка %d ", child);

 exit(child);

}

Рисунок 7.15. Пример использования функции exit

Алгоритм функции wait приведен на Рисунке 7.16. Ядро ведет поиск потомков процесса, прекративших существование, и в случае их отсутствия возвращает ошибку. Если потомок, прекративший существование, обнаружен, ядро передает его код идентификации и значение, возвращаемое через параметр функции exit, процессу, вызвавшему функцию wait. Таким образом, через параметр функции exit (status) завершающийся процесс может передавать различные значения, в закодированном виде содержащие информацию о причине завершения процесса, однако на практике этот параметр используется по назначению довольно редко. Ядро передает в соответствующие поля, принадлежащие пространству родительского процесса, накопленные значения продолжительности исполнения процесса-потомка в режиме ядра и в режиме задачи и, наконец, освобождает в таблице процессов место, которое в ней занимал прежде прекративший существование процесс. Это место будет предоставлено новому процессу.

Если процесс, выполняющий функцию wait, имеет потомков, продолжающих существование, он приостанавливается до получения ожидаемого сигнала. Ядро не возобновляет по своей инициативе процесс, приостановившийся с помощью функции wait: такой процесс может возобновиться только в случае получения сигнала. На все сигналы, кроме сигнала «гибель потомка», процесс реагирует ранее рассмотренным образом. Реакция процесса на сигнал «гибель потомка» проявляется по-разному в зависимости от обстоятельств:

• По умолчанию (то есть если специально не оговорены никакие другие действия) процесс выходит из состояния останова, в которое он вошел с помощью функции wait, и запускает алгоритм issig для опознания типа поступившего сигнала. Алгоритм issig (Рисунок 7.7) рассматривает особый случай поступления сигнала типа «гибель потомка» и возвращает «ложь». Поэтому ядро не выполняет longjump из функции sleep, а возвращает управление функции wait. Оно перезапускает функцию wait, находит потомков, прекративших существование (по крайней мере, одного), освобождает место в таблице процессов, занимаемое этими потомками, и выходит из функции wait, возвращая управление процессу, вызвавшему ее.

• Если процессы принимает сигналы данного типа, ядро делает все необходимые установки для запуска пользовательской функции обработки сигнала, как и в случае поступления сигнала любого другого типа.

• Если процесс игнорирует сигналы данного типа, ядро перезапускает функцию wait, освобождает в таблице процессов место, занимаемое потомками, прекратившими существование, и исследует оставшихся потомков.

алгоритм wait

входная информация: адрес переменной для хранения значения status, возвращаемого завершающимся процессом

выходная информация: идентификатор потомка и код возврата функции exit

{

 if (процесс, вызвавший функцию wait, не имеет потомков) return (ошибку);

 for (;;) {  /* цикл с внутренним циклом */

  if (процесс, вызвавший функцию wait, имеет потомков, прекративших существование) {

   выбрать произвольного потомка;

   передать его родителю информацию об использовании потомком ресурсов центрального процессора;

   освободить в таблице процессов место, занимаемое потомком;

   return (идентификатор потомка, код возврата функции exit, вызванной потомком);

  }

  if (у процесса нет потомков) return ошибку;

  приостановиться с приоритетом, допускающим прерывания (до завершения потомка);

 }

}

Рисунок 7.16. Алгоритм функции wait

Например, если пользователь запускает программу, приведенную на Рисунке 7.17, с параметром и без параметра, он получит разные результаты. Сначала рассмотрим случай, когда пользователь запускает программу без параметра (единственный параметр — имя программы, то есть argc равно 1). Родительский процесс порождает 15 потомков, которые в конечном итоге завершают свое выполнение с кодом возврата i, номером процесса в порядке очередности создания. Ядро, исполняя функцию wait для родителя, находит потомка, прекратившего существование, и передает родителю его идентификатор и код возврата функции exit. При этом заранее не известно, какой из потомков будет обнаружен. Из текста программы, реализующей системную функцию exit, написанной на языке Си и включенной в библиотеку стандартных подпрограмм, видно, что программа запоминает код возврата функции exit в битах 8-15 поля ret_code и возвращает функции wait идентификатор процесса-потомка. Таким образом, в ret_code хранится значение, равное 256*i, где i — номер потомка, а в ret_val заносится значение идентификатора потомка.

Если пользователь запускает программу с параметром (то есть argc › 1), родительский процесс с помощью функции signal делает распоряжение игнорировать сигналы типа «гибель потомка». Предположим, что родительский процесс, выполняя функцию wait, приостановился еще до того, как его потомок произвел обращение к функции exit: когда процесс-потомок переходит к выполнению функции exit, он посылает своему родителю сигнал «гибель потомка»; родительский процесс возобновляется, поскольку он был приостановлен с приоритетом, допускающим прерывания. Когда так или иначе родительский процесс продолжит свое выполнение, он обнаружит, что сигнал сообщал о «гибели» потомка; однако, поскольку он игнорирует сигналы этого типа и не обрабатывает их, ядро удаляет из таблицы процессов запись, соответствующую прекратившему существование потомку, и продолжает выполнение функции wait так, словно сигнала и не было. Ядро выполняет эти действия всякий раз, когда родительский процесс получает сигнал типа «гибель потомка», до тех пор, пока цикл выполнения функции wait не будет завершен и пока не будет установлено, что у процесса больше потомков нет. Тогда функция wait возвращает значение, равное -1. Разница между двумя способами запуска программы состоит в том, что в первом случае процесс-родитель ждет завершения любого из потомков, в то время как во втором случае он ждет, пока завершатся все его потомки.

#include ‹signal.h›

main(argc, argv)

int argc;

char *argv[];

{

 int i, ret_val, ret_code;

 if (argc ›= 1) signal(SIGCLD, SIG_IGN); /* игнорировать гибель потомков */

 for (i = 0; i ‹ 15; i++) if (fork() == 0) {

   /* процесс-потомок */

   printf("процесс-потомок %x ", getpid());

   exit(i);

  }

  ret_val = wait(&ret_code);

  printf("wait ret_val %x ret_code %x ", ret_val, ret_code);

}

Рисунок 7.17. Пример использования функции wait и игнорирования сигнала «гибель потомка»

В ранних версиях системы UNIX функции exit и wait не использовали и не рассматривали сигнал типа «гибель потомка». Вместо посылки сигнала функция exit возобновляла выполнение родительского процесса. Если родительский процесс при выполнении функции wait приостановился, он возобновляется, находит потомка, прекратившего существование, и возвращает управление. В противном случае возобновления не происходит; процесс-родитель обнаружит «погибшего» потомка при следующем обращении к функции wait. Точно так же и процесс начальной загрузки (init) может приостановиться, используя функцию wait, и завершающиеся по exit процессы будут возобновлять его, если он имеет усыновленных потомков, прекращающих существование.

В такой реализации функций exit и wait имеется одна нерешенная проблема, связанная с тем, что процессы, прекратившие существование, нельзя убирать из системы до тех пор, пока их родитель не исполнит функцию wait. Если процесс создал множество потомков, но так и не исполнил функцию wait, может произойти переполнение таблицы процессов из-за наличия потомков, прекративших существование с помощью функции exit. В качестве примера рассмотрим текст программы планировщика процессов, приведенный на Рисунке 7.18. Процесс производит считывание данных из файла стандартного ввода до тех пор, пока не будет обнаружен конец файла, создавая при каждом исполнении функции read нового потомка. Однако, процесс-родитель не дожидается завершения каждого потомка, поскольку он стремится запускать процессы на выполнение как можно быстрее, тем более, что может пройти довольно много времени, прежде чем процесс-потомок завершит свое выполнение. Если, обратившись к функции signal, процесс распорядился игнорировать сигналы типа «гибель потомка», ядро будет очищать записи, соответствующие прекратившим существование процессам, автоматически. Иначе в конечном итоге из-за таких процессов может произойти переполнение таблицы.

#include ‹signal.h›

main(argc, argv) {

 char buf[256];

 if (argc != 1) signal(SIGCLD, SIG_IGN); /* игнорировать гибель потомков */

 while (read(0, buf, 256))

  if (fork() == 0) {

   /* здесь процесс-потомок обычно выполняет какие-то операции над буфером (buf) */

   exit(0);

 }

}

Рисунок 7.18. Пример указания причины появления сигнала «гибель потомков»

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

7.4. Блокировка и ожидание

Из книги UNIX: взаимодействие процессов автора Стивенс Уильям Ричард

7.4. Блокировка и ожидание Продемонстрируем теперь, что взаимные исключения предназначены для блокирования, но не для ожидания. Изменим наш пример из предыдущего раздела таким образом, чтобы потребитель запускался сразу же после запуска всех производителей. Это даст


Завершение и прекращение выполнения процесса

Из книги Системное программирование в среде Windows автора Харт Джонсон М

Завершение и прекращение выполнения процесса После того как процесс завершил свою работу, он, или, точнее, выполняющийся в этом процессе поток, может вызвать функцию ExitProcess, указав в качестве параметра кодом завершения (exit code): VOID ExitProcess(UINT uExitCode)  Эта функция не


Ожидание завершения процесса

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

Ожидание завершения процесса Простейшим, но наряду с этим и обладающим наиболее ограниченными возможностями, методом синхронизации с другим процессом является ожидание его завершения. Представленные ниже стандартные функции ожидания Windows обладают рядом интересных


Ожидание завершения потока

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

Ожидание завершения потока Поток может дожидаться завершения выполнения другого потока точно так же, как потоки могут дожидаться завершения процесса, что обсуждалось в главе 6. В этом случае при вызове функций ожидания (WaitForSingleObject и WaitForMultipleObjects) вместо дескрипторов


Ожидание порта завершения ввода/вывода

Из книги Linux программирование в примерах автора Роббинс Арнольд

Ожидание порта завершения ввода/вывода Для выполнения ввода/вывода с участием дескрипторов, связанных с портом, используются функции ReadFile и WriteFile со структурами OVERLAPPED (дескрипторы событий не требуются). Далее операция ввода/вывода помещается в очередь порта


12.2.6. Ожидание сигналов

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

12.2.6. Ожидание сигналов Когда программа построена преимущественно вокруг сигналов, часто необходимо, чтобы она ожидала появления какого-то сигнала, прежде чем продолжать работу. Системный вызов pause() предоставляет простую возможность для этого.#include <unistd.h>int


17.5.7. Ожидание TCP-соединений

Из книги Операционная система UNIX автора Робачевский Андрей М.

17.5.7. Ожидание TCP-соединений Ожидание соединений TCP происходит почти идентично ожиданию соединений домена Unix. Единственные различия заключаются в семействах протоколов и адресов. Ниже показан вариант примера сервера домена Unix, который работает через сокеты TCP. 1: /* tserver.с


Ожидание условия

Из книги автора

Ожидание условия Простое ожиданиеint pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);Вызов функции блокирует вызвавший поток на условной переменной cond и разблокирует мьютекс mutex. Поток блокируется до тех пор, пока другой поток не вызовет функцию разблокирования на условной


Ожидание завершения потока

Из книги автора

Ожидание завершения потока Ожидание родительским потоком завершения одного или нескольких порожденных им «присоединенных» потоков (на вызове pthread_join()) — это простейший и эффективный вариант синхронизации потоков, не требующий для своей реализации каких-либо


Ожидание на барьере

Из книги автора

Ожидание на барьере Функция ожидания (синхронизации) на барьере:int pthread_barrier_wait(pthread_barrier_t* barrier);Вызов этой функции приводит к блокированию потока до тех пор, пока на барьере не накопится количество заблокированных потоков, определенное ранее при вызове функции


9.1.5.1. Определение статуса завершения процесса

Из книги автора

9.1.5.1. Определение статуса завершения процесса Статус завершения (exit status) (известный также под другими именами значения завершения (exit value), кода возврата (return code) и возвращаемого значения (return value)) представляет собой 8-битовое значение, которое родитель может использовать


9.1.6. Использование статуса завершения порожденного процесса

Из книги автора

9.1.6. Использование статуса завершения порожденного процесса Когда процесс заканчивается, нормальным ходом событий для ядра является освобождение всех его ресурсов. Ядро сохраняет статус завершения законченного процесса, также, как сведения о ресурсах, которые он


3.4.1. Ожидание завершения процесса

Из книги автора

3.4.1. Ожидание завершения процесса Читатели, запускавшие программу fork-exec (см. листинг 3.4), должно быть, обратили внимание на то, что вывод команды ls часто появляется после того, как основная программа уже завершила свою работу. Это связано с тем, что дочерний процесс, в


Завершение выполнения процесса

Из книги автора

Завершение выполнения процесса Процесс завершает свое выполнение с помощью функции exit(). Эта функция может быть вызвана системным вызовом exit(2), а если завершение процесса вызвано получением сигнала, функцию exit() вызывает само ядро. Функция exit() выполняет следующие