Опубликован: 28.04.2010 | Уровень: специалист | Доступ: платный | ВУЗ: Новосибирский Государственный Университет
Лекция 8:

Мультиплексирование ввода/вывода и асинхронный ввод/вывод

< Лекция 7 || Лекция 8: 1234 || Лекция 9 >

Мультиплексирование ввода при помощи poll(2)

Системный вызов poll(2) выполняет приблизительно те же задачи, что и select(3C), но использует несколько более удобный способ передачи информации о том, какие дескрипторы его интересуют. poll(2) имеет три параметра:

  • struct pollfd fds[] - массив описателей дескрипторов. Структура pollfd обсуждается далее в этом разделе
  • nfds_t nfds - количество описателей в массиве fds
  • int timeout - тайм-аут в миллисекундах. Если параметр timeout равен 0, poll работает в режиме опроса (возвращает управление немедленно). Если он равен -1, poll ждет готовности дескрипторов неограниченное время.

poll(2) возвращает количество дескрипторов, с которыми произошли какие-то события, запрошенные программой либо представляющие интерес для нее. Если poll(2) возвращает управление по тайм-ауту, код возврата будет равен 0. При ошибке poll(2) возвращает -1 и устанавливает errno.

Структура pollfd имеет следующие поля:

  • int fd - дескриптор файла. Если это поле имеет отрицательное значение, запись игнорируется.
  • short events - события, связанные с fd, которые нас интересуют.
  • short revents - return events, события, связанные с fd, которые реально произошли.

При вызове poll пользователь должен заполнить поля fd и events ; поле revents заполняется системным вызовом.

Поля events и revents представляют собой битовые маски, биты которых соответствуют типам событий. Вместо битов рекомендуется использовать символьные константы, определенные в <poll.h>

Основные используемые типы событий - POLLIN (проверять готовность к чтению), и POLLOUT (проверять готовность к записи). В действительности, эти типы композитные и представляют собой сочетания разных типов событий. Так, для сокетов TCP можно указывать проверку поступления внеполосных данных, для устройств STREAMS - проверку поступления приоритетных данных и т.д. В revents устанавливаются биты, соответствующие реально происшедшему событию, т.е. если вы заказывали ожидание POLLIN, не обязательно в revents будут установлены все биты, входящие в маску POLLIN. Это необходимо иметь в виду при проверке revents (см. пример 8.2).

Кроме POLLIN и POLLOUT, в revents также могут появляться биты POLLERR, POLLHUP и POLLNVAL. В events эти биты игнорируются, а в revents могут быть установлены при следующих условиях:

  • POLLERR - на устройстве возникла ошибка
  • POLLHUP - сокет, труба или терминальное устройство закрыты на другом конце
  • POLLNVAL - значение fd не соответствует валидному файловому дескриптору (скорее всего, дескриптор был закрыт на нашем конце).
#include <poll.h> 
struct pollfd fds[3]; 
int ifd1, ifd2, ofd, count; 


fds[0].fd = ifd1; 
fds[0].events = POLLNORM; 
fds[1].fd = ifd2; 
fds[1].events = POLLNORM; 
fds[2].fd = ofd; 
fds[2].events = POLLOUT; 
count = poll(fds, 3, 10000); 
if (count == -1) { 
    perror("poll failed"); 
    exit(1); 
} 
if (count==0) 
    printf("No data for reading or writing\n"); 
if (fds[0].revents & POLLNORM) 
    printf("There is data for reading fd %d\n", fds[0].fd); 
if (fds[1].revents & POLLNORM) 
    printf("There is data for reading fd %d\n", fds[1].fd); 
if (fds[2].revents & POLLOUT) 
    printf("There is room to write on fd %d\n", fds[2].fd);
8.2. Использование poll(2) (фрагмент программы)

Преимущества poll(2) перед select(3C) достаточно очевидны:

  1. интерфейс poll не накладывает ограничений на пространство номеров дескрипторов, во всяком случае пока эти номера входят в диапазон представления int.
  2. при большом пространстве номеров дескрипторов ( 65536 в данном контексте следует считать большим пространством), poll часто требует передачи между пользовательским процессом и ядром меньшего объема данных, чем select.
  3. poll сообщает больше информации о происшедших с дескриптором событиях, чем может сообщить select
  4. У poll входные и выходные значения разнесены по разным полям структуры, так что не требуется полностью пересоздавать массив fds после каждого вызова.

