Java named pipe linux

named pipes в Unix

Я давно читал про них, ещё когда учился основам юникс, но как-то не было нужды с ними работать. И, вот, нужда возникла.

Некая программа (допустим, foo) не умеет писать вывод в stdout, только в файл. Даже «-» в качестве имени файла всего лишь создаёт файл с названием «-» [большинство умных программ под unix знают, что одиночный минус вместо имени файла означает вывод в stdout]. Аналогично она отвергает и /dev/stdout.

Другая же программа, обрабатывающая результаты первой, допустим, bar, читает из stdin и пишет в stdout. (если быть точным, первое — это трейсер специального вида, дающий двоичный дамп, а второе — конвертор, печатающий их же в человекочитаемом виде).

Нужно их объединить в конвеер.

Некрасивый вариант — использование обычного файла. Записал, прочитал.

Есть куда более красивый вариант — это именованные пайпы. Так как у пайпа есть имя, мы можем передать его как файл первой программе, а потом передать содержимое другой.

Выглядит это так:

Пайп в файловой системе выглядит так:

(акцент на букву ‘p’ первым символом).

Как это работает? Фактически, fifo aka named pipe — это «обыкновенный pipe», примерно такой, который кодируется палкой «|». Однако, у него есть ИМЯ и это имя можно указывать всюду, где требуется файл.

Программа, которая пишет в именованный пайп, ведёт себя с ним как с файлом. Т.е. пишет себе и пишет. Программа, которая читает — аналогично. Читает себе и читает. Чтение идёт в том порядке, как была осуществлена запись (FIFO — first in first out). Положения относительно пайпа (слева/справа) определяются тем, кто читает, а кто пишет.

Важная же особенность пайпа — способность тормознуть читающую/пищущую программу, если буфер пуст/переполнен.

Рассмотрим на примере чтения. Программа пишет в пайп одну строчку в секунду. Программа чтения читает с максимально возможной скоростью. Программа «вычитывает» всё, что было в буфере, и посылает следующий запрос. Ядро этот запрос задерживает до того момента, пока не появятся данные. Таким образом, можно не париться с синхронизацией — появятся данные, программа-обработчик получит управление обратно из read() и обработает очередную порцию данных.

Источник

Знакомство с межпроцессным взаимодействием на 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».

Семафор

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

Есть два типа семафоров:

  1. семафор со счетчиком (counting semaphore), определяющий лимит ресурсов для процессов, получающих доступ к ним
  2. бинарный семафор (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-ю главу про семафоры. Добавил подглаву про мьютекс.

Источник

What are Named Pipes or FIFO in Linux/Unix systems?

Pipes were meant for communication between related processes. We Cannot use pipes for unrelated process communication. Then to achieve unrelated processes communication, the simple answer is Named Pipes. Even though this works for related processes, it gives no meaning to use the named pipes for related process communication.

Unlike pipe, we can use single named pipe that can be used for two-way communication (communication between the server and the client, plus the client and the server at the same time) as Named Pipe supports bi-directional communication.

Another name for named pipe is FIFO (First-In-First-Out). Let us see the system call (mknod()) to create a named pipe, which is a kind of a special file.

This system call would create a special file or file system node such as ordinary file, device file, or FIFO. The arguments to the system call are pathname, mode and dev. The pathname along with the attributes of mode and device information. The pathname is relative, if the directory is not specified it would be created in the current directory. The mode specified is the mode of file which specifies the file type such as the type of file and the file mode as mentioned in the following tables. The dev field is to specify device information such as major and minor device numbers.

File Type Description File Type Description
S_IFBLK block special S_IFREG Regular file
S_IFCHR character special S_IFDIR Directory
S_IFIFO FIFO special S_IFLNK Symbolic Link
File Mode Description File Mode Description
S_IRWXU Read, write, execute/search by owner S_IWGRP Write permission, group
S_IRUSR Read permission,owler S_IXGRP Execute/search permission, group
S_IWUSR Write permission, owner S_IRWXO Execute/search permission, group
S_IXUSR Execute/search permission, owner S_IROTH Read permission, others
S_IRWXG Read, write, execute/search by group S_IWOTH Write permission, others
S_IRGRP Read permission, group S_IXOTH Execute/search permission, others

File mode can also be represented in octal notation such as 0XYZ, where X represents owner, Y represents group, and Z represents others. The value of X, Y or Z can range from 0 to 7. The values for read, write and execute are 4, 2, 1 respectively. If needed in combination of read, write and execute, then add the values accordingly.

Say, if, 0640, then this means read and write (4 + 2 = 6) for owner, read (4) for group and no permissions (0) for others.

This call would return zero on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

This library function creates a FIFO special file, which is used for named pipe. The arguments to this function is file name and mode. The file name can be either absolute path or relative path. If full path name (or absolute path) is not given, the file would be created in the current folder of the executing process. The file mode information is as described in mknod() system call.

This call would return zero on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.

Let us consider a program of running the server on one terminal and running the client on another terminal. The program would only perform one-way communication. The client accepts the user input and sends the message to the server, the server prints the message on the output. The process is continued until the user enters the string “end”.

Let us understand this with an example −

Step 1 − Create two processes, one is fifoserver and another one is fifoclient.

Step 2 − Server process performs the following −

Creates a named pipe (using system call mknod()) with name “MYFIFO”, if not created.

Opens the named pipe for read only purposes.

Here, created FIFO with permissions of read and write for Owner. Read for Group and no permissions for Others.

Waits infinitely for message from the Client.

If the message received from the client is not “end”, prints the message. If the message is “end”, closes the fifo and ends the process.

Step 3 − Client process performs the following −

1) Opens the named pipe for write only purposes.

2) Accepts the string from the user.

3) Checks, if the user enters “end” or other than “end”. Either way, it sends a message to the server. However, if the string is “end”, this closes the FIFO and also ends the process.

4) Repeats infinitely until the user enters string “end”.

Two-way Communication Using Named Pipes

The communication between pipes are meant to be unidirectional. Pipes were restricted to one-way communication in general and need at least two pipes for two-way communication. Pipes are meant for inter-related processes only. Pipes can’t be used for unrelated processes communication, say, if we want to execute one process from one terminal and another process from another terminal, it is not possible with pipes. Named pipe is meant for communication between two or more unrelated processes and can also have bi-directional communication.

Already, we have seen the one-directional communication between named pipes, i.e., the messages from the client to the server. Now, let us take a look at the bi-directional communication i.e., the client sending message to the server and the server receiving the message and sending back another message to the client using the same named pipe.

Following is an example −

Step 1 − Create two processes, one is fifoserver_twoway and another one is fifoclient_twoway.

Step 2 − Server process performs the following −

Creates a named pipe (using library function mkfifo()) with name “fifo_twoway” in /tmp directory, if not created.

Opens the named pipe for read and write purposes.

Here, created FIFO with permissions of read and write for Owner. Read for Group and no permissions for Others.

Waits infinitely for a message from the client.

If the message received from the client is not “end”, prints the message and reverses the string. The reversed string is sent back to the client. If the message is “end”, closes the fifo and ends the process.

Step 3 − Client process performs the following −

Opens the named pipe for read and write purposes.

Accepts string from the user.

Checks, if the user enters “end” or other than “end”. Either way, it sends a message to the server. However, if the string is “end”, this closes the FIFO and also ends the process.

If the message is sent as not “end”, it waits for the message (reversed string) from the client and prints the reversed string.

Repeats infinitely until the user enters the string “end”.

Источник

Читайте также:  Run openvpn as service linux
Оцените статью