Учебники

Межпроцессное взаимодействие — трубы

Труба является средством связи между двумя или более взаимосвязанными или взаимосвязанными процессами. Это может быть либо один процесс, либо связь между дочерним и родительским процессами. Связь также может быть многоуровневой, такой как связь между родителем, дочерним элементом и внуком и т. Д. Связь обеспечивается одним процессом, записывающим данные в канал, а другим считывающим данные из канала. Чтобы выполнить системный вызов pipe, создайте два файла: один для записи в файл, а другой для чтения из файла.

Механизм трубы можно увидеть в режиме реального времени, например, заполнить водой трубу в каком-нибудь контейнере, скажем, ведре, и кто-то, кто заберет его, скажем, с кружкой. Процесс заполнения — это не что иное, как запись в канал, а процесс чтения — не что иное, как извлечение из канала. Это подразумевает, что один выход (вода) является входом для другого (ведро).

Труба с одним

#include<unistd.h>

int pipe(int pipedes[2]);

Этот системный вызов создаст канал для односторонней связи, т. Е. Он создает два дескриптора, первый из которых подключен для чтения из канала, а другой — для записи в канал.

Дескриптор pipedes [0] предназначен для чтения, а pipedes [1] — для записи. Все, что записано в pipedes [1], может быть прочитано из pipedes [0].

Этот вызов вернет ноль в случае успеха и -1 в случае сбоя. Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Несмотря на то, что основные операции для файла — чтение и запись, важно открыть файл перед выполнением операций и закрыть файл после завершения необходимых операций. Обычно по умолчанию для каждого процесса открываются 3 дескриптора, которые используются для ввода (стандартный ввод — stdin), вывода (стандартный вывод — stdout) и ошибки (стандартная ошибка — stderr) с файловыми дескрипторами 0, 1 и 2 соответственно.

Этот системный вызов вернул бы файловый дескриптор, используемый для дальнейших файловых операций чтения / записи / поиска (lseek). Обычно файловые дескрипторы начинаются с 3 и увеличиваются на одно число при увеличении количества открытых файлов.

Аргументами, передаваемыми системному вызову open, являются pathname (относительный или абсолютный путь), флаги, указывающие цель открытия файла (скажем, открытия для чтения, O_RDONLY, для записи, O_WRONLY, для чтения и записи, O_RDWR, для добавления в существующий файл O_APPEND, для создания файла, если он не существует с O_CREAT и т. Д.) И требуемый режим, обеспечивающий права на чтение / запись / выполнение для пользователя или владельца / группы / других. Режим может быть упомянут с символами.

Чтение — 4, Запись — 2 и Выполнение — 1.

Например: Восьмеричное значение (начинается с 0), 0764 означает, что владелец имеет права на чтение, запись и выполнение, группа имеет права на чтение и запись, другая имеет права на чтение. Это также может быть представлено как S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, что подразумевает или операцию 0700 | 0040 | 0020 | 0004 → 0764.

Этот системный вызов в случае успеха возвращает новый идентификатор файлового дескриптора и -1 в случае ошибки. Причину ошибки можно определить с помощью переменной errno или функции perror ().

#include<unistd.h>

int close(int fd)

Вышеупомянутый системный вызов закрывает уже открытый дескриптор файла. Это означает, что файл больше не используется, а связанные с ним ресурсы могут быть использованы любым другим процессом. Этот системный вызов возвращает ноль в случае успеха и -1 в случае ошибки. Причину ошибки можно определить с помощью переменной errno или функции perror ().

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

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

Идентификатор дескриптора файла должен идентифицировать соответствующий файл, который возвращается после вызова системного вызова open () или pipe (). Файл должен быть открыт перед чтением из файла. Он автоматически открывается в случае вызова pipe () системным вызовом.

Этот вызов вернет число прочитанных байтов (или ноль в случае встречи с концом файла) в случае успеха и -1 в случае сбоя. Возвращаемые байты могут быть меньше количества запрошенных байтов, на случай, если данные недоступны или файл закрыт. Правильный номер ошибки устанавливается в случае сбоя.

Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

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

Идентификатор дескриптора файла должен идентифицировать соответствующий файл, который возвращается после вызова системного вызова open () или pipe ().

Файл должен быть открыт перед записью в файл. Он автоматически открывается в случае вызова pipe () системным вызовом.

Этот вызов вернет количество записанных байтов (или ноль, если ничего не написано) в случае успеха и -1 в случае сбоя. Правильный номер ошибки устанавливается в случае сбоя.

Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

Примеры программ

Ниже приведены примеры программ.

Пример программы 1 — Программа для записи и чтения двух сообщений с использованием канала.

Алгоритм

Шаг 1 — Создать трубу.

Шаг 2 — Отправить сообщение на канал.

Шаг 3 — Получить сообщение из канала и записать его в стандартный вывод.

Шаг 4 — Отправить еще одно сообщение на канал.

Шаг 5 — Получить сообщение из канала и записать его в стандартный вывод.

Примечание. Получение сообщений также можно выполнить после отправки всех сообщений.

Исходный код: simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

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

Шаги выполнения

компиляция

gcc -o simplepipe simplepipe.c

Исполнение / выход

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Пример программы 2 — Программа для записи и чтения двух сообщений через канал с использованием родительского и дочернего процессов.

Алгоритм

Шаг 1 — Создать трубу.

Шаг 2 — Создайте дочерний процесс.

Шаг 3 — Родительский процесс пишет в канал.

Шаг 4 — Дочерний процесс получает сообщение из канала и записывает его в стандартный вывод.

Шаг 5 — Повторите шаг 3 и шаг 4 еще раз.

Исходный код: pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

Шаги выполнения

компиляция

gcc pipewithprocesses.c –o pipewithprocesses

выполнение

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

Двусторонняя связь с использованием труб

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

Ниже приведены шаги для достижения двусторонней связи —

Шаг 1 — Создайте две трубы. Сначала родитель должен писать, а ребенок читать, скажем, как pipe1. Во-вторых, ребенок должен писать, а родитель — читать, скажем, как pipe2.

Шаг 2 — Создайте дочерний процесс.

Шаг 3 — Закройте нежелательные концы, так как для каждого сеанса связи необходим только один конец.

Шаг 4 — Закройте нежелательные концы в родительском процессе, прочитайте конец pipe1 и запишите конец pipe2.

Шаг 5 — Закройте нежелательные концы в дочернем процессе, запишите конец pipe1 и прочитайте конец pipe2.

Шаг 6 — Выполните общение по мере необходимости.

Труба с двумя

Примеры программ

Пример программы 1 — Достижение двусторонней связи с использованием каналов.

Алгоритм

Шаг 1 — Создайте pipe1 для родительского процесса для записи и дочернего процесса для чтения.

Шаг 2 — Создайте pipe2 для дочернего процесса для записи и родительского процесса для чтения.

Шаг 3 — Закройте нежелательные концы трубы с родительской и дочерней стороны.

Шаг 4 — Родительский процесс для написания сообщения и дочерний процесс для чтения и отображения на экране.

Шаг 5 — Дочерний процесс для записи сообщения и родительский процесс для чтения и отображения на экране.

Исходный код: twowayspipe.c