每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。
管道
管道的linux命令:ps auxf | grep mysql
上面命令行里面的竖线就是一个管道,它的功能是将前一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。
管道可以分为匿名管道和命名管道。
上述,‘ | ’就是一个匿名管道。命名管道也被叫做FIFO,因为数据是先进先出的传输方式。
在使用命名管道前,先需要通过mkfifo命令来创建,并且指定管道名字:
mkfifo myPipe
myPipe就是这个管道的名称 ,基于Linux一切皆文件的理念,所以管道也是以文件的方式存在。
接下来,可以往myPipe这个管道写入数据:
$ echo "hello" > myPipe // 将数据写进管道
// 停住了 ...
操作了之后,你会发现命令执行后就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。
于是,执行另外一个命令来读取这个管道里的数据:
$ cat < myPipe // 读取管道里的数据
hello
可以看到管道里的内容被读取出来了,并打印在了终端上,另外一方面,echo那个命令也正常退出了。
我们可以看出,管道这种通信方式效率低,不适合进程间频繁地交换数据。
当然,它的好处,自然就是简单,同时我们也很容易得知管道里的数据已经被另一个进程读取了。
管道创建的原理是什么?
匿名管道的创建,需要通过这个系统调用:
int pipe(int fd[2])
这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符fd[0],另一个是管道的写入端描述符fd[1]。注意,这个匿名管道是特殊的文件,只存在于内存,不存在于文件系统中。
其实,所谓管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。
怎么样才能使得管道是跨过两个进程的呢?
可以使用fork创建子进程,创建的子进程会复制父进程中的文件描述符,这样就做到了两个进程各有两个fd[0]和fd[1],两个进程就可以通过各自的fd写入和读取同一个管道文件实现跨进程通信了。
管道只能一端写入,另一端读出,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入,也都可以读出。那么,为了避免这种情况,通常的做法是:
(1)父进程关闭读取的fd[0],只保留写入的fd[1];
(2)子进程关闭写入的fd[1],只保留读取的fd[0]。
所以说如果需要双向通信,则应该创建两个管道。
到这里,我们仅仅解析了使用管道进行父进程与子进程之间的通信,但是在我们shell里面并不是这样的。
在shell里面执行 A | B 命令的时候,A进程和B进程都是shell创建出来的子进程,A和B之间不存在父子关系,它俩的父进程都是shell。
所以说,在shell里面通过 | 匿名管道将多个命令连接在一起,实际上也就是创建了多个子进程,那么在我们编写shell脚本时,能使用一个管道搞定的事,就不要多用一个管道,这样可以减少创建子进程的系统开销。
总结
对于匿名管道也就是无名管道,它的通信范围是存在于父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过fork来复制父进程fd文件描述符,来达到通信的目的。
对于命名管道也就是有名管道,它可以在不相关的进程间通信。因为有名管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。
不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据自然也是从内核中读取,同时通信数据都遵循先进先出原则。