При использовании poll(2) в многопоточной программе приложимы те же соображения, которые высказывались в конце предыдущего раздела.

Использование /dev/poll

Использование poll(2) с большим количеством файловых дескрипторов приводит к передаче больших объемов данных между пользовательским процессом и ядром. При этом, скорее всего, большая часть этих данных передается впустую - ведь если процесс действительно может работать с таким большим числом дескрипторов, это, скорее всего, означает, что большинство из них не готовы к работе.

В Solaris предоставляется нестандартный API, который может использоваться для решения этой проблемы. Этот API описывается на странице системного руководства poll(7D) и состоит в использовании специального псевдоустройства /dev/poll.

Это устройство открывается как обычный файл системным вызовом open(2). Затем в него следует записать одну или несколько структур pollfd (т.е. тех же самых структур, которые использует poll(2) ). Запись осуществляется системным вызовом write(2) и может осуществляться в несколько приемов. При этом, если вы несколько раз записываете структуры, соответствующие одному и тому же дескриптору, с разными значениями поля events, это будет означать расширение списка опрашиваемых событий для вашего дескриптора. Т.е. если вы сначала запишете pollfd с events==POLLIN, а затем с events==POLLOUT, дескриптор будет опрашиваться в режиме POLLIN | POLLOUT.

Если вы хотите исключить дескриптор из множества опрашиваемых, вам следует записать структуру pollfd, в которой поле events содержит бит POLLREMOVE.

Многократное открытие /dev/poll одним процессом приводит к созданию нескольких независимых наборов дескрипторов.

Сам опрос осуществляется вызовом ioctl(2) с командой DP_POLL. Этот ioctl использует в качестве параметра значение struct dvpoll *. Тип struct dvpoll описан в <sys/devpoll.h> и содержит следующие поля:

  • struct pollfd* dp_fds - указатель на массив, в который следует положить описатели дескрипторов, с которым связаны события
  • int dp_nfds - размер массива dp_fds. Также, максимальное количество описателей дескрипторов, которые следует получить
  • int dp_timeout - тайм-аут в миллисекундах. Этот параметр соответствует параметру timeout poll(2).

Ioctl DP_POLL возвращает количество описателей файловых дескрипторов, записанных в dp_fds, 0 если ioctl был разблокирован по тайм-ауту и -1 в случае ошибки.

С практической точки зрения, важное отличие этого API от poll(2) состоит в том, что при использовании poll(2) описатели дескрипторов после опроса расположены на тех же местах в массиве, на которых вы сами их разместили. Напротив, ioctl DP_POLL возвращает вам массив, который включает только те дескрипторы, с которыми связаны события, причем эти дескрипторы лежат в массиве в том порядке, в котором их счел удобным разместить драйвер /dev/poll. Т.е. вы должны проcматривать dp_fds в порядке увеличения индекса, используя затем значение поля fd как ключ поиска. Скорее всего, вам придется завести массив (возможно, ассоциативный), связывающий значение файлового дескриптора с метаинформацией о том, что это за дескриптор и что вы с ним хотели делать. При работе с этим массивом вам следует иметь в виду, что Unix при нормальной работе переиспользует номера файловых дескрипторов, т.е. вам надо обновлять данные в вашем массиве каждом закрытии файла.

Корректная реализация всей требуемой функциональности (особенно в многопоточной программе) требует большого объемакода, причем кода, довольно сложного при отладке. Скорее всего, именно поэтому поддержка poll(7D) приложениями ограниченна и этот API не стал ни юридическим стандартом, ни даже стандартом де-факто. Большинство современных Unix-систем, за исключением Solaris, не поддерживают /dev/poll и не имеют планов реализации такой поддержки в обозримом будущем.

Однако с точки зрения производительности poll(7D) дает ощутимые преимущества даже при вполне реалистичных количествах файловых дескрипторов на процесс. По данным измерений, опубликованных на сайте http://developers.sun.com/solaris/articles/polling_efficient.html, при 4000 тысячах файловых дескрипторов, poll(7D) требует в 16 раз меньше процессорного времени на исполнение заданного количества циклов опроса-чтения-записи, чем poll(2)!

< Лекция 7 || Лекция 8: 1234 || Лекция 9 >
Dima Puvovarov
Dima Puvovarov
Россия
Святослав Песенко
Святослав Песенко
Украина, Кривой Рог, КГПУ, 2006