Shell除了有输入/输出重定向的功能,还可以将两个或者多个命令(程序或者进程)连接到一起,把一个命令的输出作为下一个命令的输入,以这种方式连接的两个或者多个命令就形成了一种特殊的文件称为管道(pipe)。Linux中管道分为两种:匿名管道 和 命名管道
一、匿名管道
我们常见的管道符|
它是一个匿名管道,只能用于具有亲缘关系的进程之间,这是它与命名管道的最大区别。 命名管道叫named pipe或者FIFO(先进先出),可以用命令mkfifo
创建。
1、匿名管道的命令语法格式
Linux匿名管道的具体语法格式如下:
command1 | command2 [| comand3.....]
当在两个命令之间设置管道时,管道符|
左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入,而第二个命令是从标准输入读取,那么这两个命令就可以形成一个管道。大部分的 Linux 命令都可以用来形成管道。
2、示例:
(1)寻找mysql进程
(2)获取docker中所有的容器
(3)获取当前目录下文件的个数
等等,管道还有好多用处,这里仅仅给出一些示例。
二、命名管道 named piped
我们常用的|
是一个匿名管道,它只能用于具有亲缘关系的进程之间,当需要在爱多个线程之间使用管道的时候就需要命名管道。
1、命名管道的创建
命名管道的语法格式如下:
#创建一个管道
mkfifo 管道名
2、示例:
使用mkfifo命令就可以创建一个有名字的管道,前文提到过,管道实际也是一种文件,只不过它具有先进先出以及管道中的数据被读取以后就消失的特性(类似于队列)。
(1)使用命令管道实现多终端交互
在/dev/pts/0终端上创建了一个名为/tmp/fifo3的管道,并向管道总中输入数据,在/dev/pts/1终端获取数据
- 在/dev/pts/0终端创建管道,并向管道输入数据
- 在/dev/pts/1终端通过管道接收到数据
值的注意的是,在/dev/pts/1终端终端接收数据的时候,当/dev/pts/0没有给管道输入数据的时候,/dev/pts/1终端会一直等待,当数据从管道读取之后再次尝试读取发现之前的数据没有了。这也进一步验证了管道的两个特性:FIFO和数据被读取以后就消失。
(2)使用命名管道和FD实现对Shell并发的控制
创建一个多线程的网络检查脚本,要求最多开启5个线程同时工作
#!/bin/bash
# ping test muti thread
thread=5
tmp_fifofile=/tmp/$$.fifo
mkfifo $tmp_fifofile
exec 8<> $tmp_fifofile
rm -rf $tmp_fifofile
for i in `seq $thread`; do
#向管道中扔5个1,在这里没有实质性作用,向管道中扔任何东西都可以,但是要扔5个
echo "1" >&8
done
for i in {1..254}; do
#从管道中拿一个数据“1”,如果没有拿到就会阻塞在这里(相当于获取锁)
read -u 8
{
ip=192.168.92.$i
ping -c1 ${ip} &>dev/null
if [ "$?" -eq 0 ]; then
echo "$ip is up"
else
echo "$ip is down"
fi
#当一个线程执行完任务之后向管道中在添加一个数据(相当于释放锁)
echo "1" >&8
}&
done
wait
#释放8这个文件句柄
exec 8>&-
echo -e "\nComplete!"
执行结果【部分】:
三、管道的实现机制
管道技术是Linux操作系统中由来已久的一种进程间通信机制。所有的管道技术,无论是半双工的匿名管道,还是命名管道,它们都是利用FIFO排队模型来指挥进程间的通信。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:
- 限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
-
读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
-
从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。
不过在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。