Различия между функциями wait и waitpid

Различия между функциями wait и waitpid

Теперь мы проиллюстрируем разницу между функциями wait и waitpid, используемыми для сброса завершенных дочерних процессов. Для этого мы изменим код нашего клиента TCP так, как показано в листинге 5.7. Клиент устанавливает пять соединений с сервером, а затем использует первое из них (sockfd[0]) в вызове функции str_cli. Несколько соединений мы устанавливаем для того, чтобы породить от параллельного сервера множество дочерних процессов, как показано на рис. 5.2.

Рис. 5.2. Клиент, установивший пять соединений с одним и тем же параллельным сервером

Листинг 5.7. Клиент TCP, устанавливающий пять соединений с сервером

//tcpcliserv/tcpcli04.c

 1 #include "unp.h"

 2 int

 3 main(int argc, char **argv)

 4 {

 5  int i, sockfd[5];

 6  struct sockaddr_in servaddr;

 7  if (argc != 2)

 8   err_quit("usage: tcpcli <Ipaddress>");

 9  for (i = 0; i < 5; i++) {

10   sockfd[i] = Socket(AF_INET, SOCK_STREAM, 0);

11   bzero(&servaddr, sizeof(servaddr));

12   servaddr.sin_family = AF_INET;

13   servaddr.sin_port = htons(SERV_PORT);

14   Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

15   Connect(sockfd[i], (SA*)&servaddr, sizeof(servaddr));

16  }

17  str_cli(stdin, sockfd[0]); /* эта функция выполняет все необходимые

                               действия для формирования запроса клиента */

18  exit(0);

19 }

Когда клиент завершает работу, все открытые дескрипторы автоматически закрываются ядром (мы не вызываем функцию close, а пользуемся только функцией exit) и все пять соединений завершаются приблизительно в одно и то же время. Это вызывает отправку пяти сегментов FIN, по одному на каждое соединение, что, в свою очередь, вызывает примерно одновременное завершение всех пяти дочерних процессов. Это приводит к доставке пяти сигналов SIGCHLD практически в один и тот же момент, что показано на рис. 5.3.

Доставка множества экземпляров одного и того же сигнала вызывает проблему, к рассмотрению которой мы и приступим.

Рис. 5.3. Клиент завершает работу, закрывая все пять соединений и завершая все пять дочерних процессов

Сначала мы запускаем сервер в фоновом режиме, а затем — новый клиент. Наш сервер, показанный в листинге 5.1, несколько модифицирован — теперь в нем вызывается функция signal для установки обработчика сигнала SIGCHLD, приведенного в листинге 5.6.

linux % tcpserv03 &

[1] 20419

linux % tcpcli04 206.62.226.35

hello мы набираем эту строку

hello и она отражается сервером

^D    мы набираем символ конца файла

child 20426 terminated выводится сервером

Первое, что мы можем заметить, — данные выводит только одна функция printf, хотя мы предполагаем, что все пять дочерних процессов должны завершиться. Если мы выполним программу ps, то увидим, что другие четыре дочерних процесса все еще существуют как зомби.

PID TTY TIME CMD

20419 pts/6 00:00:00 tcpserv03

20421 pts/6 00:00:00 tcpserv03 <defunct>

20422 pts/6 00:00:00 tcpserv03 <defunct>

20423 pts/6 00:00:00 tcpserv03 <defunct>

Установки обработчика сигнала и вызова функции wait из этого обработчика недостаточно для предупреждения появления зомби. Проблема состоит в том, что все пять сигналов генерируются до того, как выполняется обработчик сигнала, и вызывается он только один раз, поскольку сигналы Unix обычно не помещаются в очередь. Более того, эта проблема является недетерминированной. В приведенном примере с клиентом и сервером на одном и том же узле обработчик сигнала выполняется один раз, оставляя четыре зомби. Но если мы запустим клиент и сервер на разных узлах, то обработчик сигналов, скорее всего, выполнится дважды: один раз в результате генерации первого сигнала, а поскольку другие четыре сигнала приходят во время выполнения обработчика, он вызывается повторно только один раз. При этом остаются три зомби. Но иногда в зависимости от точного времени получения сегментов FIN на узле сервера обработчик сигналов может выполниться три или даже четыре раза.

