Скачиваний:
90
Добавлен:
12.05.2015
Размер:
913.92 Кб
Скачать

13.5. Именованные каналы (fifo)

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

Аббревиатура FIFO расшифровывается как “first in, first out” – “первым вошел, первым вышел”, то есть эти каналы работают как очереди. Именованные каналы в Unix функционируют подобно неименованным – они позволяют передавать данные только в одну сторону. Однако в отличие от программных каналов каждому каналу FIFO сопоставляется полное имя в файловой системе, что позволяет двум неродственным процессам обратиться к одному и тому же FIFO.

FIFO создается функцией mkfifo.

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo (const char *pathname, mode_t mode);

/* возвращает 0 при успешном выполнении, -1 – при возникновении ошибок */

Здесь pathname – обычное для Unix полное имя файла, которое и будет именем FIFO.

Аргумент mode указывает битовую маску разрешений доступа к файлу, аналогично второму аргументу команды open. Для задания разрешений на доступ к FIFO можно использовать шесть констант из табл. 4.12 (чтение и/или запись для индивидуального владельца, группового владельца и остальных пользователей).

Функция mkfifo действует как open, вызванная с аргументом O_CREAT | O_EXCL. Это означает, что создается новый канал FIFO или возвращается ошибка EEXIST, в случае, если канал с указанным именем уже существует. Если не требуется создавать новый канал, вызывайте open вместо mkfifo. Для открытия существующего канала или создания нового в том случае, если его еще не существует, вызовите mkfifo, проверьте, не возвращена ли ошибка EEXIST, и если такое случится, вызовите функцию open.

Команда mkfifo также создает канал FIFO. Ею можно пользоваться в сценариях интерпретатора или из командной строки.

После создания канал FIFO должен быть открыт на чтение или запись с помощью либо функции open, либо одной из стандартных функций открытия файлов из библиотеки ввода-вывода (например, fopen). FIFO может быть открыт либо только на чтение, либо только на запись. Нельзя открывать канал на чтение и запись, поскольку именованные каналы могут быть только односторонними.

При записи в программный канал или канал FIFO вызовом write данные всегда добавляются к уже имеющимся, а вызов read считывает данные, помещенные в программный канал или FIFO первыми. При вызове функции lseek для программного канала или FIFO будет возвращена ошибка ESPIPE.

Пример. Переделаем программу, приведенную в листинге 13.1, таким образом, чтобы использовать два канала FIFO вместо двух программных каналов. Функции client и server останутся прежними; отличия появятся только в функции main, новый текст которой приведен в листинге 13.3.

Листинг 13.3. Приложение клиент-сервер, которое создает и использует два FIFO

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/stat.h>

#include <sys/types.h>

#define FIFO1 "/tmp/fifo.1"

#define FIFO2 "/tmp/fifo.2"

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

void client(int, int), server(int, int);

void delete_fifos(void)

{

unlink(FIFO1);

unlink(FIFO2);

}

int main(int argc, char **argv)

{

int readfd, writefd, status;

pid_t childpid;

/* создание двух FIFO; если они уже существуют - OK */

if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))

{

fprintf(stderr, "Невозможно создать %s: %s\n", FIFO1, strerror(errno));

exit(1);

}

if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST))

{

unlink(FIFO1);

fprintf(stderr, "Невозможно создать %s: %s\n", FIFO2, strerror(errno));

exit(1);

}

if ((childpid = fork()) == -1)

{

unlink(FIFO1);

unlink(FIFO2);

fprintf(stderr, "Ошибка вызова функции fork: %s\n", strerror(errno));

exit(1);

}

if (childpid == 0) /* дочерний процесс */

{

if ((readfd = open(FIFO1, O_RDONLY, 0)) < 0)

{

fprintf(stderr, "Потомок: невозможно открыть %s для чтения: %s\n",

FIFO1, strerror(errno));

exit(1);

}

if ((writefd = open(FIFO2, O_WRONLY, 0)) < 0)

{

fprintf(stderr, "Потомок: невозможно открыть %s для записи: %s\n",

FIFO2, strerror(errno));

exit(1);

}

server(readfd, writefd);

exit(0);

}

/* родительский процесс */

if (atexit(delete_fifos))

{

unlink(FIFO1);

unlink(FIFO2);

fprintf(stderr, "Родитель: невозможно зарегистрировать delete_fifos: %s\n",

strerror(errno));

exit(1);

}

if ((writefd = open(FIFO1, O_WRONLY, 0)) < 0)

{

fprintf(stderr, "Родитель: невозможно открыть %s для записи: %s\n",

FIFO1, strerror(errno));

exit(1);

}

if ((readfd = open(FIFO2, O_RDONLY, 0)) < 0)

{

fprintf(stderr, "Родитель: невозможно открыть %s для чтения: %s\n",

FIFO2, strerror(errno));

exit(1);

}

client(readfd, writefd);

if (waitpid(childpid, &status, 0) == -1)

