Альтернативным способом является удаление обоих вызовов функции
sigprocmask
, находящихся внутри цикла
for
, что предотвращает разблокирование сигнала и его последующее блокирование. Однако проблема состоит в следующем: в такой ситуации весь цикл выполняется при блокированном сигнале, что уменьшает быстроту реагирования обработчика сигнала. При этом дейтаграммы не будут теряться (если, конечно, буфер приема сокета достаточно велик), но выдача сигнала процессу будет задерживаться на время блокирования сигнала. Одной из задач при создании приложений, производящих обработку сигналов, должна быть минимизация времени блокирования сигнала.
Листинг 25.3. Обработчик сигнала SIGIO
//sigio/dgecho01.c
57 static void
58 sig_io(int signo)
59 {
60 ssize_t len;
61 int nread;
62 DG *ptr;
63 for (nread = 0;;) {
64 if (nqueue >= QSIZE)
65 err_quit("receive overflow");
66 ptr = &dg[iput];
67 ptr->dg_salen = clilen;
68 len = recvfrom(sockfd, ptr->dg_data, MAXDG, 0,
69 ptr->dg_sa, &ptr->dg_salen);
70 if (len < 0) {
71 if (errno == EWOULDBLOCK)
72 break; /* все сделано; очередь на чтение отсутствует */
73 else
74 err_sys("recvfrom error");
75 }
76 ptr->dg_len = len;
77 nread++;
78 nqueue++;
79 if (++iput >= QSIZE)
80 iput = 0;
81 }
82 cntread[nread]++; /* гистограмма количества дейтаграмм.
считанных для каждого сигнала */
83 }
Во время создания этих обработчиков сигналов была обнаружена следующая проблема: в стандарте POSIX сигналы обычно не помещаются в очередь. Это означает, что если во время пребывания внутри обработчика сигналов (при этом сигнал заведомо заблокирован) возникает еще два сигнала, то сигнал доставляется еще один раз.
ПРИМЕЧАНИЕ
В стандарте POSIX предусмотрено несколько сигналов реального времени, для которых обеспечивается буферизация, однако ряд других сигналов, в том числе и SIGIO, обычно не буферизуются, то есть не помещаются в очередь на доставку.
Рассмотрим следующий сценарий. Прибывает дейтаграмма и выдается сигнал. Обработчик сигнала считывает дейтаграмму и помещает ее в очередь к основному циклу. Но во время работы обработчика сигнала приходят еще две дейтаграммы, вызывая генерацию сигнала еще дважды. Поскольку сигнал блокирован, то когда обработчик сигналов возвращает управление после обработки первого сигнала, он запустится снова всего лишь один раз. После второго запуска обработчик считывает вторую дейтаграмму, а третья будет оставлена в очереди приходящих дейтаграмм сокета. Эта третья дейтаграмма будет прочитана, только если (и только когда) придет четвертая. Когда придет четвертая дейтаграмма, считана и поставлена в очередь на обработку основным циклом будет именно третья, а не четвертая дейтаграмма.
Поскольку сигналы не помещаются в очередь, дескриптор, установленный для управляемого сигналом ввода-вывода, обычно переводится в неблокируемый режим. Обработчик сигнала
SIGIO
мы кодируем таким образом, чтобы он считывал дейтаграммы в цикле, который прерывается, только когда при считывании возвращается ошибка
EWOULDBLOCK
.
Проверка переполнения очереди
64-65
Если очередь переполняется, происходит завершение работы. Для обработки такой ситуации существуют и другие способы (например, можно размещать в памяти дополнительные буферы), но для данного примера достаточно простого завершения.
Чтение дейтаграммы
66-76
На неблокируемом сокете вызывается функция
recvfrom
. Элемент массива, обозначенный индексом
iput
, — это то место, куда записывается дейтаграмма. Если нет дейтаграмм, которые нужно считывать, мы выходим из цикла
for
с помощью оператора
break
.
Увеличение счетчиков и индекса на единицу
77-80
Переменная
nread
является диагностическим счетчиком количества дейтаграмм, читаемых на один сигнал. Переменная
nqueue
— это количество дейтаграмм для обработки основным циклом.
82
Прежде чем обработчик сигналов возвращает управление, он увеличивает счетчик на единицу в соответствии с количеством дейтаграмм, прочитанных за один сигнал. Этот массив распечатывается программой в листинге 25.4 и представляет собой диагностическую информацию для обработки сигнала
SIGHUP
.
Последняя функция (листинг 25.4) представляет собой обработчик сигнала
SIGHUP
, который выводит массив
cntread
. Он считает количество дейтаграмм, прочитанных за один сигнал.
Листинг 25.4. Обработчик сигнала SIGHUP
//sigio/dgecho01.c
84 static void
85 sig_hup(int signo)
86 {
87 int i;
88 for (i = 0; i <= QSIZE; i++)
89 printf("cntread[%d] = %ld\n", i, cntread[i]);
90 }
Чтобы проиллюстрировать, что сигналы не буферизуются и что в дополнение к установке флага, указывающего на управляемый сигналом ввод-вывод, необходимо перевести сокет в неблокируемый режим, запустим этот сервер с шестью клиентами одновременно. Каждый клиент посылает серверу 3645 строк (для отражения). При этом каждый клиент запускается из сценария интерпретатора в фоновом режиме, так что все клиенты стартуют приблизительно одновременно. Когда все клиенты завершены, серверу посылается сигнал
SIGHUP
, в результате чего сервер выводит получившийся массив
cntread
: