Linux进程间通信
“休息过长就会发霉。”--沃尔特·司各特
在讲述完毕Linux中的基础IO之后,我们要来到Linux中很重要的一个模块,即进程间通信。
目录
1.引入
2.管道
2.1内容
2.2原理
2.3操作
2.3.1匿名管道的创建
2.3.2命名管道的创建
1.引入
进程间通信(ipc)是指:操作系统为用户提供的几种进程间通信方式,让进程之间可以实现通信功能。现在的问题是,进程间为什么不能直接进行通信,而需要操作系统提供通信方式。
这个问题的答案和之前的提到的内容有关,我们知道:进程在访问数据的时候都是通过地址来访问的,然而一个进程中的地址都是虚拟地址,在经过页表映射之后才可以访问实际的物理地址。
因此当一个进程想要给另外一个进程传递数据(进程间通信)的时候,就需要将空间地址传递给对方。但是就接收数据的进程而言,它其中的页表映射内容和发送数据进程的页表内容大相径庭,所以接收数据的进程无法访问到具体的数据地址。
该设计的初衷是让进程具有独立性(稳定)与之伴随着的问题便是:进程间的通信无法直接完成。所以需要操作系统来提供一些相关的进程间通信方式:管道、共享内存、消息队列和信号量。
2.管道
2.1内容
管道的作用:用于实现数据(资源)的传输。
管道的特性:半双工通信--可以选择方向的单向通信(同一时间不能既发送数据又接收数据),正如其名,管道里的流水既可以流出也可以流入,但无法同一时间既流出又流入。
管道的本质:内核中的一块缓冲区(内核空间中的内存--由系统进行管理)
管道的分类:
- 匿名管道:管道没有标识符,无法被其他进程找到,只能做拥有具有亲缘关系的进程间通信;
- 命名管道:管道具有标识符,能够被其他进程找到,可以做用于同意主句的任意进程间通信。
2.2原理
匿名管道的原理:父进程创建了一个匿名管道之后,会收到匿管道对应的文件描述符,进而可以对匿名管道中的内容做增删查改等基本操作。而当子进程创建出来之后,子进程复制父进程,则会复制到同一个匿名管道的操作句柄,进而也可以对其进行基本的操作。
如此操作之后,则会产生数据交互区,即实现了父子进程间的通信。其中发送数据就是向管道写入数据,接受数据就是从管道读取数据。
不过值得注意的是,刚才有提到管道的“文件描述符”这一名词,这是因为管道本质上是一种特殊的文件,因为Linux中一切皆文件。但是管道并不是传统意义上的磁盘文件,它的本质是内存。
命名管道的原理:本质上也是内核中的一块缓冲区。与匿名管道不同的便是,它在创建的时候有名字,可以用于同一主机上任意进程间通信。其余的原理基本同匿名管道。
2.3操作
2.3.1匿名管道的创建
int pipe(int pipefd[2]);
pipe接口的功能是创建一个匿名管道,并通过参数返回管道的两个操作句柄。其中pipefd是一个具有两个整形元素的数据,内部创建管道会将相关的描述符存储在数据之中。
- pipefd[0] -- 用于从管道中读取数据;
- pipefd[1] -- 用于从管道中写入数据。
返回值:创建成功返回0,失败返回-1。注意创建匿名管道之前,一定要在创建子进程之前。
在了解匿名管道的概念和操作之后,我们照旧通过代码来对上述内容进行一个简单的实践。
最后执行上述代码,我们在终端上得到这样的结果:
可以明显的观察到,子进程1对管道写入了数据,子进程2对管道中的数据进行了读取。这样的结果很好的阐明了:管道对于进程间通信的实现方式。
值得注意的是:当管道中的读端被关闭之后,持续向管道写入数据会导致进程崩溃,因为管道能容纳的数据有限,只存不取(只写不读)会产生数据过多,管道无法存储。因此当进程不在读取管道中的数据时,此时向管道写入数据时没有意义的,系统将进程结束--进程崩溃。
而当管道中的写端被关闭之后,则当读取完管道中的数据之后,不会产生阻塞,会返回0。这意味着当我们通过read返回得到0时,说明管道中的数据已经被读取完,无法读取到新的数据了已经。因此,我们也可以通过read的返回值,来决定什么时候停止从管道读取数据。(有些人永远等不来~)
2.3.2命名管道的创建
int mkfifo(const char* pathname, mode_t mode);
mkfifo接口的功能时创建一个命名管道,会依赖pathname参数建立特殊的FIFO文件,mode则是管道文件的访问权限。
创建成功则返回0,失败则返回-1。
照例我来通过代码对命名管道的接口进行实践:
我们向创建一个read_pipe.c文件,其中代码如上图所示,目的是从创建的管道文件中读取数据。
在此之后,我们创建一个write_pipe.c文件,来向管道写入数据,程序内容如上图所示。
生成可执行程序之后,我们复制终端,在两个终端上分别进行读写操作,发现程序运行良好,很好的实现的进程通信的目的。(结果不做展示,望大家自我实践)
值得注意的是,对于命名管道:如果我单独打开两个可执行程序,即只向管道读取数据或是只向管道写入数据会造成阻塞,直到管道被其他任意进程以另一种方式打开,进程才能继续运行。
造成这样结果的原因也十分简单,我们可以从管道的作用来理解,管道本身的作用便是实现进程间通信,若是进程间不存在通信,则会阻塞直到通信开始。这样的设计有助于节省空间,因为当一个管道不同时具备读写(即管道不通信),就没必要为其开辟对应的缓冲区。
除上述讲到的命名管道特性外,匿名管道具有的特性,命名管道都具有。