Какие процессы могут обмениваться информацией через pipe

от admin

Русские Блоги

Взаимодействие процессов — это обмен данными между двумя процессами. В Linux существует несколько способов взаимодействия процессов. В этой статье мы в основном представляем самый простой метод-канал взаимодействия процессов.

Способы коммуникации процесса

Единственный способ обмена информацией между процессами — передача открытых файлов.

Труба

Канал — это одна из старейших и наиболее простых форм системного IPC, все системы Linux предоставляют такой механизм связи. Однако у конвейера есть два следующих ограничения:

  • Это полудуплексный режим, то есть данные по каналу могут передаваться только в одном направлении. Если требуется двусторонняя связь, между двумя процессами должны быть установлены два канала;
  • Каналы могут использоваться только между двумя процессами с общим предком; —

Механизм реализации трубы

Канал — это буфер, которым управляет ядро, один конец которого подключен к выходу процесса, а другой конец подключен к входу процесса. Буфер конвейера не обязательно должен быть большим, он спроектирован как кольцевая структура данных, когда два процесса завершаются, жизненный цикл конвейера также завершается.

Создание конвейера

Канал создается путем вызова функции конвейера.

  • 1
  • 2
  • 3

Он возвращает два файловых дескриптора по выходному параметру fd,fd [0] открыт для чтения, fd [1] открыт для записи, Выходные данные fd [1] являются входными данными fd [0]. Когда канал создается успешно, функция канала возвращает 0. Если создание не удается, она возвращает -1. Связь между fd [0] и fd [1] показана на рисунке ниже:

Как общаться по трубе

Выше мы установили конвейер в одном процессе, но на самом деле конвейер в одном процессе бесполезен. Обычно процесс сначала вызывает функцию конвейера для создания канала, а затем вызывает функцию fork (). Функция fork изменяет родительский процесс. Соответствующая структура данных наследуется дочернему процессу, так что дочерний процессДескриптор файлаFd [0] и fd [1] в таблице указывают на файл конвейера, на который указывает родительский процесс, так что связь между двумя процессами может быть реализована. Вышеупомянутый процесс выглядит следующим образом:

Связанные правила использования трубопроводной связи

Для конвейера от дочернего процесса к родительскому процессу (запись дочернего процесса, чтение родительского процесса) родительский процесс закрывает fd [1], дочерний процесс закрывает fd [0], когда часть конвейера закрывается (закрывается на основе вышеизложенного Один конец трубы) Работают следующие два правила:

  1. При чтении канала, сегмент записи которого был закрыт, после того, как все данные были прочитаны,чтение возвращает 0(чтение возвращает 0, чтобы указать, что конец файла был прочитан);
    Проверим:

Программа позволяет дочернему процессу записать строку три раза, а затем закрывает дочерний процесс fd [1], то есть закрывает конец записи канала, и не закрывает fd [0] родительского процесса, то есть конец чтения канала.

  1. Если вы пишете в канал, конец чтения которого закрыт, он будетГенерируйте соответствующие сигналыПроцесс записи сегмента завершается. Если сигнал игнорируется или сигнал захватывается и возвращается обработчиком, команда write вернет -1, а errno будет установлено значениеEPIPE;
    Проверим:

Цель кода такова: мы позволяем концу записи (дочернему процессу) всегда записывать строку msg, а конец чтения (родительский процесс) читает три раза, а затем закрывает fd [0] родительского процесса, так что дочерний процесс записывает все время , А родительский процесс не читает. Результаты приведены ниже:

Мы обнаружили, что после того, как родительский процесс закрыл fd [0], дочерний процесс былПрервано ненормально, Из кода выхода и кода сигнала выхода дочернего процесса мы обнаружили, что это сигнал 13 (SIGPIPE) Завершается, поэтому запись в канал, закрытый на конце чтения, не соответствует действительности для PIPE. Операционная система отправит процессу завершения записи после закрытия конца чтенияSIGPIPEВызвать завершение процесса.

  1. Если концы для чтения и записи канала не закрыты, но конец канала для записи больше не записывает данные в канал. В это время, если в конвейере нет данных, процесс чтения будет заблокирован до тех пор, пока в конвейере не появятся данные, прежде чем считывать данные и возвращаться.
  2. Если файловый дескриптор, указывающий на конец чтения канала, не закрыт, а тот, который удерживает конец чтения канала, не читает данные из канала, то есть процесс, записывающий данные в канал. Если канал заполнен, а затем записывает данные в канал, напишите снова Вызывает блокировку процесса до тех пор, пока в канале не останется места, прежде чем он продолжит запись данных в канал и возврат.

емкость трубы

Мы можем пройти* man 7 pipe*; Для запроса пропускной способности трубопроводаpipe_capacity

Написание, компиляция и запуск программы для организации двунаправленной связи между родственными процессами через pipe

Pipe служит для организации однонаправленной или симплексной связи. Если бы в предыдущем примере мы попытались организовать через pipe двустороннюю связь, когда процесс-родитель пишет информацию в pipe, предполагая, что ее получит процесс-ребенок, а затем читает информацию из pip’а, предполагая, что ее записал порожденный процесс, то могла бы возникнуть ситуация, в которой процесс-родитель прочитал бы собственную информацию, а процесс-ребенок не получил бы ничего. Для использования одного pip’а в двух направлениях необходимы специальные средства синхронизации процессов, о которых говорилось на лекциях. Более простой способ организации двунаправленной связи между родственными процессами заключается в использовании двух pipe. Модифицируйте программу из предыдущего примера (раздел «Прогон программы для организации однонаправленной связи между родственными процессами через pipe») для организации такой двусторонней связи, откомпилируйте ее и запустите на исполнение.

Необходимо отметить, что в Solaris реализованы полностью дуплексные pip’ы. В этой системе для обоих файловых дескрипторов, ассоциированных с pip’ом, разрешены и операция чтения, и операция записи. Однако такое поведение не характерно для pip’ов и не является переносимым.

Особенности поведения вызовов read() и write() для pip’а

Системные вызовы read() и write() имеют определенные особенности поведения при работе с pip’ом, связанные с его ограниченным размером, задержками в передаче данных и возможностью блокирования обменивающихся информацией процессов. Организация запрета блокирования этих вызовов для pipe выходит за рамки нашего курса.

Будьте внимательны при написании программ, обменивающихся большими объемами информации через pipe. Помните, что за один раз из pip’а может прочитаться меньше информации, чем вы запрашивали, и за один раз в pipe может записаться меньше информации, чем вам хотелось бы. Проверяйте значения, возвращаемые вызовами!

Одна из особенностей поведения блокирующегося системного вызова read() связана с попыткой чтения из пустого pip’а. Если есть процессы, у которых этот pipe открыт для записи, то системный вызов блокируется и ждет появления информации. Если таких процессов нет, он вернет значение 0 без блокировки процесса. Эта особенность приводит к необходимости закрытия файлового дескриптора, ассоциированного с входным концом pip’a, в процессе, который будет использовать pipe для чтения (close(fd[1]) в процессе-ребенке в программе из раздела «Прогон программы для организации однонаправленной связи между родственными процессами через pipe»). Аналогичной особенностью поведения при отсутствии процессов, у которых pipe открыт для чтения, обладает и системный вызов write(), с чем связана необходимость закрытия файлового дескриптора, ассоциированного с выходным концом pip’a, в процессе, который будет использовать pipe для записи (close(fd[0]) в процессе-родителе в той же программе).

Системные вызовы read и write (продолжение)

Особенности поведения при работе с pipe, FIFO и socket

Системный вызов read

Попытка прочитать меньше байт, чем есть в наличии в канале связи.

