进程间通信
下面的内容主要来自小林coding。
每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。
Linux 内核提供了不少进程间通信的机制,我们来一起瞧瞧有哪些?
管道
如果你学过 Linux 命令,那你肯定很熟悉「|
」这个竖线。
ps auxf | grep mysql
上面命令行里的「|
」竖线就是一个管道,它的功能是将前一个命令(ps auxf
)的输出,作为后一个命令(grep mysql
)的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。
同时,我们得知上面这种管道是没有名字,所以「|
」表示的管道称为匿名管道,用完了就销毁。
管道还有另外一个类型是命名管道,也被叫做 FIFO
,因为数据是先进先出的传输方式。
在使用命名管道前,先需要通过 mkfifo
命令来创建,并且指定管道名字:
$ mkfifo myPipe
myPipe 就是这个管道的名称,基于 Linux 一切皆文件的理念,所以管道也是以文件的方式存在,我们可以用 ls 看一下,这个文件的类型是 p,也就是 pipe(管道) 的意思:
$ ls -l
prw-r--r--. 1 root root 0 Jul 17 02:45 myPipe
接下来,我们往 myPipe 这个管道写入数据:
$ echo "hello" > myPipe // 将数据写进管道
// 停住了 ...
你操作了后,你会发现命令执行后就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。
于是,我们执行另外一个命令来读取这个管道里的数据:
$ cat < myPipe // 读取管道里的数据
hello
可以看到,管道里的内容被读取出来了,并打印在了终端上,另外一方面,echo 那个命令也正常退出了。
我们可以看出,管道这种通信方式效率低,不适合进程间频繁地交换数据。当然,它的好处,自然就是简单,同时也我们很容易得知管道里的数据已经被另一个进程读取了。
那管道如何创建呢,背后原理是什么?
匿名管道的创建,需要通过下面这个系统调用:
int pipe(int fd[2])
这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符 fd[0]
,另一个是管道的写入端描述符 fd[1]
。注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。
![](https://gitee.com/wardseptember/images/raw/master/imgs/20201014200618.png)
其实,所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。
看到这,你可能会有疑问了,这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢?
我们可以使用 fork
创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「 fd[0]
与 fd[1]
」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。
![](https://gitee.com/wardseptember/images/raw/master/imgs/20201014200740.png)
管道只能一端写入,另一端读出,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入,也都可以读出。那么,为了避免这种情况,通常的做法是:
- 父进程关闭读取的 fd[0],只保留写入的 fd[1];
- 子进程关闭写入的 fd[1],只保留读取的 fd[0];