7 pid = wait(&stat);
8 printf("child terrmnated\n", pid);
9 return;
10 }
В обработчике сигналов не рекомендуется вызов стандартных функций ввода-вывода, таких как printf, по причинам, изложенным в разделе 11.18. В данном случае мы вызываем функцию printf как средство диагностики, чтобы увидеть, когда завершается дочерний процесс.
В системах System V и Unix 98 дочерний процесс не становится зомби, если процесс задает действие SIG_IGN для SIGCHLD. К сожалению, это верно только для System V и Unix 98. В POSIX прямо сказано, что такое поведение этим стандартом не предусмотрено. Переносимый способ обработки зомби состоит в том, чтобы перехватывать сигнал SIGCHLD и вызывать функцию wait или waitpid.
Если мы откомпилируем в Solaris 9 программу, представленную в листинге 5.1, вызывая функцию Signal
с нашим обработчиком sig_chld
, и будем использовать функцию signal
из системной библиотеки (вместо нашей версии, показанной в листинге 5.5), то получим следующее:
solaris % tcpserv02 &
[2] 16939
solaris % tcpcli01 127.0.0.1
hi there
hi there
^D
child 16942 terminated
accept error: Interrupted system call
Последовательность шагов в этом примере такова:
1. Мы завершаем работу клиента, вводя символ EOF. TCP клиента посылает сегмент FIN серверу, и сервер отвечает сегментом ACK.
2. Получение сегмента FIN доставляет EOF ожидающей функции readline
дочернего процесса. Дочерний процесс завершается.
3. Родительский процесс блокирован в вызове функции accept
, когда доставляется сигнал SIGCHLD
. Функция sig_chld
(наш обработчик сигнала) выполняется, функция wait
получает PID дочернего процесса и статус завершения, после чего из обработчика сигнала вызывается функция printf
. Обработчик сигнала возвращает управление.
4. Поскольку сигнал был перехвачен родительским процессом, в то время как родительский процесс был блокирован в accept
), ядро заставляет функцию accept
возвратить ошибку EINTR
(прерванный системный вызов). Родительский процесс не обрабатывает эту ошибку корректно (см. листинг 5.1), поэтому функция main
преждевременно завершается.
Цель данного примера — показать, что при написании сетевых программ, перехватывающих сигналы, необходимо получать информацию о прерванных системных вызовах и обрабатывать их. В этом специфичном для Solaris 2.5 примере функция signal
из стандартной библиотеки С не осуществляет автоматический перезапуск прерванного вызова, то есть флаг SA_RESTART
, установленный нами в листинге 5.5, не устанавливается функцией signal из системной библиотеки. Некоторые другие системы автоматически перезапускают прерванный системный вызов. Если мы запустим тот же пример в 4.4BSD, используя ее библиотечную версию функции signal
, ядро перезапустит прерванный системный вызов и функция accept
не возвратит ошибки. Одна из причин, по которой мы определяем нашу собственную версию функции signal
и используем ее далее, — решение этой потенциальной проблемы, возникающей в различных операционных системах (см. листинг 5.5).
Кроме того, мы всегда программируем явную функцию return
для наших обработчиков сигналов (см. листинг 5.6), даже если функция ничего не возвращает (void
), чтобы этот оператор напоминал нам о возможности прерывания системного вызова при возврате из обработчика.
Обработка прерванных системных вызовов