Как упоминалось в главе 11, неблокируемый файл можно определить с помощью системного вызова fcntl
. Если медленный файл неблокируемый, read()
сразу же возвращается. Если данные недоступны, она просто возвращает 0. Неблокируемый ввод- вывод предоставляет простое решение мультиплексирования, предотвращая блокирование файловых операций.
Показанная ниже модифицированная версия mpx-blocks
пользуется преимуществом неблокируемого ввода-вывода для более гладкого переключения между p1
и p2
.
1: /* mpx-nonblock.c */
2:
3: #include <errno.h>
4: #include <fcntl.h>
5: #include <stdio.h>
6: #include <unistd.h>
7:
8: int main(void) {
9: int fds[2];
10: char buf[4096];
11: int i;
12: int fd;
13:
14: /* открыть оба канала в неблокирующем режиме */
15: if ((fds[0] = open('p1', O_RDONLY | O_NONBLOCK)) < 0) {
16: perror('open p1');
17: return 1;
18: }
19:
20: if ((fds[1] = open('p2', O_RDONLY | O_NONBLOCK)) < 0) {
21: perror('open p2');
22: return 1;
23: }
24:
25: fd = 0;
26: while (1) {
27: /* если данные доступны, прочитать и отобразить их */
28: i = read(fds[fd], buf, sizeof (buf) - 1);
29: if ((i < 0) && (errno ! = EAGAIN)) {
30: perror('read');
31: return 1;
32: } else if (i > 0) {
33: buf[i] = ' ';
34: printf('чтение: %s', buf);
35: }
36:
37: /* читать из другого файлового дескриптора */
38: fd = (fd + 1) % 2;
39: }
40: }
Важное различие между mpx-nonblock
и mpx-blocks
состоит в том, что программа mpx-nonblock
не закрывается, когда один из каналов, из которого она считывает, закрыт. Неблокируемый read()
из канала без записывающих устройств возвращает 0 байт, из канала с таковыми, но без доступных данных read()
возвращает EAGAIN
.
Простое переключение неблокируемого ввода-вывода между дескрипторами файлов достается высокой ценой. Программа всегда опрашивает два файловых дескриптора для ввода — она никогда не блокируется. Постоянная работа программы приносит системе массу проблем, поскольку операционная система не может перевести процесс в режим ожидания (попробуйте запустить 10 копий mpx- nonblock
в своей системе и посмотрите, как это скажется на ее производительности).
13.1.2. Мультиплексирование с помощью poll()
Для эффективного мультиплексирования Linux предоставляет системный вызов poll()
, позволяющий процессу блокировать одновременно несколько файловых дескрипторов. Постоянно проверяя каждый файловый дескриптор, процесс создает отдельный системный вызов, определяющий, из каких файловых дескрипторов процесс будет читать, а на какие — записывать. Когда один или несколько таких файлов имеют данные, доступные для чтения, или могут принимать данные, записываемые в них, poll
() завершается, и приложение может считывать и записывать данные в дескрипторах, не беспокоясь о блокировке. После обработки этих файлов процесс создает еще один вызов poll()
, блокируемый до готовности файла. Ниже показано определение poll()
.
#include <sys/poll.h>
int poll(struct pollfd * fds, int numfds, int timeout);
Последние два параметра очень просты; numfds
задает количество элементов в массиве, на который указывает первый параметр, a timeout
определяет, насколько долго poll()
должна ожидать события. Если в качестве тайм-аута задается 0, poll()
никогда не входит в состояние тайм-аута.
Первый параметр, fds
, описывает, какие файловые дескрипторы следует контролировать, и для каких типов ввода-вывода. Это указатель на массив структур struct pollfd
.
struct pollfd {
int fd; /* файловый дескриптор */
short events; /* ожидаемые события ввода-вывода */
short revents; /* происшедшие события ввода-вывода */
};
Первый элемент, fd
, является контролируемым файловым дескриптором, а элемент events описывает, какие типы событий подлежат мониторингу. Последний представляет собой один или несколько перечисленных флагов, объединенных с помощью логического 'ИЛИ'.
POLLIN | Нормальные данные доступны для считывания из файлового дескриптора. |
POLLPRI | Приоритетные (внешние) данные доступны для считывания. |