Читает требуемое количество байт и возвращает значение, соответствующее прочитанному количеству. Прочитанная информация удаляется из канала связи.

В канале связи находится меньше байт, чем затребовано, но не нулевое количество.

Читает все, что есть в канале связи, и возвращает значение, соответствующее прочитанному количеству. Прочитанная информация удаляется из канала связи.

Попытка читать из канала связи, в котором нет информации. Блокировка вызова разрешена.

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

Читать:
Пример 2 2х2 решение сколько будет

Попытка читать из канала связи, в котором нет информации. Блокировка вызова не разрешена.

Если есть процессы, у которых канал связи открыт для записи, системный вызов возвращает значение -1 и устанавливает переменную errno в значение EAGAIN. Если таких процессов нет, системный вызов возвращает значение 0.

Системный вызов write

Попытка записать в канал связи меньше байт, чем осталось до его заполнения.

Требуемое количество байт помещается в канал связи, возвращается записанное количество байт.

Попытка записать в канал связи больше байт, чем осталось до его заполнения. Блокировка вызова разрешена.

Вызов блокируется до тех пор, пока все данные не будут помещены в канал связи. Если размер буфера канала связи меньше, чем передаваемое количество информации, то вызов тем самым будет ждать, пока часть информации не будет считана из канала связи. Возвращается записанное количество байт.

Попытка записать в канал связи больше байт, чем осталось до его заполнения, но меньше, чем размер буфера канала связи. Блокировка вызова запрещена.

Системный вызов возвращает значение -1 и устанавливает переменную errno в значение EAGAIN.

В канале связи есть место. Попытка записать в канал связи больше байт, чем осталось до его заполнения, и больше, чем размер буфера канала связи. Блокировка вызова запрещена.

Записывается столько байт, сколько осталось до заполнения канала. Системный вызов возвращает количество записанных байт.

Попытка записи в канал связи, в котором нет места. Блокировка вызова не разрешена.

Системный вызов возвращает значение -1 и устанавливает переменную errno в значение EAGAIN.

Попытка записи в канал связи, из которого некому больше читать, или полное закрытие канала на чтение во время блокировки системного вызова.

Если вызов был заблокирован, то он разблокируется. Процесс получает сигнал SIGPIPE. Если этот сигнал обрабатывается пользователем, то системный вызов вернет значение -1 и установит переменную errno в значение EPIPE.

Необходимо отметить дополнительную особенность системного вызова write при работе с pip’ами и FIFO. Запись информации, размер которой не превышает размер буфера, должна осуществляться атомарно – одним подряд лежащим куском. Этим объясняется ряд блокировок и ошибок в предыдущем перечне.

Задача повышенной сложности: определите размер pipe для вашей операционной системы.

Каналы (pipe,fifo)

Каналы — неименованные (pipe) и именованные (fifo) — это средство передачи данных между процессами.

Можно представить себе канал как небольшой кольцевой буфер в ядре операционной системы. С точки зрения процессов, канал выглядит как пара открытых файловых дескрипторов – один на чтение и один на запись (можно больше, но неудобно). Мы можем писать в канал до тех пор пока есть место в буфере, если место в буфере кончится – процесс будет заблокирован на записи. Можем читать из канала пока есть данные в буфере, если данных нет – процесс будет заблокирован на чтении. Если закрыть дескриптор отвечающий за запись, то попытка чтения покажет конец файла. Если закрыть дескриптор отвечающий за чтение, то попытка записи приведет к доставке сигнала SIGPIPE и ошибке EPIPE.

При использовании канала в программировании на языке shell

блокировки чтения/записи обеспечивают синхронизацию скорости выполнения двух программ и их одновременное завершение.

Понятия позиции чтения/записи для каналов не существует, поэтому запись всегда производится в хвост буфера, а чтение с головы.

