Канал межпроцессного взаимодействия Linux

задняя часть Linux

1. Обзор

1.1 Канал — это поток байтов:

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

1.2 Чтение данных из пайпа:

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

1.3 Трубы однонаправленные:

  • Направление передачи данных в канале одностороннее, один конец канала используется для записи, а другой конец используется для чтения.

1.4 Пропускная способность трубопровода ограничена:

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

1.5 Гарантируется, что записи не более байтов PIPE_BUF являются атомарными:

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

2. Создавайте и используйте пайплайны

#include<unistd.h>
int pipe(int filedes[2]);//return 0 on success,or -1 on error
  • filedes[0] представляет конец канала для чтения;

  • filedes[1] представляет конец канала записи;

  • Используйте системные вызовы read() и write() для выполнения ввода-вывода в каналах.

Обработать файловый дескриптор после создания канала

3. Закройте неиспользуемые файловые дескрипторы каналов.

3.1 Причина, по которой процесс чтения должен закрыть дескриптор записи канала, который он содержит:

read() блокируется до тех пор, пока все дескрипторы записи канала не будут закрыты.

3.2 Процессы записи Причина, по которой дескриптор чтения конвейера удерживается им:

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

3.3 Причины закрытия неиспользуемых файловых дескрипторов каналов:

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

4. Пайпы можно использовать как метод синхронизации процессов

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

   switch (fork()) {
        case -1:
                ERR_EXIT("fork");
        case 0:
                if (close(pfd[0]) == -1)
                    ERR_EXIT("close");
                
                //Child does some work,and lets parent know It‘s done
                
                if(close(pfd[1])==-1)
                    ERR_EXIT("close");

                //child now carries on to do other things...
               
                _exit(EXIT_SUCCESS);
        default:
                break;
    }

    if(close(pfd[1])==-1)
        ERR_EXIT("close");
    //parent may do other work,then synchronizes with children

    if(read(pfd[0],&dummy,1)!=0)//block
        ERR_EXIT("read");
 

5. Общайтесь с командами оболочки через конвейеры: popen()

#include<stdio.h>
FIFE *popen(const char *command,const char *mode);
    //return file stream,or NULL on error
int pclose(FILE *stream);
    //return termination status of child process,or -1 on error

Функция popen() создает канал, затем создает подпроцесс для выполнения оболочки, который, в свою очередь, создает подпроцесс для выполнения командной строки. Параметр режима — это строка, указывающая, должен ли вызывающий процесс считывать данные из канала (режим — r) или записывать данные в канал (режим — w).

Значение mode определяет, подключен ли стандартный вывод выполняемой команды к концу канала записи или его стандартный ввод подключен к концу канала чтения.

вызов процесса fp--(fork(),exec())-->/bin/sh--(fork(),exec())-->command (stdout)--(pipe)-->вызов процесса fp

a)mode is r

Вызов процесса fp--(fork(), exec())-->/bin/sh--(fork(), exec())-->command (stdin)

call process fp--(pipe)-->command stdin

b) mode is w