31: sigemptyset(&mask);
32: sigaddset(&mask, SIGRTMIN);
33:
34: sigprocmask(SIG_BLOCK, &mask, &oldMask);
35:
36: /* Сгенерировать сигналы */
37: val.sival_int = 1;
38: sigqueue(me, SIGRTMIN, val);
39: val.sival_int++;
40: sigqueue(me, SIGRTMIN, val);
41: val.sival_int++;
42: sigqueue(me, SIGRTMIN, val);
43:
44: /* Разрешить доставку сигналов */
45: sigprocmask(SIG_SETMASK, &oldMask, NULL);
46:
47: return 0;
48: }
Глава 13
Расширенная обработка файлов
В Linux файлы применяются при решении большого количества задач, среди которых, например, хранение долговременных данных, организация сетей с помощью сокетов и доступ к устройствам посредством файлов устройств. Разнообразие приложений, работающих с файлами, привело к созданию множества специальных способов управления файлами. В главе 11 рассматривались наиболее распространенные действия с файлами; в настоящей же главе исследуются специализированные файловые операции. В частности, мы рассмотрим следующие вопросы: использование одновременно нескольких файлов, отображение файлов на системную память, блокировка файлов, чтение и запись вразброс.
13.1. Мультиплексирование входных и выходных данных
Многим клиент-серверным приложениям необходимо считывать входные данные или записывать выходные данные с помощью одновременно нескольких файловых дескрипторов. Например, современные Web-браузеры открывают одновременно несколько сетевых подключений, чтобы уменьшить время загрузки Web-страницы. Это позволяет им загружать множество изображений, имеющихся на большинстве Web-страниц, быстрее, чем с помощью последовательных подключений. Кроме канала межпроцессных взаимодействий (IPC), используемого графическими браузерами для связи с X-сервером, на котором они отображаются, браузеры работают с множеством файловых дескрипторов.
Браузеру легче всего обработать эти файлы, считывая и обрабатывая данные из них (системный вызов read() в сетевом подключении, так же, как и в канале, возвращает доступные в настоящий момент данные и блокирует их только в случае неготовности). Этот подход эффективен, пока все подключения доставляют данные достаточно регулярно.
Если одно из сетевых подключений является медленным, начинают возникать проблемы. Когда браузер снова считывает из этого файла, он перестает работать, в то время как read() блокируется в ожидании поступления данных. Не стоит и упоминать, что подобное поведение не является удобоваримым для пользователя браузера.
Для иллюстрации этих проблем рассмотрим короткую программу, считывающую из двух файлов, p1 и p2. Для ее испытания откройте три сеанса работы с X-терминалом (или воспользуйтесь тремя виртуальными консолями). Создайте каналы под именами p1 и p2 (с помощью команды mknod), затем запустите cat > p1 и cat > p2 в двух терминалах, одновременно запустив mpx-blocks в третьем. После этого набирайте любой текст в каждом окно cat и смотрите, как он появляется. Помните, что две команды cat не будут записывать данные в каналы до конца строки.
1: /* mpx-blocks.с */
2:
3: #include <fcntl.h>
4: #include <stdio.h>
5: #include <unistd.h>
6:
7: int main(void) {
8: int fds[2];
9: char buf[4096];
10: int i;
11: int fd;
12:
13: if ((fds[0] = open("p1", O_RDONLY) ) < 0) {
14: perror("open p1");
15: return 1;
16: }
17:
18: if ( (fds[1] = open("p2", O_RDONLY)) < 0) {
19: perror("open p2");
20: return 1;
21: }
22:
23: fd = 0;
24: while (1) {
25: /* если данные доступны, прочитать и отобразить их */
26: i = read (fds[fd], buf, sizeof (buf) - 1);
27: if (i < 0) {
28: perror("read");
29: return 1;
30: } else if (!i) {
31: printf("канал закрытn");
32: return 0;
33: }
34:
35: buf[i] = ' ';
36: printf ("чтение: %s", buf);
37:
38: /* читать из другого файлового дескриптора */
39: fd = (fd + 1) % 2;
40: }
41: }
Хотя программа mpx-blocks может считывать одновременно из обоих каналов, это не является особо эффективным. Она считывает из каждого канала по очереди. После запуска программа читает из первого файла, пока в нем доступны данные, второй файл игнорируется вплоть до возврата из read() для первого файла. Как только произошел возврат, первый файл игнорируется вплоть до чтения данных из второго файла. Этот метод не поддерживает гладкое мультиплексирование данных. На рис. 13.1 показана программа mpx-blocks во время выполнения.
Рис. 13.1. Примеры запуска мультиплексной передачи
13.1.1. Неблокируемый ввод-вывод
Как упоминалось в главе 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() никогда не входит в состояние тайм-аута.