Правильным решением будет вызвать функцию waitpid вместо wait. В листинге 5.8 представлена версия нашей функции sigchld, корректно обрабатывающая сигнал SIGCHLD. Эта версия работает, потому что мы вызываем функцию waitpid в цикле, получая состояние любого из дочерних процессов, которые завершились. Необходимо задать параметр WNOHANG: это указывает функции waitpid, что не нужно блокироваться, если существуют выполняемые дочерние процессы, которые еще не завершились. В листинге 5.6 мы не могли вызвать функцию wait в цикле, поскольку нет возможности предотвратить блокирование функции wait при наличии выполняемых дочерних процессов, которые еще не завершились.

В листинге 5.9 показана окончательная версия нашего сервера. Он корректно обрабатывает возвращение ошибки EINTR из функции accept и устанавливает обработчик сигнала (листинг 5.8), который вызывает функцию waitpid для всех завершенных дочерних процессов.

Листинг 5.8. Окончательная (корректная) версия функции sig_chld, вызывающая функцию waitpid

//tcpcliserv/sigchldwaitpid.c

 1 #include "unp.h"

 2 void

 3 sig_chld(int signo)

 4 {

 5  pid_t pid;

 6  int stat;

 7  while ((pid = waitpid(-1, &stat, WNOHANG)) >0)

 8   printf("child %d terminated ", pid);

 9  return;

10 }

Листинг 5.9. Окончательная (корректная) версия TCP-сервера, обрабатывающего ошибку EINTR функции accept

//tcpcliserv/tcpserv04.c

 1 #include "unp.h"

 2 int

 3 main(int argc, char **argv)

 4 {

 5  int listenfd, connfd;

 6  pid_t childpid;

 7  socklen_t clilen;

 8  struct sockaddr_in cliaddr, servaddr;

 9  void sig_chld(int);

10  listenfd = Socket(AF_INET, SOCK_STREAM, 0);

11  bzero(&servaddr, sizeof(servaddr));

12  servaddr.sin_family = AF_INET;

13  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

14  servaddr.sin_port = htons(SERV_PORT);

15  Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));

16  Listen(listenfd, LISTENQ);

17  Signal(SIGCHLD, sig_chld); /* нужно вызвать waitpid() */

18  for (;;) {

19   clilen = sizeof(cliaddr);

20   if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {

21    if (errno == EINTR)

22     continue; /* назад к for() */

23    else

24     err_sys("accept error");

25   }

26   if ((childpid = Fork()) == 0) { /* дочерний процесс */

27    Close(listenfd); /* закрываем прослушиваемый сокет */

28    str_echo(connfd); /* обрабатываем запрос */

29    exit(0);

30   }

31   Close(connfd); /* родитель закрывает присоединенный сокет */

32  }

33 }

Целью этого раздела было продемонстрировать три сценария, которые могут встретиться в сетевом программировании.

1. При выполнении функции fork, порождающей дочерние процессы, следует перехватывать сигнал SIGCHLD.

2. При перехватывании сигналов мы должны обрабатывать прерванные системные вызовы.

3. Обработчик сигналов SIGCHLD должен быть создан корректно с использованием функции waitpid, чтобы не допустить появления зомби.

Окончательная версия нашего сервера TCP (см. листинг 5.9) вместе с обработчиком сигналов SIGCHLD в листинге 5.8 обрабатывает все три сценария.

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

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

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

4.2. Различия между внутренними и внешними оценками производственного процесса

Из книги Модель зрелости процессов разработки программного обеспечения автора Паулк Марк

4.2. Различия между внутренними и внешними оценками производственного процесса Несмотря на эти сходства, результаты внутренней и внешней оценки могут расходиться даже в случае успешного применения одного и того же метода. Одной из причин этого является то, что объем


12.3.3.2 Wait

Из книги Архитектура операционной системы UNIX автора Бах Морис Дж

12.3.3.2 Wait многопроцессорная версия алгоритма wait { для (;;) { /* цикл */ перебор всех процессов-потомков:   if (потомок находится в состоянии "прекращения существования") return;  P(zombie_semaphore);  /* начальное значение — 0 */ }}Рисунок 12.15. Многопроцессорная версия алгоритма waitИз главы 7 мы


