Литмир - Электронная Библиотека
Содержание  
A
A

Листинг 20.4. Простая некорректная реализация функции pselect

//lib/pselect.c

 9 #include "unp.h"

10 int

11 pselect(int nfds, fd_set *rset, fd_set *wset, fd_set *xset,

12  const struct timespec *ts, const sigset_t *sigmask)

13  {

14  int n;

15  struct timeval tv;

16  sigset_t savemask;

17  if (ts != NULL) {

18   tv.tv_sec = ts->tv_sec;

19   tv.tv_usec = ts->tv_nsec / 1000; /* наносекунды -> микросекунды */

20  }

21  sigprocmask(SIG_SETMASK, sigmask, &savemask); /* маска вызывающего

                                                     процесса */

22  n = select(nfds, rset, wset, xset., (ts == NULL) ? NULL : &tv);

23  sigprocmask(SIG_SETMASK, &savemask, NULL); /* восстанавливаем

                                                  исходную маску */

24  return (n);

25 }

Использование функций sigsetjmp и siglongjmp

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

siglongjmp
. Этот метод называется нелокальным оператором goto (nonlocal goto), поскольку мы можем использовать его для перехода из одной функции в другую. В листинге 20.5 проиллюстрирована эта технология.

Листинг 20.5. Вызов функций sigsetjmp и siglongjmp из обработчика сигнала

//bcast/dgclibcast5.c

 1 #include "unp.h"

 2 #include <setjmp.h>

 3 static void recvfrom_alarm(int);

 4 static sigjmp_buf jmpbuf;

 5 void

 6 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

 7 {

 8  int n;

 9  const int on = 1;

10  char sendline[MAXLINE], recvline[MAXLINE + 1];

11  socklen_t len;

12  struct sockaddr *preply_addr;

13  preply_addr = Malloc(servlen);

14  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));

15  Signal(SIGALRM, recvfrom_alarm);

16  while (Fgets(sendline, MAXLINE, fp) != NULL) {

17   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

18   alarm(5);

19   for (;;) {

20    if (sigsetjmp(jmpbuf, 1) != 0)

21     break;

22    len = servlen;

23    n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

24    recvline[n] = 0; /* null terminate */

25    printf("from %s: %s",

26     Sock_ntop_host(preply_addr, len), recvline);

27   }

28  }

29  free(preply_addr);

30 }

31 static void

32 recvfrom_alarm(int signo)

33 {

34  siglongjmp(jmpbuf, 1);

35 }

Размещение буфера перехода в памяти

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

Вызов функции sigsetjmp

20-23
 Когда мы вызываем функцию
sigsetjmp
непосредственно из нашей функции
dg_cli
, она устанавливает буфер перехода и возвращает нуль. Мы продолжаем работать дальше и вызываем функцию
recvfrom
.

Обработка сигнала SIGALRM и вызов функции siglongjmp

31-35
 Когда сигнал доставлен, мы вызываем функцию
siglongjmp
. Это заставляет
sigsetjmp
в функции
dg_cli
возвратить значение, равное второму аргументу (1), который должен быть ненулевым. Это приведет к завершению цикла
for
в функции
dg_cli
.

Использование функций

sigsetjmp
и
siglongjmp
подобным образом гарантирует, что мы не останемся навсегда блокированы в вызове функции
recvfrom
из-за доставки сигнала в неподходящее время. Однако такое решение создает иную потенциальную проблему. Если сигнал доставляется в тот момент, когда функция
printf
осуществляет вывод данных, управление будет передано из
printf
обратно на
sigsetjmp
. При этом в структурах данных
printf
могут возникнуть противоречия. Чтобы предотвратить эту проблему, следует объединить блокирование и разблокирование сигналов, показанное в листинге 20.2, с помощью нелокального оператора
goto
.

Применение IPC в обработчике сигнала функции

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

recvfrom
, наш обработчик сигнала при помощи средств IPC (Interprocess Communications — взаимодействие процессов) может сообщить функции
dg_cli
о том, что время таймера истекло. Это аналогично предложению, сделанному нами раньше, когда обработчик сигнала устанавливал глобальную переменную
had_alarm
по истечении времени таймера. Глобальная переменная использовалась как некая разновидность IPC (поскольку она была доступна и нашей функции, и обработчику сигнала). Однако при таком решении наша функция должна была проверять эту переменную, что могло привести к проблемам синхронизации в том случае, когда сигнал доставлялся приблизительно в это же время.

225
{"b":"225366","o":1}