进程之间可能存在协同工作的场景,一个进程把自己的数据交付给另一个进程,让其进行处理,如:
cat file.txt | wc -l
执行cat指令会触发一个进程,执行完cat file.txt 能读取到file.txt文件的内容,然后将读取结果通过管道交付给下一个进程wc,让wc来统计file.txt文件有多少行内容
这就是进程间通信的方式之一 —— 管道
目录
一、进程间通信的本质前提
因为进程之间具有独立性,一个进程是看不到另一个进程的资源的!!
两者在这种水火不容的情况下,要如何进行通信呢?? 答案是 给他们一块公共资源(公共内存)
这块内存不属于任何进程!!如果属于进程A,那么进程B能访问到这块公共资源,这就打破了进程的独立性原则,所以这块公共资源属于OS
所以进程间通信的前提就是 有一块双方都能访问的公共资源,这块资源是属于OS的
二、匿名管道的作用原理
下面以父子进程为例,介绍管理的作用方式
一个进程如果要向磁盘写入数据,那么必会先得到写入文件的文件描述符fd ——》 找到对应文件的struct file ——》 调用文件操作函数 ——》向文件内核缓冲区写入数据 ——》OS定时调用驱动函数 write_disk 向磁盘写入数据
现在我们通过fork()创建了一个子进程,子进程会拷贝父进程的进程控制块和struct files,此时两个进程就有了一块公共资源 struct file
现在我们不触发底层的写入函数,那么数据就会留在文件内核缓冲区,此时我们的子进程就可以读取到父进程的数据了,这就是管道的作用过程
三、管道创建函数pipe
实际上Linux给我们提供了创建管道的函数pipe,pipe函数正是基于“子进程会继承父进程的资源”这种特性来实现的,因为子进程会继承父进程的资源,那么子进程和父进程就能看到同一份文件内核缓冲区
1、pipe函数的声明
pipe函数的参数是一个输出型参数,返回两个文件描述符。管道被创建时,会需要使用两个文件描述符,一个文件描述符用于向管道写数据,一个文件描述符用于从管道读数据
pipe函数的返回值代表管道是否创建成功。如果创建成功,返回0;否则返回-1
2、pipe函数的使用
=======================步骤一:父进程创建管道=======================
int pipefd[2]; //存储创建管道时,占用的两个文件描述符
pipe(pipefd);
要使用pipe函数,需要了解pipe函数是如何跟文件描述符建立关系的,pipe函数被调用的时候,会像下面这样。当我们要从管道读数据时,使用的是文件描述符 pipefd[0];向管道写入数据时,使用的是文件描述符 pipefd[1]
注意:
为什么要使用两个文件描述符来一个读一个写,使用一个可以吗?
答案是不行!如果只有一个文件描述符来读,那么子进程继承的时候也只能读,父进程只能读,子进程也只能读,这样就无法通信
=======================步骤二:父进程创建子进程=======================
fork(); //创建子进程
我们继续创建一个子进程,让子进程和父进程通信,子进程被创建时,会继承父进程的 fd_array[ ],所以子进程也会指向这个管道
==============步骤三:关闭父进程的pipefd[1] 和 子进程的pipefd[0]=============
if(fork()==0)
{
//子进程
close(pipefd[0]);
}
//父进程
close(pipefd[1]);
管道是一个只能单向通信的通信信道,要么是父进程写,子进程读;要么是子进程写,父进程读
同时开放父进程的读写功能没有太大意义,而且容易混淆
我们这里就以 子进程写,父进程读 为例,所以我们要关闭子进程的pipefd[0],以及父进程的pipefd[1]
=======================步骤四:测试=======================
现在我们综合上述的代码,同时加一点内容来测试父子进程之间是否能通信
这里就不展示Makefile的内容了
这就说明父子间的进程就通信成功了,但是还没有结束,对于下面四种情况又该如何呢
(1) 子进程写入的速度 远大于 父进程读取的速度
(2) 子进程正在写入,但是父进程关闭了文件描述符
(3) 父进程读取的速度 远大于 子进程写入的速度
(4) 子进程写完一批数据以后关闭文件描述符,父进程在读取
这四种情况放到其他博文里说明