进程间通信
进程间通信,即IPC(I nter P rocess C ommunication),在学习进程时,我们知道进程是具有独立性的,两个进程之间无法直接进行信息传递。为了实现进程间通信,人们创造除了许多方法,比如管道,共享内存等,本文旨在理清这些通信方法的原理。
对于进程间通信,我们应该始终关注一个根本性的概念:进程间通信,本质是让两个进程看到同一份资源。
管道
管道分为匿名管道 和命名管道,但其实它们之间差别并不是很大,理解其中一个便可以理解另一个。
匿名管道
匿名管道只能用于父子间通信。先概述一下原理:
首先,父进程通过 pipe 函数创建一个管道,管道本质是一个特殊的文件,父进程有两个文件描述符会指向它(一个读,一个写)。
然后,父进程创建子进程,因为子进程会继承父进程的文件描述符表,所以其中也会有两个文件描述符指向管道文件。
最后,父进程关闭其中一个文件描述符,比如关闭表示 "读" 的文件描述符,而子进程关闭表示 "写" 的文件描述符。这样,父进程可以对管道进行 "写入信息" 的操作,子进程可以对管道进行 "读出信息" 的操作。

命名管道
匿名管道只能用于父子进程之间,而命名管道可以用于任意两个进程之间。
命名管道的原理与匿名管道的一致,所以对于原理不过多赘述,但有些问题值得讨论。
前面说管道通信的本质是让两个进程看到同一个文件,父子进程之间通过继承文件描述符表的方式看到同一个文件,那两个陌生进程怎么保证看到同一个文件呢?
我们先来看例子:

可以看到,我们在一端创建管道时要准备好管道的路径信息,然后另一端再根据路径找到管道。
匿名管道和命名管道的差异
匿名管道和命名管道除了使用场景,只剩创建方式不同 ,匿名管道使用 pipe 创建,而命名管道使用 mkfifo 创建。我们来看看它们的函数原型:

pipefd[2]为传出参数,pipefd[0]是读端,pipefd[1]是写端。

pathname是要创建的管道文件名,mode是文件的权限。
在创建与打开管道的工作结束后,匿名管道和命名管道就具有相同的语义了。
管道的一些特性
同步机制
管道文件,自带同步机制。内核自动帮你处理好读写两端的阻塞和等待,保证数据的有序、安全传递,不需要你手动加锁或信号量。
存在以下4种通信情况:
1.写端慢 ,读端快:读端阻塞,等待写端。
2.写端快 ,读端慢:当缓冲区满了,写端阻塞,等待读端。
3.写端关闭 ,读端继续:读端一直读,直到 read 返回 0,表示读到文件结尾。
4.写端继续 ,读端关闭:无意义,操作系统杀掉写端。
单项通信
前面说管道是半双工的,什么叫半双工呢?
半双工即同一时间只能单向传输,比如 A 和 B 进行通话:A 说话时,B 只能听,如果 B 想说话,就必须先等 A 说完。
除了半双工,还有单工 和全双工 。单工是一端只能听,不能说,另一端相反 。而全双工是同一时间可以双向通信。
注意后面说的两种通信方式不是管道的特性啊!管道只是半双工的!
生命周期
管道(管道文件)的生命周期是随进程的,也就是进程结束它就关闭了。
面向字节流
管道是面向字节流的。管道里的数据是一串连续的 "字节流",没有固定的 "消息边界",读和写的次数不一定对应,内核不帮你区分 "哪段是第一条消息、哪段是第二条"。
比如写端的本意是传输:AA BB CC(两个单词为一组),但是传输进入管道的数据是 A A B B C C,读端不知道写端的本意,可能会读成 AAB BCC(三个单词为一组)之类的。
如果想要解决这个问题,我们可以使用手动加分隔符 ,或者先传入读取规则(读取长度)再传入数据等方法。又或者,我们直接换一种IPC实现方式(见下篇)。