》进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的。如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通信呢?内核提供一条通道不成问题,问题是如何标识这条通道才能使各进程都可以访问它?文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道。
》mkfifo命令创建一个FIFO特殊文件,是一个命名管道(可以用来做进程之间通信的桥梁)
》管道也是一种文件,一般是linux中的一个页大小,4k,管道数据一旦被读取就没了。(管道大小和管道的buff大小理解有错误,请看 pipe-buffer-size-is-4k-or-64k? )
》管道是单方向
FIFO和UNIX Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。
FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道,如 prw-rw-r-- 1 simba simba 0 May 21 10:13 p2,文件类型标识为p表示FIFO,文件大小为0。各进程可以打开这个文件进行read/write,实际上是在读写内核通道(根本原因在于这个file结构体所指向的read、write函数和常规文件不一样),这样就实现了进程间通信。UNIX Domain Socket和FIFO的原理类似,也需要一个特殊的socket文件来标识内核中的通道,例如/run目录下有很多系统服务的socket文件:
srw-rw-rw- 1 root root 0 May 21 09:59 acpid.socket
文件类型s表示socket,这些文件在磁盘上也没有数据块。
一、命名管道(FIFO)
匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
二、命名管道和匿名管道
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open。
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,这些工作完成之后,它们具有相同的语义。
The only difference between pipes and FIFOs is the manner in which they are created and opened. Once these tasks have been accomplished, I/O on pipes and FIFOs has exactly the same semantics.
三、命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
需要注意的是打开的文件描述符默认是阻塞的,大家可以写两个很简单的小程序测试一下,主要也就一条语句
int fd = open("p2", O_WRONLY); 假设p2是命名管道文件,把打开标志换成 O_RDONLY 就是另一个程序了,可以先运行RD程序,此时会阻塞,再在另一个窗口运行WR程序,此时两个程序都会从open返回成功。非阻塞时也不难测试,open时增加标志位就可以了。
使用命名管道
下面均是再OSX系统实验。
创建管道
liuzhizhi@lzz-rmbp|pipe # mkfifo log.pipe
1
查看管道
liuzhizhi@lzz-rmbp|pipe # ls -l
total 0
prw-r--r-- 1 liuzhizhi staff 0 10 14 22:21 log.pipe
1
2
3
仔细看下最前面的部分 开头是 p,表示这是一个管道
使用管道
一对一的情况
#第一个窗口
# ls -l > log.pipe
#第二个窗口
# cat < log.pipe
total 0
prw-r--r-- 1 liuzhizhi staff 0 10 14 22:25 log.pipe
1
2
3
4
5
6
7
这里管道并不是水桶,当我们执行 ls -l > log.pipe 的时候,这里就开始阻塞了,只有当我们再第二个窗口执行 cat < log.pipe 的时候,第一个窗口的ls命令才执行完。
所以这里的管道非常形象,它只是负责传输,虽然它也能存点水,但只有实用的时候才能存住一点水,如果只有一端倒水或者只有一端接水,都是无法正常运行的。
一个输入,多个接收的情况
#窗口1
cat < log.pipe
#窗口2
cat < log.pipe
#窗口3
echo "lzz" > log.pipe
1
2
3
4
5
6
7
8
执行的结果是窗口2输出了 lzz,第一个窗没有输出,说明接收方无论多个只能获取一份水,只要被一个接收方接受到,另外一个就无法获取了(所以他不是订阅型的),通过持续往管道输入可以看到,接收方应该是基于抢占式的。
多个输入,一个接受的情况
这里写了一个python程序来做为持续的输入
#coding:utf-8
#logpipe.py
import time
import sys
word = sys.argv[1]
while 1:
print >> sys.stdout, word
time.sleep(1)
1
2
3
4
5
6
7
8
9
10
测试
#窗口1
cat < log.pipe
#窗口2
python logpipe.py win1 > log.pipe
#窗口3
python logpipe.py win2 > log.pipe
#输出
win2
win2
win2
win2
win1
win1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
其实这种情况,比较好理解,有个下水道大家都往里面倒水,最后汇总到一个口流出来,由于有缓冲区,所以并不会马上读取到结果。
小结
当然我们这里是用bash 操作这个命名管道,其实基本所有的语言都可以使用这个工具,例如bash命令写入管道,python读取,lua读取。