Для архитектуры i386 размер буфера, связанного с каналом устанавливают кратным размеру страницы (4096 байт). В Linux в версиях до 2.6.11 использовалась одна страница (4 КБ), после — 16 страниц (65 КБ), с возможностью изменения через fcntl . POSIX определяет значение PIPE_BUF, задающего максимальный размер атомарной записи. В Linux PIPE_BUF равен 4096 байт.

Неименованные каналы

Неименованный канал создается вызовом pipe, который заносит в массив int [2] два дескриптора открытых файлов. fd[0] – открыт на чтение, fd[1] – на запись (вспомните STDIN == 0, STDOUT == 1). Канал уничтожается, когда будут закрыты все файловые дескрипторы ссылающиеся на него.

В рамках одного процесса pipe смысла не имеет, передать информацию о нем в произвольный процесс нельзя (имени нет, а номера файловых дескрипторов в каждом процессе свои). Единственный способ использовать pipe – унаследовать дескрипторы при вызове fork (и последующем exec ). После вызова fork канал окажется открытым на чтение и запись в родительском и дочернем процессе. Т.е. теперь на него будут ссылаться 4 дескриптора. Теперь надо определиться с направлением передачи данных – если надо передавать данные от родителя к потомку, то родитель закрывает дескриптор на чтение, а потомок — дескриптор на запись.

Оставлять оба дескриптора незакрытыми плохо по двум причинам:

Родитель после записи не может узнать считал ли дочерний процесс данные, а если считал то сколько. Соответственно, если родитель попробует читать из pipe, то, вполне вероятно, он считает часть собственных данных, которые станут недоступными для потомка.

Если один из процессов завершился или закрыл свои дескрипторы, то второй этого не заметит, так как pipe на его стороне по-прежнему открыт на чтение и на запись.

Если надо организовать двунаправленную передачу данных, то можно создать два pipe.

Именованные каналы

Именованный канал FIFO доступен как объект в файловой системе. При этом, до открытия объекта FIFO на чтение, собственно коммуникационного объекта не создаётся. После открытия открытия объекта FIFO в одном процессе на чтение, а в другом на запись, возникает ситуация полностью эквивалентная использованию неименованного канала.

Объект FIFO в файловой системе создаётся вызовом функции int mkfifo(const char *pathname, mode_t mode); ,

Основное отличие между pipe и FIFO — то, что pipe могут совместно использовать только процессы находящиеся в отношении родительский-дочерний, а FIFO может использовать любая пара процессов.

Правила обмена через канал

При обмене данными через канал существуют два особых случая:

  1. Попытка чтения при отсутствии писателей
  2. Попытка записи при отсутствии читателей

Первый случай интерпретируется как конец файла и вызов read вернёт 0. Второй случай не имеет аналогов при работе с обычными файлами, а потому вызывает доставку сигнала SIGPIPE. Программы-фильтры, которые работают с STDOUT по сигналу SIGPIPE обычно завершают работу. Если программа расcчитана на работу с каналами, то для корректной обработки этой ситуации она должна явно изменить стандартный обработчик SIGPIPE, установив его в игнорирование сигнала или переназначив на свою функцию.

Для защиты от этих особых случаев при открытии именованного канала FIFO вызов open() на чтение или на запись блокируется, пока кто-нибудь не откроет канал с другой стороны. Если открывать FIFO с опцией O_NONBLOCK, то одиночное открытие на чтение пройдёт успешно, а попытка открыть на запись FIFO без читателей вернёт ошибку ENXIO (устройство не существует). Открытие FIFO одновременно на чтение и на запись в POSIX не определено. В Linux такой вариант сработает и в блокирующем и в неблокирующем режимах.

Передача данных через файл¶

При помощи конвейера можно передавать вывод одной команды на ввод другой. В примере показан конвейер команд: fortune, cowsay, sed, shuf.

Именованный канал¶

В программировании именованный канал или именованный конвейер (англ. named pipe) — один из методов межпроцессного взаимодействия, расширение понятия конвейера в Unix и подобных ОС.

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

Похожие статьи