Различия между приложением и сеансом

Из книги Энциклопедия разработчика модулей ядра Linux автора Померанц Ори

Различия между приложением и сеансом В ASP.NET состояние приложения учитывается экземпляром типа HttpApplicationState. Этот класс дает возможность сделать глобальную информацию доступной для всех пользователей (и всех страниц), зарегистрированных в вашем приложении ASP.NET. При этом


Различия между версиями 2.0 и 2.2

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

Различия между версиями 2.0 и 2.2 Я не знаю, что все ядро достаточно хорошо документирует все изменения. В ходе преобразования примеров (или фактически, адаптации изменений Еммануела Папиракиса) я натолкнулся на следующие различия. Я привожу их все здесь вместе, чтобы помочь


Различия между Windows и UNIX

Из книги TCP/IP Архитектура, протоколы, реализация (включая IP версии 6 и IP Security) автора Фейт Сидни М

Различия между Windows и UNIX В Windows и UNIX выбраны различные стратегии. Большинство поставщиков UNIX-систем реализуют модель LP64, в которой размер как длинного целочисленного, так и указательного типов данных составляет 64 бита. Такую модель иногда называют моделью "I32, LP64", чтобы


17.8 Различия между новостями и рассылочным списком

Из книги VBA для чайников автора Каммингс Стив

17.8 Различия между новостями и рассылочным списком Приложения для сетевых новостей более эффективны, чем рассылочные списки. Новости хранятся на центральном сервере и доступны для многих пользователей. Несколько пользователей могут одновременно читать новости из


Различия между VBA и Visual Basic

Из книги Защита от хакеров корпоративных сетей автора Автор неизвестен

Различия между VBA и Visual Basic VBA имеет очень много общего с Visual Basic, своим старшим братом, предназначенным для создания независимых приложений. А раз языки похожи, вы можете перенести большую часть своих навыков в программировании на VBA в Visual Basic. Однако вам следует помнить о


Различия между вирусами, Троянскими программами и червями

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

Различия между вирусами, Троянскими программами и червями Вредоносный код обычно классифицируется по типу механизма распространения. В некоторых случаях принимаются во внимание платформа, на которой он работает, и механизм запуска (например, для активизации


Использование указателей для связи между функциями

Из книги Linux и UNIX: программирование в shell. Руководство разработчика. автора Тейнсли Дэвид

Использование указателей для связи между функциями      Мы только прикоснулись к обширному и увлекательному миру указателей. Сейчас нашей целью является использование указателей для решения задачи об установлении связи между функциями. Ниже приводится программа, в


wait

Из книги Цифровая фотография от А до Я [2-е издание] автора Газаров Артур Юрьевич

wait Формат:wait ID процессаЭта команда устанавливает длительность ожидания для ID процесса перед возобновлением его выполнения либо устанавливает длительность ожидания всех фоновых процессов перед возобновлением их выполнения.Для задания интервала ожидания ID процесса


Различия между пленочной и цифровой фотографией

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

Различия между пленочной и цифровой фотографией Внешне многие зеркальные цифровые и пленочные фотокамеры очень похожи. Но разница между пленочными и цифровыми фотоаппаратами существенна — в первом случае в качестве светочувствительного элемента используется


5.10. Функции wait и waitpid

Из книги Wiki-правительство [Как технологии могут сделать власть лучше, демократию – сильнее, а граждан – влиятельнее] автора Новек Бет

5.10. Функции wait и waitpid В листинге 5.7 мы вызываем функцию wait для обработки завершенного дочернего процесса.#include <sys/wait.h>pid_t wait(int *statloc);pid_t waitpid(pid_t pid, int *statloc, int options);Обе функции возвращают ID процесса в случае успешного выполнения, -1 в случае ошибкиОбе функции, и wait, и waitpid,


Различия между управляющими объектами (drivers) и ограничениями

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

Различия между управляющими объектами (drivers) и ограничениями Управляющие объекты и ограничения похожи тем, что они влияют на изменение свойств пути (речь идет о параметрах анимации — прим. пер.), но в тоже время они очень разные: ограничения действуют непосредственно на