Канал — поток данных между двумя или несколькими процессами, имеющий интерфейс, аналогичный чтению или записи в файл.
Неименованный канал является средством взаимодействия между связанными процессами - родительским и дочерним. Родительский процесс создает канал при помощи системного вызова:
int pipe(int fd[2]);
возвращаются два файловых дескриптора: fd[0] является дескриптором для чтения из канала, fd[1] - дескриптором для записи в канал.
Процесс - источник записывает данные в канал, а процесс - приемник считывает данные, освобождая его для дальнейшего использования.
Если родительский процесс, создавший канал, порождает несколько дочерних процессов, то все дочерние процессы подключены к другому концу канала. Если, например, родительский процесс выводит данные в канал, то они "достанутся" тому дочернему процессу, который раньше выполнит системный вызов read.
Канал создаётся по запросу и существует только в ходе работы двух процессов, другие процессы в системе не могут обратиться к этому каналу.
- Если нужен двунаправленный обмен данными между процессами, то родительский процесс создает два канала, один из которых используется для передачи данных в одну сторону, а другой - в другую. После получения процессами дескрипторов канала для работы с каналом используются файловые системные вызовы:
int read(int pipe_fd, void *area, int cnt);
int write(int pipe_fd, void *area, int cnt);
Первый аргумент этих вызовов - дескриптор канала, второй - указатель на область памяти, с которой происходит обмен, третий - количество байт. Оба вызова возвращают число переданных байт (или -1 - при ошибке).
Выполнение этих системных вызовов может переводить процесс в состояние ожидания. Это происходит, если процесс пытается читать данные из пустого канала или писать данные в переполненный канал. Процесс выходит из ожидания, когда в канале появляются данные или когда в канале появляется свободное место, соответственно.
- popen, pclose - создание и ликвидация канала между программой и командой
#include <stdio.h>
1- FILE *popen (command, type)
char *command, *type;
Функция popen() запускает внешнюю программу и возвращает вызвавшему ее приложению указатель на структуру FILE, связанный либо со стандартным потоком ввода, либо со стандартным потоком вывода запущенного процесса.
Например: FILE *ptr;
ptr = popen (cmd, "r") или f = popen(argv[1], "r");
В параметре argv[1] функции popen передается не имя файла, а команда на запуск программы или команды оболочки, например, "ls -al". Если вызов popen() был успешен, мы можем считывать данные, выводимые запущенной командой, с помощью обычной функции fread(3): fread(buf, 1, BUF_SIZE, f)
Особенность функции popen() заключается в том, что эта функция не возвращает NULL, даже если переданная ей команда не является корректной. Самый простой способ обнаружить ошибку в этой ситуации - попытаться прочесть данные из потока вывода. Если в потоке вывода нет данных (fread() возвращает значение 0), значит произошла ошибка. Для вывода данных, прочитанных с помощью fread(), на терминал мы используем функцию write() с указанием дескриптора стандартного потока вывода: write(1, buf, len);
Аргументами функции popen являются указатели на цепочки символов с завершающим нулевым байтом. Первая из них, command, содержит команду shell'а, а вторая задает режим ввода/вывода: "r" для чтения и "w" для записи. Функция popen создает канал между вызывающей программой и указанной командой. Возвращаемое значение - это указатель на поток. Если режим равен "w", то можно писать на стандартный ввод команды, выводя в созданный поток. Если режим равен "r", то можно читать со стандартного вывода команды, вводя из созданного потока.
2- int pclose (stream)
FILE *stream;
pclose(f);
Поток, открытый с помощью popen, должен быть закрыт с помощью функции pclose, которая ожидает завершения запущенного процесса выполнения команды и выдает код его завершения.
Поскольку открытые файлы разделяются, команда с режимом "r" может быть использована как входной фильтр, а команда с режимом "w" - как выходной фильтр.
- При завершении использования канала процесс выполняет системный вызов:
int close(int pipe_fd);
Если процесс на одной из сторон канала завершается и закрывает канал, другому процессу посылается специальный сигнал — SIGPIPE.
Неименованные каналы поддерживаются командной оболочкой, обеспечивая передачу информации из одной программы (на стандартное устройство - терминал) в другую (прием информации со стандартного устройства - клавиатуры), это достигается с помощью символа "|" при написании команды: $ ps- sf | grep my_proc.
Пример: Программа запускает другую программу и считывает данные, которые та выводит в свой стандартный поток вывода. Для решения этой задачи мы воспользуемся функциями popen(3) и pclose(3). Программа makelog выполняет команду оболочки, переданную ей в качестве параметра и записывает данные, выводимые этой командой, одновременно на стандартный терминал и в файл log.txt.
#include <stdio.h>
#include <errno.h>
#define BUF_SIZE 0x100
int main(int argc, char * argv[])
{
FILE * f;
FILE * o;
int len;
char buf[BUF_SIZE];
if (argc != 1) //или 2? – проверка на число аргументов
f = popen(argv[1], "r"); //выполнение команды-параметра
if (f == NULL)
{
perror("ошибка:\n");
return -1;
}
o = fopen("log.txt", "w");
while ((len = fread(buf, 1, BUF_SIZE, f)) != 0)
{ write(1, buf, len); //вывод на экран
fwrite(buf, 1, len, o); //вывод в файл
}
pclose(f);
fclose(o);
return 0; }
Например, если скомпилировать программу:
gcc makelog.c -o makelog
а затем скомандовать
makelog "ls -al"
На экране терминала будут распечатаны данные, выводимые командой оболочки ls -al, а в рабочей директории программы makelog будет создан файл log.txt, содержащий те же данные.
Основным недостатком неименованных каналов является невозможность их использования между процессами, не имеющими общего родителя, но этот недостаток был устранен с появлением именованных каналов (named pipes), или каналов FIFO.