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

13.1.3. Мультиплексирование с помощью select()

13.1.3. Мультиплексирование с помощью select()

Системный вызов poll() был изначально представлен как часть Unix-дерева System V. Усилиями разработчиков BSD та же основная проблема была решена похожим способом — предоставлением системного вызова select().

#include <sys/select.h>
int select(int numfds, fd_set * readfds, fd_set * writefds,
 fd_set * exceptfds, struct timeval * timeout);

Три промежуточных параметра — readfds, writefds и exceptfds — определяют, за какими файловыми дескрипторами необходимо следить. Каждый параметр — это указатель на fd_set, структуру данных, позволяющую процессу определить произвольное количество файловых дескрипторов[74]. Ею манипулируют с помощью перечисленных ниже макросов.

FD_ZERO(fd_set * fds); Очищает fds — в наборе не содержатся файловые дескрипторы. Этот макрос используется для инициализации структур fd_set.
FD_SET(intfd, fd_set * fds); Добавляет fd к fd_set.
FD_CLR(intfd, fd_set * fds); Удаляет fd из fd_set.
FD_ISSET(int fd, fd_set * fds); Возвращает true, если fd содержится в установленном fds.

Первый набор файловых дескрипторов select(), readfds, содержит перечень файловых дескрипторов, вызывающих возврат вызова select(), когда они готовы для чтения[75] или (для каналов и сокетов) когда процесс на другом конце файла закрыл его. Когда любой файловый дескриптор в writefds готов к записи, select() возвращается, exceptfds содержит файловые дескрипторы для слежения за исключительными условиями. В Linux (так же, как и в Unix) это происходит только при поступлении внешних данных в сетевое подключение. В качестве любого из них можно указать NULL, если тот или иной тип события вас не интересует.

Окончательный параметр, timeout, определяет, насколько долго (в миллисекундах) вызову select() необходимо ожидать какого-либо события. Это указывает на struct timeval, которая выглядит следующим образом.

#include <sys/time.h>
struct timeval {
 int tv_sec; /* секунды */
 int tv_usec; /* микросекунды */
};

Первый элемент — tv_sec — это количество оставшихся секунд, a tv_usec — это количество оставшихся микросекунд. Если значением timeout является NULL, select() блокируется до следующего события. Если он указывает на struct timeval, содержащую 0 в обоих элементах, вызов select() не блокируется. Он обновляет наборы файловых дескрипторов, чтобы определить, какой файловый дескриптор в настоящее время готов для чтения или записи, а затем немедленно возвращается.

Первый параметр, numfds, вызывает наибольшие трудности. Он задает количество файловых дескрипторов (начиная с файлового дескриптора 0), которое может быть определено с помощью fd_sets. Еще один (и, возможно, более легкий) способ поведения numfds намного лучше максимального файлового дескриптора select()[76].

Поскольку Linux обычно позволяет каждому процессу иметь до 1024 файловых дескрипторов, numfds избавляет ядро от необходимости просмотра всех 1024 файловых дескрипторов, которые может содержать каждая структура fd_set, что улучшает показатели производительности.

После возврата три структуры fd_set содержат файловые дескрипторы с задержкой входных данных, на которые можно произвести запись или которые находятся в исключительном состоянии. Вызов select() в Linux возвращает общее количество элементов, установленных в трех структурах fd_set, 0 в случае тайм-аута вызова либо -1 в случае ошибки. Однако многие системы Unix считают определенные файловые дескрипторы в возвращаемом значении только один раз, даже если они находятся как в readfds, так и в writefds, поэтому в целях переносимости лучше совершать проверку только тогда, когда возвращаемое значение больше 0. Если возвращаемое значение равно -1, не думайте, что структуры fd_set остаются незатронутыми. Linux обновляет их только в случае, если select() возвращает значение больше 0, однако некоторые системы Unix демонстрируют иное поведение.

Еще одним параметром, связанным с переносимостью, является timeout. Ядра Linux[77] обновляют его, чтобы отобразить количество времени, оставшегося до тайм-аута вызова select(), но большинство других систем Unix его не обновляют[78]. Однако другие системы не обновляют тайм-аут с целью соответствия более привычной реализации.

Для переносимости устраните зависимость от поведения и явно настройте структуру timeout перед вызовом select().

Теперь рассмотрим несколько примеров применения select(). Для начала используем select() без связи с файлами, создав вторичный вызов sleep().

#include <sys/select.h>
#include <sys/stdlib.h>
int usecsleep(int usees) {
 struct timeval tv;
 tv.tv_sec = 0;
 tv.tv_usec = useсs;
 return select(0, NULL, NULL, NULL, &tv);
}

Этот код разрешает переносимые паузы длительностью менее секунды (это обеспечивает также библиотечная функция BSD usleep(), но select() намного более переносима). Например, usecsleep(500000) вызывает паузу минимум на полсекунды.

Вызов select() также используется для решения примера мультиплексирования каналов, с которым мы работали. Решение очень похоже на решение при использовании poll().

 1: /* mpx-select.c */
 2:
 3: #include <fcntl.h>
 4: #include <stdio.h>
 5: #include <sys/select.h>
 6: #include <unistd.h>
 7:
 8: int main(void) {
 9:  int fds[2];
10:  char buf[4096];
11:  int i, rc, maxfd;
12:  fd_set watchset; /* fds для чтения */
13:  fd_set inset; /* обновляется select() */
14:
15:  /* открыть оба канала */
16:  if ((fds[0] = open("p1", O_RDONLY | O_NONBLOCK)) < 0) {
17:   perror("open p1");
18:   return 1;
19:  }
20:
21:  if ((fds[1] = open("p2", O_RDONLY | O_NONBLOCK)) < 0) {
22:   perror("open p2");
23:   return 1;
24:  }
25:
26:  /* начать чтение из обоих файловых дескрипторов */
27:  FD_ZERO(&watchset);
28:  FD_SET(fds[0], &watchset);
29:  FD_SET(fds[1], &watchset);
30:
31:  /* найти максимальный файловый дескриптор */
32:  maxfd = fds[0] > fds[1] ? fds[0] : fds[1];
33:
34:  /* пока наблюдаем за одним из fds[0] или fds[1] */
35:  while (FD_ISSET(fds[0], &watchset) ||
36:   FD_ISSET(fds[1], &watchset)) {
37:   /* здесь копируем watchset, потому что select() обновляет его */
38:   inset = watchset;
39:   if (select(maxfd + 1, &inset, NULL, NULL, NULL) < 0) {
40:    perror("select");
41:    return 1;
42:   }
43:
44:   /* проверить, какой из файловых дескрипторов
45:      готов для чтения из него */
46:   for (i = 0; i < 2; i++) {
47:    if (FD_ISSET(fds[i], &inset )) {
48:     /* fds[i] готов для чтения, двигаться дальше... */
49:     rc = read(fds[i], buf, sizeof (buf) - 1);
50:     if (rc < 0) {
51:      perror("read");
52:      return 1;
53:     } else if (!rc) {
54:      /* этот канал закрыт, не пытаться
55:         читать из него снова */
56:      close(fds[i]);
57:      FD_CLR(fds[i], &watchset);
58:     } else {
59:      buf[rc] = '';
60:      printf("чтение: %s", buf);
61:     }
62:    }
63:   }
64:  }
65:
66:  return 0;
67: }

Оглавление книги


Генерация: 1.827. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз