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