Linux中的管道是什么? 频道重定向如何工作?
您可能已多次使用cmd0语法| cmd1 | cmd2类似于您的终端。
您可能还知道什么是管道重定向,它用于将一个命令的输出重定向为下一个命令的输入。
但是你知道下面是什么吗? 频道重定向实际上如何工作?
不用担心,因为今天我们对Unix管道进行了神秘化处理,因此下次您与那些花哨的竖条纹约会时,您就知道确切的情况了。
注意。 我们在某些地方使用了术语Unix,因为管道的概念(与Linux中的许多其他事物一样)来自Unix。
Linux中的频道:一般概念
这是关于“ Unix中的管道是什么?”的各处信息:
- Unix管道是一种IPC(进程间通信)机制,可将一个程序的输出重定向到另一个程序的输入。
这是每个人都给出的一般性解释。 我们想更深入。 让我们以一种更技术性的方式通过删除抽象来改写上一行:
- Unix管道是一种IPC(进程间通信)机制,该机制采用stdout程序并将其通过缓冲区发送到另一个stdin程序。
好多了。 删除抽象使其更清晰,更准确。 您可以查看下图以了解管道的工作原理。
管道命令最简单的示例之一是将某些命令输出管道传输到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中的频道类型
Linux上有两种管道:
- 未命名的频道,也称为匿名频道。
- 命名管道
无标题频道
顾名思义,无标题频道没有名称。 每当您使用|字符时,它们就会由Unix shell动态创建。
人们谈论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
这两个命令同时完成了它们的执行。
这是常规文件和命名管道之间的根本区别之一。 在其他进程读取之前,不会将任何内容写入管道。
为什么使用命名管道? 这是为什么要使用命名管道的列表
命名管道不占用磁盘空间
如果您使用du -s管道,则会看到它不占用空间。 这是因为命名管道就像端点,用于从内存缓冲区读取和写入内存缓冲区。 写入命名管道的任何内容实际上都存储在临时内存缓冲区中,当从另一个进程执行读取操作时,该缓冲区将被刷新。
减少I / O
由于写入命名管道意味着将数据存储在内存缓冲区中,因此大文件操作会大大减少磁盘I / O。
两个截然不同的过程之间的联系
可以使用命名管道从另一个进程立即高效地获取事件的输出。 由于读取和写入同时发生,因此延迟几乎为零。
了解下层渠道 [для опытных пользователей и разработчиков]
下一节将更深入地讨论管道与实际的实现。 本部分要求您有基本的了解:
- C程序如何工作
- 什么是系统调用
- 什么是流程
- 什么是文件描述符
我们将不详细介绍示例。 我们只在谈论“渠道”。 对于大多数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("В сообщении от ребенка: %sn", 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的有符号整数数组。
这是因为管道只不过是代表两个文件描述符的两个无符号整数的数组。 一种写作,一种阅读。 它们都指向缓冲区在内存中的位置,通常为1MB。
在这里,我们将变量命名为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管道的全部内容。