Возможно, вы слишком много раз использовали синтаксис cmd0 | cmd1 | cmd2, подобный вашему терминалу.
Вы, вероятно, также знаете, что такое перенаправление канала, которое используется для перенаправления вывода одной команды в качестве ввода для следующей команды.
Но знаете ли вы, что под ним? Как на самом деле работает перенаправление каналов?
Не беспокойтесь, потому что сегодня мы демистифицируем каналы Unix, чтобы в следующий раз, когда вы пойдете на свидание с этими причудливыми вертикальными полосами, вы точно знали, что происходит.
Примечание. Мы использовали термин Unix в некоторых местах, потому что концепция каналов (как и многие другие вещи в Linux) происходит от Unix.
Вот что вы повсюду увидите относительно «что такое каналы в Unix?»:
Это общее объяснение, которое дают все. Мы хотим проникнуть глубже. Давайте перефразируем предыдущую строку более техническим языком, убрав абстракции:
Намного лучше. Удаление абстракции сделало его намного чище и точнее. Вы можете посмотреть на следующую схему, чтобы понять, как работает pipe.
Один из простейших примеров команды pipe – передать некоторый вывод команды команде grep для поиска определенной строки.
Очень важно понять, что конвейер передает команду stdout другой команде stdin, но не как аргумент команды. Мы объясним это на примере.
Если вы используете команду cat без аргументов, по умолчанию будет выполняться чтение из stdin. Вот пример:
$ cat Блог AndreyEx о Linux ^D Блог AndreyEx о Linux
Здесь мы использовали cat без передачи файлов, поэтому по умолчанию stdin. Затем мы написали строку, а затем использовал Ctrl + d, чтобы сообщить, что закончили писать (Ctrl + d подразумевает EOF или конец файла). Как только мы закончили писать, cat прочитал stdin и написал эту строку в stdout.
Теперь рассмотрим следующую команду:
echo hey | cat
Вторая команда НЕ эквивалентна cat hey. Здесь stdout”hey” переносится в буфер и передает stdin в cat. Поскольку аргументов командной строки не было, cat по умолчанию выбрал stdin для чтения, а что-то уже было в stdin, поэтому команда cat приняла это и напечатала stdout.
Фактически, мы создали файл с именем hey и поместили в него некоторый контент.
В Linux есть два типа каналов:
Без названия каналы, как следует из названия, не имеют имени. Они создаются на лету вашей оболочкой Unix всякий раз, когда вы используете символ |.
Когда люди говорят о каналах в Linux, они обычно говорят об этом. Они полезны, потому что вам, как конечному пользователю, не нужно ничего отслеживать. Ваша оболочка справится со всем этим.
Этот немного отличается. Именованные каналы действительно присутствуют в файловой системе. Они существуют как обычный файл. Вы можете создать именованный файл, используя следующую команду:
mkfifo pipe
Это создаст файл с именем «pipe». Выполните следующую команду:
$ ls -l pipe prw-r--r-- 1 ausername ausername 0 Jul 12 09:51 pipe
Обратите внимание на «p» в начале, это означает, что файл является каналом. Теперь воспользуемся этим каналом.
Как мы говорили ранее, конвейер пересылает вывод одной команды на вход другой. Это как курьерская служба, вы даете посылку доставить с одного адреса, а они доставляют по другому. Итак, первый шаг – предоставить пакет, то есть предоставить каналу что-то.
echo hey > pipe
Вы заметите, что echo еще не вернули нам терминал. Откройте новый терминал и попробуйте прочитать из этого файла.
cat pipe
Обе эти команды завершили свое выполнение одновременно.
Это одно из фундаментальных различий между обычным файлом и именованным каналом. В канал ничего не записывается, пока какой-либо другой процесс не прочитает его.
Зачем использовать именованные каналы? Вот список того, почему вы хотите использовать именованные каналы
Если вы выполните a du -s pipe, вы увидите, что он не занимает места. Это потому, что именованные каналы похожи на конечные точки для чтения и записи из буфера памяти и в него. Все, что записывается в именованный канал, фактически сохраняется во временном буфере памяти, который сбрасывается, когда операция чтения выполняется из другого процесса.
Поскольку запись в именованный канал означает сохранение данных в буфере памяти, операции с большими файлами значительно сокращают дисковый ввод-вывод.
Выходные данные события можно мгновенно и очень эффективно получить из другого процесса с помощью именованных каналов. Поскольку чтение и запись происходят одновременно, время ожидания практически равно нулю.
В следующем разделе речь идет о каналах на более глубоком уровне с фактическими реализациями. Этот раздел требует от вас базового понимания:
Мы не будем вдаваться в подробности примеров. Речь идет только о «каналах». Для большинства пользователей Linux этот раздел не нужен.
В конце мы предоставили для компиляции образец файла – Makefile. Имейте в виду, что эти программы предназначены только для иллюстративных целей, поэтому, если вы видите, что ошибки обрабатываются некорректно.
Рассмотрим следующий пример программы:
// pipe.c #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <stdlib.h> #include <errno.h> extern int errno; int main(){ signed int fd[2]; pid_t pid; static char input[50]; static char buf[50]; pipe(fd); if((pid=fork())==-1){ int err=errno; perror("ответвление не сработало"); exit(err); } if(pid){ close(fd[1]); read(fd[0], buf, 50); printf("В сообщении от ребенка: %s\n", buf); } else { close(fd[0]); printf("Введите сообщение от родителя: "); for(int i=0; (input[i]=getchar())!=EOF && input[i]!='\n' && i<49; i++); write(fd[1], input, 50); exit(0); } return 0; }
В строке 16 мы создали безымянный канал, используя функцию pipe(). Первое, что следует заметить, это то, что мы передали массив целых чисел со знаком длиной 2.
Это потому, что канал – это не что иное, как массив из двух целых чисел без знака, представляющих два файловых дескриптора. Один для письма, один для чтения. И оба они указывают на расположение буфера в памяти, которое обычно составляет 1 МБ.
Здесь мы назвали переменную fd. fd [0] – дескриптор входного файла, fd [1] – дескриптор выходного файла. В этой программе один процесс записывает строку в файловый дескриптор fd [1], а другой процесс читает из файлового дескриптора fd [0].
Именованный канал ничем не отличается. С именованным каналом вместо двух файловых дескрипторов вы получаете имя файла, которое можно открыть из любого процесса и работать с ним, как с любым другим файлом, учитывая при этом характеристики канала.
Вот пример программы, которая делает то же самое, что и предыдущая программа, но вместо анонимного канала создает именованный канал.
// fifo.c #include <unistd.h> #include <sys/types.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> extern int errno; #define fifo "npipe" int main(void){ pid_t pid; static char input[50]; static char buf[50]; signed int fd; mknod(fifo, S_IFIFO|0700, 0); if((pid=fork())<0){ int err=errno; perror("ответвление не сработало"); exit(err); } if(pid){ fd=open(fifo, O_RDONLY); read(fd, buf, 50); close(fd); printf("Вывод есть : %s", buf); remove(fifo); exit(0); } else { fd=open(fifo, O_WRONLY); for(int i=0; (input[i]=getchar())!=EOF && input[i]!='\n' && i<49; i++); write(fd, input, strlen(input)); close(fd); exit(0); } return 0; }
Здесь мы использовали системный вызов mknod для создания именованного канала. Как вы можете видеть, хотя мы удалили канал по завершении, вы вполне можете оставить его и легко использовать для связи между различными программами, просто открыв и записав в файл с именем «npipe» в моем случае.
Вам также не придется создавать два разных канала для двусторонней связи, как в случае с анонимными каналами.
Вот образец Makefile, как и было обещано. Поместите его в тот же каталог, что и предыдущие программы, с именами «pipe.c» и «fifo.c» соответственно.
CFLAGS?=-Wall -g -O2 -Werror CC?=clang build: $(CC) $(CFLAGS) -o pipe pipe.c $(CC) $(CFLAGS) -o fifo fifo.c clean: rm -rf pipe fifo
Вот так. Это действительно все, что есть в каналах Unix.