{ /* ожидание завершения дочернего процесса */

fprintf(stderr, "Ошибка вызова функции waitpid: %s\n", strerror(errno));

exit(1);

}

if (status == 0)

fprintf(stderr, "Копирование файла успешно завершено\n");

close(readfd);

close(writefd);

exit(0);

}

В файловой системе в каталоге /tmp создается два канала. Если какой-либо из них уже существует – ничего страшного. Константа FILE_MODEопределена таким образом, что владельцу файла разрешается чтение и запись в него, а группе и прочим пользователям – только чтение. Эти биты разрешений накладываются на маску процесса, определяющую режим доступа к вновь создаваемым файлам (раздел 4.8).

Далее происходит вызов fork, дочерний процесс вызывает функциюserver, а родительский процесс вызывает функциюclient(листинг 13.1). Перед вызовом этих функций родительский процесс открывает первый канал на запись, а второй – на чтение, в то время как дочерний процесс открывает первый канал на чтение, а второй – на запись. Картина аналогична примеру с каналами и иллюстрируется рис.13.7.

рис. 13.7

Изменения по сравнению с примером, в котором использовались программные каналы, следующие:

  • для создания и открытия программного канала требуется только один вызов – pipe. Для создания и открытия FIFO требуется вызов mkfifo и последующий вызов open;

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

Польза от лишнего вызова, необходимого для создания FIFO, следующая: канал FIFO получает имя в файловой системе, что позволяет одному процессу создать такой канал, а другому – открыть его, даже если последний не является родственным первому. С программными каналами это неосуществимо.

В программах, некорректно использующих каналы FIFO, могут возникать неочевидные проблемы. Рассмотрим, например, листинг13.3: если поменять порядок двух вызовов функцииopen, в породившем процессе, программа перестанет работать. Причина в том, что чтение из FIFO блокирует процесс, если канал еще не открыт на запись каким-либо другим процессом. Действительно, если мы меняем порядок вызововopenв породившем процессе, и породивший, и порожденный процессы открывают канал на чтение, притом что на запись он еще не открыт, так что оба процесса блокируются. Такая ситуация называется блокированием, или зависанием (deadlock). Она будет рассмотрена подробно в следующем разделе.

Пример. Влистинге 13.3клиент и сервер все еще являются родственными процессами. Переделаем этот пример так, чтобы родство между ними отсутствовало. Влистинге 13.4приведен текст программы-сервера, а влистинге 13.5– текст программы-клиента. В этом примере сервер создает и удаляет каналы FIFO.

Листинг 13.4. Функция main программы-сервера

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/stat.h>

#include <sys/types.h>

#define FIFO1 "/tmp/fifo.1"

#define FIFO2 "/tmp/fifo.2"

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

void server(int, int);

void delete_fifos(void)

{

unlink(FIFO1);

unlink(FIFO2);

}

int main(int argc, char **argv)

{

int readfd, writefd;

/* создание двух FIFO; если они уже существуют - OK */

if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))

{

fprintf(stderr, "Сервер: невозможно создать %s: %s\n",

FIFO1, strerror(errno));

exit(1);

}

if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST))

{

unlink(FIFO1);

fprintf(stderr, "Сервер: невозможно создать %s: %s\n",

FIFO2, strerror(errno));

exit(1);

}

if (atexit(delete_fifos))

{

unlink(FIFO1);

unlink(FIFO2);

fprintf(stderr, "Сервер: невозможно зарегистрировать delete_fifos: %s\n",

strerror(errno));

exit(1);

}

if ((readfd = open(FIFO1, O_RDONLY, 0)) < 0)

{

fprintf(stderr, "Сервер: невозможно открыть %s для чтения: %s\n",

FIFO1, strerror(errno));

exit(1);

}

if ((writefd = open(FIFO2, O_WRONLY, 0)) < 0)

{

fprintf(stderr, "Сервер: невозможно открыть %s для записи: %s\n",

FIFO2, strerror(errno));

exit(1);

}

server(readfd, writefd);

exit(0);

}

Листинг 13.5. Функция main программы-клиента

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#define FIFO1 "/tmp/fifo.1"

#define FIFO2 "/tmp/fifo.2"

void client(int, int);

int main(int argc, char **argv)

{

int readfd, writefd;

if ((writefd = open(FIFO1, O_WRONLY, 0)) < 0)

{

fprintf(stderr, "Клиент: невозможно открыть %s для записи: %s\n",

FIFO1, strerror(errno));

exit(1);

}

if ((readfd = open(FIFO2, O_RDONLY, 0)) < 0)

{

fprintf(stderr, "Клиент: невозможно открыть %s для чтения: %s\n",

FIFO2, strerror(errno));

exit(1);

}

client(readfd, writefd);

close(readfd);

close(writefd);

exit(0);

}

Для корректной работы клиента и сервера сначала необходимо запустить программу-сервер в фоновом режиме, а затем запустить программу-клиента.

Соседние файлы в папке Chapter.4