Что такое fifo linux
С помощью труб могут общаться только родственные друг другу процессы, полученные с помощью fork (). Именованные каналы FIFO позволяют обмениваться данными с абсолютно «чужим» процессом.
С точки зрения ядра ОС FIFO является одним из вариантов реализации трубы. Системный вызов mkfifo () предоставляет процессу именованную трубу в виде объекта файловой системы. Как и для любого другого объекта, необходимо предоставлять процессам права доступа в FIFO, чтобы определить, кто может писать, и кто может читать данные. Несколько процессов могут записывать или читать FIFO одновременно. Режим работы с FIFO — полудуплексный, т.е. процессы могут общаться в одном из направлений. Типичное применение FIFO — разработка приложений «клиент — сервер».
Синтаксис функции для создания FIFO следующий:
int mkfifo(const char *fifoname, mode_t mode); При возникновении ошибки функция возвращает -1, в противном случае 0. В качестве первого параметра указывается путь, где будет располагаться FIFO. Второй параметр определяет режим работы с FIFO. Пример использования приведен ниже:
int fd_fifo; /*дескриптор FIFO*/
char buffer[]=»Текстовая строка для fifo\n»;
/*Если файл с таким именем существует, удалим его*/
if((mkfifo(«/tmp/fifo0001.1», O_RDWR)) == -1)
fprintf(stderr, «Невозможно создать fifo\n»);
/*Открываем fifo для чтения и записи*/
if((fd_fifo=open(«/tmp/fifo0001.1», O_RDWR)) == — 1)
fprintf(stderr, «Невозможно открыть fifo\n»);
if(read(fd_fifo, &buf, sizeof(buf)) == -1)
fprintf(stderr, «Невозможно прочесть из FIFO\n»);
printf(«Прочитано из FIFO : %s\n»,buf);
Если в системе отсутствует функция mkfifo (), можно воспользоваться общей функцией для создания файла: int mknod(char *pathname, int mode, int dev);
Здесь pathname указывает обычное имя каталога и имя FIFO. Режим обозначается константой S_IFIFO из заголовочного файла . Здесь же определяются права доступа. Параметр dev не нужен. Пример вызова mknod :
if(mknod(«/tmp/fifo0001.1», S_IFIFO | S_IRUSR | S_IWUSR,
Флаг O_NONBLOCK может использоваться только при доступе для чтения. При попытке открыть FIFO с O_NONBLOCK для записи возникает ошибка открытия. Если FIFO закрыть для записи через close или fclose , это значит, что для чтения в FIFO помещается EOF.
Если несколько процессов пишут в один и тот же FIFO, необходимо обратить внимание на то, чтобы сразу не записывалось больше, чем PIPE_BUF байтов. Это необходимо, чтобы данные не смешивались друг с другом. Установить пределы записи можно следующей программой: #include
/*Создаем новый FIFO*/
if((mkfifo(«fifo0001», O_RDWR)) == -1)
fprintf(stderr, «Невозможно создать FIFO\n»);
printf(«Можно записать в FIFO сразу %ld байтов\n»,
printf(«Одновременно можно открыть %ld FIFO \n»,
При попытке записи в FIFO, который не открыт в данный момент для чтения ни одним процессом, генерируется сигнал SIGPIPE .
В следующем примере организуется обработчик сигнала SIGPIPE , создается FIFO, процесс-потомок записывает данные в этот FIFO, а родитель читает их оттуда. Пример иллюстрирует простое приложение типа «клиент — сервер»:
static volatile sig_atomic_t sflag;
static sigset_t signal_new, signal_old, signal_leer;
static void sigfunc(int sig_nr)
fprintf(stderr, «SIGPIPE вызывает завершение
if(signal(SIGPIPE, sigfunc) == SIG_ERR)
fprintf(stderr, «Невозможно получить сигнал
/*Удаляем все сигналы из множества сигналов*/
/*Устанавливаем signal_new и сохраняем его*/
/* теперь маской сигналов будет signal_old*/
&signal_old) 0) /*Родитель читает из FIFO*/
if (( r_fifo=open(«/tmp/fifo0001.1», O_RDONLY))
Next: Блокировка файлов Up: Трубы (pipes) Previous: Функция popen() Contents 2004-06-22
Источник
Linux.yaroslavl.ru
Канал — механизм для связи между процессами; данные, записывающиеся в канал одним процессом могут читаться другим процессом. Данные обрабатываются в порядке ‘первым пришел’ — ‘первым ушел’ (FIFO). Канал не имеет никакого имени; он создан для одного использования, и оба конца должны быть унаследованы от одиночного процесса, который создал канал.
FIFO специальный файл является подобным каналу, но вместо анонимного, временного соединения, FIFO имеет имя или имена подобно любому другому файлу. Процесс открывает FIFO по имени, чтобы связаться через него.
Канал или FIFO должен быть открыт с обоих концов одновременно. Если Вы читаете из канала или файла FIFO, в который никто ничего не пишет (возможно потому что, они все закрыли файл, или вышли), то чтение возвращает конец файла. Запись в канал или FIFO, который не имеет процесс считывания, обрабатывается как условие ошибки; это генерирует сигнал SIGPIPE, и сбои с кодом ошибки EPIPE, если сигнал обработан или блокируется.
Ни каналы ни FIFO специальные файлы не позволяют позиционирование файла. И чтение и запись происходит последовательно; чтение из начала файла и запись в конец.
Примитив для создания канала — функция pipe. Она создает оба, и чтения и записи концы канала. Это не очень полезно для одиночного процесса, использовать канал, чтобы разговаривать с собой. В типичном использовании, процесс создает канал только прежде, чем он ветвится на один или более дочерних процессов (см. Раздел 23.4 [Создание Процесса]). Канал используется для связи или между родителем или дочерними процессами, или между двумя процессами братьями.
Функция pipe объявлена в заглавном файле ‘unistd.h’.
При успехе pipe возвращает значение 0. При отказе, -1. Следующие errno условия ошибки определены для этой функции: EMFILE
процесс имеет слишком много файлов открытыми. ENFILE
имеются слишком много открытых файлов во всей системе. См. Раздел 2.2 [Коды Ошибки], для получения более подробной информации о ENFILE. Вот пример простой программы, которая создает канал. Эта программа использует функцию ветвления (см. Раздел 23.4 [Создание Процесса]) чтобы создать дочерний процесс. Родительский процесс напишет данные, которые читается дочерним процессом.
Общее использование каналов должно послать данные к или получать данные из программы, выполняемой как подпроцесс.
Один из способов выполнения этого — использовать комбинацию pipe (чтобы создать канал), fork (чтобы создать подпроцесс), dup2 (чтобы вынудить подпроцесс использовать pipe как стандартный ввод или канал вывода), и exec (чтобы выполнить новую программу). Или, Вы можете использовать popen и pclose.
Преимущество использования popen и pclose — в том, что интерфейс является намного более простым и более удобным для использования. Но они не предлагают так много гибкости, как использование функций низкого уровня непосредственно.
Однако, вместо того, чтобы ждать завершения команды, она создает канал к подпроцессу и возвращает поток, который соответствует этому каналу.
Если Вы определяете аргумент режима ‘r’, Вы можете читать из потока, чтобы отыскать данные из канала стандартного вывода подпроцесса. Подпроцесс наследует канал стандартного ввода из родительского процесса.
Аналогично, если Вы определяете аргумент режима ‘w’, Вы можете писать в поток, чтобы посылать данные на канал стандартного ввода подпроцесса. Подпроцесс наследует канал стандартного вывода из родительского процесса.
В случае ошибки, popen возвращает пустой указатель. Это может случаться, если канал или поток не может быть создан, если подпроцесс не может быть раздвоен, или если программа не может быть выполнена.
Вот пример, показывающий, как использовать popen и pclose, чтобы фильтровать вывод через другую программу.
FIFO специальный файл подобен каналу, за исключением того, что он создан различным способом. Вместо анонимного канала связи, FIFO специальный файл введен в файловую систему, вызовом mkfifo.
Если Вы создали FIFO специальный файл таким образом,, любой процесс может открывать его для чтения или записи, таким же образом как обычный файл. Однако, он должен быть открыт в оба конца одновременно прежде, чем Вы можете делать любой ввод или вывод на нем. Открытие FIFO для чтения обычно блокируется, пока некоторый другой процесс не открывает тот же самый FIFO для записи, и наоборот.
Mkfifo функция объявлена в заглавном файле ‘sys/stat.h’.
Нормальное, успешное возвращаемое значение из mkfifo — 0. В случае ошибки возвращается -1. В дополнение к обычным синтаксическим ошибкам имени файла следующие errno условия ошибки определены для этой функции: EEXIST
именованный файл уже существует. ENOSPC
каталог или файловая система не может быть расширен. EROFS
каталог, который содержал бы файл постоянно находится в файловой системе только для чтения.
Чтение или запись данных в канал мгновенны, если размер данных меньше чем PIPE_BUF. Это означает что передача данных кажется мгновенной, в этом случае ничто в системе не может наблюдать состояние, в котором он является частично полным. Быстрый ввод — вывод не может начинаться сразу же (может требоваться ждать пространство буфера или для данных), но если только он начинается, то он заканчивается немедленно.
Чтение или запись большего количества данных может не быть быстрым; например, выходные данные из других процессов, совместно использующих дескриптор могут быть разбиты на части.
См. Раздел 27.6 [Ограничения для Файлов], для уточнения информации относительно параметра PIPE_BUF.
Источник
Знакомство с межпроцессным взаимодействием на Linux
Межпроцессное взаимодействие (Inter-process communication (IPC)) — это набор методов для обмена данными между потоками процессов. Процессы могут быть запущены как на одном и том же компьютере, так и на разных, соединенных сетью. IPC бывают нескольких типов: «сигнал», «сокет», «семафор», «файл», «сообщение»…
Отступление: данная статья является учебной и расчитана на людей, только еще вступающих на путь системного программирования. Ее главный замысел — познакомиться с различными способами взаимодействия между процессами на POSIX-совместимой ОС.
Именованный канал
Для передачи сообщений можно использовать механизмы сокетов, каналов, D-bus и другие технологии. Про сокеты на каждом углу можно почитать, а про D-bus отдельную статью написать. Поэтому я решил остановиться на малоозвученных технологиях отвечающих стандартам POSIX и привести рабочие примеры.
Рассмотрим передачу сообщений по именованным каналам. Схематично передача выглядит так:
Для создания именованных каналов будем использовать функцию, mkfifo():
Примечание: mode используется в сочетании с текущим значением umask следующим образом: (mode &
umask). Результатом этой операции и будет новое значение umask для создаваемого нами файла. По этой причине мы используем 0777 (S_IRWXO | S_IRWXG | S_IRWXU), чтобы не затирать ни один бит текущей маски.
Как только файл создан, любой процесс может открыть этот файл для чтения или записи также, как открывает обычный файл. Однако, для корректного использования файла, необходимо открыть его одновременно двумя процессами/потоками, одним для получение данных (чтение файла), другим на передачу (запись в файл).
В случае успешного создания FIFO файла, mkfifo() возвращает 0 (нуль). В случае каких либо ошибок, функция возвращает -1 и выставляет код ошибки в переменную errno.
Типичные ошибки, которые могут возникнуть во время создания канала:
- EACCES — нет прав на запуск (execute) в одной из директорий в пути pathname
- EEXIST — файл pathname уже существует, даже если файл — символическая ссылка
- ENOENT — не существует какой-либо директории, упомянутой в pathname, либо является битой ссылкой
- ENOSPC — нет места для создания нового файла
- ENOTDIR — одна из директорий, упомянутых в pathname, на самом деле не является таковой
- EROFS — попытка создать FIFO файл на файловой системе «только-на-чтение»
Чтение и запись в созданный файл производится с помощью функций read() и write().
Пример
mkfifo.c
Мы открываем файл только для чтения (O_RDONLY). И могли бы использовать O_NONBLOCK модификатор, предназначенный специально для FIFO файлов, чтобы не ждать когда с другой стороны файл откроют для записи. Но в приведенном коде такой способ неудобен.
Компилируем программу, затем запускаем ее:
В соседнем терминальном окне выполняем:
В результате мы увидим следующий вывод от программы:
Разделяемая память
Следующий тип межпроцессного взаимодействия — разделяемая память (shared memory). Схематично изобразим ее как некую именованную область в памяти, к которой обращаются одновременно два процесса:
Для выделения разделяемой памяти будем использовать POSIX функцию shm_open():
Функция возвращает файловый дескриптор, который связан с объектом памяти. Этот дескриптор в дальнейшем можно использовать другими функциями (к примеру, mmap() или mprotect()).
Целостность объекта памяти сохраняется, включая все данные связанные с ним, до тех пор пока объект не отсоединен/удален (shm_unlink()). Это означает, что любой процесс может получить доступ к нашему объекту памяти (если он знает его имя) до тех пор, пока явно в одном из процессов мы не вызовем shm_unlink().
Переменная oflag является побитовым «ИЛИ» следующих флагов:
- O_RDONLY — открыть только с правами на чтение
- O_RDWR — открыть с правами на чтение и запись
- O_CREAT — если объект уже существует, то от флага никакого эффекта. Иначе, объект создается и для него выставляются права доступа в соответствии с mode.
- O_EXCL — установка этого флага в сочетании с O_CREATE приведет к возврату функцией shm_open ошибки, если сегмент общей памяти уже существует.
Как задается значение параметра mode подробно описано в предыдущем параграфе «передача сообщений».
После создания общего объекта памяти, мы задаем размер разделяемой памяти вызовом ftruncate(). На входе у функции файловый дескриптор нашего объекта и необходимый нам размер.
Пример
Следующий код демонстрирует создание, изменение и удаление разделяемой памяти. Так же показывается как после создания разделяемой памяти, программа выходит, но при следующем же запуске мы можем получить к ней доступ, пока не выполнен shm_unlink().
shm_open.c
После создания объекта памяти мы установили нужный нам размер shared memory вызовом ftruncate(). Затем мы получили доступ к разделяемой памяти при помощи mmap(). (Вообще говоря, даже с помощью самого вызова mmap() можно создать разделяемую память. Но отличие вызова shm_open() в том, что память будет оставаться выделенной до момента удаления или перезагрузки компьютера.)
Компилировать код на этот раз нужно с опцией -lrt:
Смотрим что получилось:
Аргумент «create» в нашей программе мы используем как для создания разделенной памяти, так и для изменения ее содержимого.
Зная имя объекта памяти, мы можем менять содержимое разделяемой памяти. Но стоит нам вызвать shm_unlink(), как память перестает быть нам доступна и shm_open() без параметра O_CREATE возвращает ошибку «No such file or directory».
Семафор
Семафор — самый часто употребляемый метод для синхронизации потоков и для контролирования одновременного доступа множеством потоков/процессов к общей памяти (к примеру, глобальной переменной). Взаимодействие между процессами в случае с семафорами заключается в том, что процессы работают с одним и тем же набором данных и корректируют свое поведение в зависимости от этих данных.
Есть два типа семафоров:
- семафор со счетчиком (counting semaphore), определяющий лимит ресурсов для процессов, получающих доступ к ним
- бинарный семафор (binary semaphore), имеющий два состояния «0» или «1» (чаще: «занят» или «не занят»)
Рассмотрим оба типа семафоров.
Семафор со счетчиком
Смысл семафора со счетчиком в том, чтобы дать доступ к какому-то ресурсу только определенному количеству процессов. Остальные будут ждать в очереди, когда ресурс освободится.
Итак, для реализации семафоров будем использовать POSIX функцию sem_open():
В функцию для создания семафора мы передаем имя семафора, построенное по определенным правилам и управляющие флаги. Таким образом у нас получится именованный семафор.
Имя семафора строится следующим образом: в начале идет символ «/» (косая черта), а следом латинские символы. Символ «косая черта» при этом больше не должен применяться. Длина имени семафора может быть вплоть до 251 знака.
Если нам необходимо создать семафор, то передается управляющий флаг O_CREATE. Чтобы начать использовать уже существующий семафор, то oflag равняется нулю. Если вместе с флагом O_CREATE передать флаг O_EXCL, то функция sem_open() вернет ошибку, в случае если семафор с указанным именем уже существует.
Параметр mode задает права доступа таким же образом, как это объяснено в предыдущих главах. А переменной value инициализируется начальное значение семафора. Оба параметра mode и value игнорируются в случае, когда семафор с указанным именем уже существует, а sem_open() вызван вместе с флагом O_CREATE.
Для быстрого открытия существующего семафора используем конструкцию:
, где указываются только имя семафора и управляющий флаг.
Пример семафора со счетчиком
Рассмотрим пример использования семафора для синхронизации процессов. В нашем примере один процесс увеличивает значение семафора и ждет, когда второй сбросит его, чтобы продолжить дальнейшее выполнение.
sem_open.c
В одной консоли запускаем:
В соседней консоли запускаем:
Бинарный семафор
Вместо бинарного семафора, для которого так же используется функция sem_open, я рассмотрю гораздо чаще употребляемый семафор, называемый «мьютекс» (mutex).
Мьютекс по существу является тем же самым, чем является бинарный семафор (т.е. семафор с двумя состояниями: «занят» и «не занят»). Но термин «mutex» чаще используется чтобы описать схему, которая предохраняет два процесса от одновременного использования общих данных/переменных. В то время как термин «бинарный семафор» чаще употребляется для описания конструкции, которая ограничивает доступ к одному ресурсу. То есть бинарный семафор используют там, где один процесс «занимает» семафор, а другой его «освобождает». В то время как мьютекс освобождается тем же процессом/потоком, который занял его.
Без мьютекса не обойтись в написании, к примеру базы данных, к которой доступ могут иметь множество клиентов.
Для использования мьютекса необходимо вызвать функцию pthread_mutex_init():
Функция инициализирует мьютекс (перемнную mutex) аттрибутом mutexattr. Если mutexattr равен NULL, то мьютекс инициализируется значением по умолчанию. В случае успешного выполнения функции (код возрата 0), мьютекс считается инициализированным и «свободным».
Типичные ошибки, которые могут возникнуть:
- EAGAIN — недостаточно необходимых ресурсов (кроме памяти) для инициализации мьютекса
- ENOMEM — недостаточно памяти
- EPERM — нет прав для выполнения операции
- EBUSY — попытка инициализировать мьютекс, который уже был инициализирован, но не унечтожен
- EINVAL — значение mutexattr не валидно
Чтобы занять или освободить мьютекс, используем функции:
Функция pthread_mutex_lock(), если mutex еще не занят, то занимает его, становится его обладателем и сразу же выходит. Если мьютекс занят, то блокирует дальнейшее выполнение процесса и ждет освобождения мьютекса.
Функция pthread_mutex_trylock() идентична по поведению функции pthread_mutex_lock(), с одним исключением — она не блокирует процесс, если mutex занят, а возвращает EBUSY код.
Фунция pthread_mutex_unlock() освобождает занятый мьютекс.
Коды возврата для pthread_mutex_lock():
- EINVAL — mutex неправильно инициализирован
- EDEADLK — мьютекс уже занят текущим процессом
Коды возврата для pthread_mutex_trylock():
- EBUSY — мьютекс уже занят
- EINVAL — мьютекс неправильно инициализирован
Коды возврата для pthread_mutex_unlock():
- EINVAL — мьютекс неправильно инициализирован
- EPERM — вызывающий процесс не является обладателем мьютекса
Пример mutex
mutex.c
Данный пример демонстрирует совместный доступ двух потоков к общей переменной. Один поток (первый поток) в автоматическом режиме постоянно увеличивает переменную counter на единицу, при этом занимая эту переменную на целую секунду. Этот первый поток дает второму доступ к переменной count только на 10 миллисекунд, затем снова занимает ее на секунду. Во втором потоке предлагается ввести новое значение для переменной с терминала.
Если бы мы не использовали технологию «мьютекс», то какое значение было бы в глобальной переменной, при одновременном доступе двух потоков, нам не известно. Так же во время запуска становится очевидна разница между pthread_mutex_lock() и pthread_mutex_trylock().
Компилировать код нужно с дополнительным параметром -lpthread:
Запускаем и меняем значение переменной просто вводя новое значение в терминальном окне:
Вместо заключения
В следующих статьях я хочу рассмотреть технологии d-bus и RPC. Если есть интерес, дайте знать.
Спасибо.
UPD: Обновил 3-ю главу про семафоры. Добавил подглаву про мьютекс.
Источник