Именованные каналы (FIFOs - First In First Out)
Пост на тему Именованные каналы (FIFOs - First In First Out)
Введение
Система Linux IPC (Inter-process communication) предоставляет средства для взаимодействия процессов между собой.
В распоряжении программистов есть несколько методов IPC:
- полудуплексные каналы UNIX
- FIFO (именованные каналы)
- Очереди сообщений в стиле SYSV
- Множества семафоров в стиле SYSV
- Разделяемые сегменты памяти в стиле SYSV
- Сетевые сокеты (в стиле Berkeley) (не охватывается этой статьей)
- Полнодуплексные каналы (каналы потоков) (не охватывается этой статьей)
Если эти возможности эффективно используются, то они обеспечивают солидную базу для поддержания идеологии клиент/сервер в любой UNIX-системе, включая Linux.
Основные понятия
Именованные каналы во многом работают так же, как и обычные каналы, но все же имеют несколько заметных отличий.
Именованные каналы существуют в виде специального файла устройства в файловой системе.
Процессы различного происхождения могут разделять данные через такой канал.
Именованный канал остается в файловой системе для дальнейшего использования и после того, как весь ввод/вывод сделан.
Создание FIFO
Есть несколько способов создания именованного канала. Первые два могут быть осуществлены непосредственно из shell-а.
mknod MYFIFO p
mkfifo a=rw MYFIFO
Эти две команды выполняют идентичные операции, за одним исключением. Команда mkfifo предоставляет возможность для изменения прав доступа к файлу FIFO непосредственно после создания. При использовании mknod будет необходим вызов команды chmod.
Файлы FIFO могут быть быстро идентифицированы в физической файловой системе посредством индикатора “p”, представленного здесь в длинном листинге директории.
$ ls -1 MYFIFO
^prw-r--r-- 1 root root 0 Dec 14 22:15 MYFIFO| ...
Также заметьте, что вертикальный разделитель располагается непосредственно после имени файла. Другая веская причина запустить
Linux, eh?
Чтобы создать FIFO на Си, мы можем прибегнуть к использованию системного вызова mknod():
LIBRARY FUNCTION: mknod();
PROTOTYPE: int mknod( char *pathname, mode_t mode, dev_t dev );
RETURNS: 0 в случае успеха,
-1 в случае ошибки:
errno = EFAULT (ошибочно указан путь)
EACCESS (нет прав)
ENAMETOOLONG (слишком длинный путь)
ENOENT (ошибочно указан путь)
ENOTDIR (ошибочно указан путь)
(остальные смотрите в man page для mknod)
NOTES: Создает узел файловой системы (файл, файл устройства или
FIFO)
Оставим более детальное обсуждение mknod()-а man page, а сейчас давайте рассмотрим простой пример создания FIFO на Си:
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
В данном случае файл “/tmp/MYFIFO” создан как FIFO-файл. Требуемые права - это “0666”, хотя они находятся под влиянием установки umask, как например:
final_umask = requested_permissions & ~original_umask ...
Общая хитрость - использовать системный вызов umask() для того, чтобы временно устранить значение umask-а:
umask(0);
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
Кроме того, третий аргумент mknod()-а игнорируется, в противном случае мы создаем файл устройства. В этом случае он должен отметить верхнее и нижнее числа файла устройства.
Операции FIFO
Операции ввода/вывода FIFO, по существу, такие же, как для обычных каналов, за одним исключением. Чтобы физически открыть проход к каналу, должен быть использован системный вызов “open” или библиотечная функция. С полудуплексными каналами это невозможно, поскольку канал находится в ядре, а не в физической файловой системе. В нашем примере мы будем трактовать канал как поток, открывая его fopen()-ом и закрывая fclose()-ом.
Рассмотрим простой сервер-процесс:
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: fifoserver.c
****************************************************************************
#include
#include #include
#include
#include
#define FIFO_FILE "MYFIFO"
int main(void)
{
FILE *fp;
char readbuf[80];
/* Создаем FIFO, если он еще не существует */
umask(0);
mknod(FIFO_FILE, S_IFIFO|0666, 0);
while(1)
{
fp = fopen(FIFO_FILE, "r");
fgets(readbuf, 80, fp);
printf("Received string: %s\n", readbuf);
fclose(fp);
}
return(0);
}
Поскольку FIFO блокирует по умолчанию, запустим сервер фоном после того, как его откомпилировали:
$ fifoserver&
Скоро мы обсудим действие блокирования, но сначала рассмотрим следующего простого клиента для нашего сервера:
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: fifoclient.c
****************************************************************************
#include
#include
#define FIFO_FILE "MYFIFO"int main(int argc, char *argv[])
{
FILE *fp;
if ( argc != 2 ) {
printf("USAGE: fifoclient [string]\n");
exit(1);
}
fputs(argv[1], fp);
fclose(fp);
return(0);
}
Действие блокирования над FIFO
Если FIFO открыт для чтения, процесс его блокирует до тех пор, пока какой-нибудь другой процесс не откроет FIFO для записи. Аналогично для обратной ситуации. Если такое поведение нежелательно, то может быть использован флаг O_NONBLOCK в системном вызове open(), чтобы отменить действие блокирования.
В примере с нашим простым сервером мы только засунули его в фон и позволили там осуществлять блокирование. Альтернативой могло бы быть перепрыгивание на другую виртуальную консоль и запуск клиента, переключение туда и обратно, чтобы увидеть результат.