进程通信的产生
如果未来进程之间要协同,一个进程要把自己的数据交给另一个进程,或者一个进程要命令另外一个进程做其他事。但是,由于进程之间是具有独立性的,如果想把一个进程的数据交给另一个进程,基本不可能。
由此诞生出如何实现进程通信。
如何做到进程通信
进程之间是有独立性的?还需要保证这两者之间可以进行通信,由此我们被迫使用第三者,他可以对这两个进程通信起来。那他是谁呢?无疑我们第一个想到的便是操作系统。
结论:进程间通信的前提是:让不同的进程看到同一份资源。这份资源由 OS 提供,而资源一定是某种形式的内存空间!
进程通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程通信分类及发展背景
既然有了对进程通信概念理解,那么让进程实现通信就落实到资源这一块问题了。
进程通信分类如图所示。
我们前面有接触过管道,但是并没有谈及具体含义,现在就可以来解决这个问题了。
大家都知道,我们程序员是非常'偷懒'的,有可以复用的代码绝不会多写一遍,实际上这也是一种'巧智',从某方面提高了可维护性。
管道就是一种'取巧'的方式,它是基于文件的通信方法。
但是随着时代的发展,大家发现管道并不能解决所有问题,由于一批新问题的产生导致程序员必须创造一个真正的资源,供进程间通信,由此产生了 System V 标准和消息队列等...
System V 进程间通信 --> 单独设计通信模块 --> 制定标准了 --> 只能进行本地通信 (自己的电脑)。
这里谈及下 System V 的生态问题:
System V 标准并非由单一公司定制,而是由 AT&T(美国电话电报公司)主导开发的一套 UNIX 操作系统标准。
实际上定标准和实现标准对应的代码是两批人。
一般定标准的人都是不需要定标准的,而是有另一层进行代码编写。那为什么另一层凭什么听从定标准的人呢?因为定标准的人在技术上一定是处于技术领先的地位 --> 这意味着他的产品领先 --> 我所定下的标准,时代会跟着走。如果你不跟着我的标准走,那么你就被时代淘汰,就会没落。反之,如果你跟着我定的方向走,就能继续发展。--> 从而使你被迫听从定标准的这批人。
管道概念
我们把从一个进程连接到另一个进程的一个数据流称为一个'管道'。
如何证明两边都是进程呢?
- 在 Linux 系统中,每个进程都有唯一的进程号 PID,通过 ps 工具确实观察到有三个进程,且有唯一的进程号。
- 它们的状态都为 S+,表明是正在运行的进程。
- 既然它们的父进程都相同,打印父进程发现是 bash shell,说明三者是并发执行的。
匿名管道
fork 共享管道原理
我们之前写过让父子进程同时向显示器进行打印内容,这是如何做到的呢?
让父子进程看到了同一份资源。在上图中,父进程创建子进程,子进程会以拷贝父进程的内核数据结构,此时父子都指向一个 struct file,那么 struct file 里是什么呢?里面不是有个文件缓冲区吗?这不意味着父子看到了同一份资源?那么就可以基于这个原理进行进程的通信。
文件描述符角度 - 理解管道
父子进程可以看到同一份资源了,但是会出现一个问题:如果父进程写到 101 位置时,此时位置是停留在 101 这个位置的,此时子进程读是往 101 后读,因此会读到空内容。为了实现父子进程之间的通信,我们不得不让 struct file 也拷贝一份,这样便能实现父子进程间的通信。实际上,这便是管道的设计原理。
管道的定义:管道是一个基于文件系统的一个内存级的单向通信的文件,主要用来进程间通信 (IPC, Inter-Process Communication) 的。
- 父进程要以'读写'两种方式打开同一个管道文件。
- 为什么要读写打开? --> 如果只打开读,那么子只能继承读,写也同理 --> 让子进程,也继承 rw 方式。
- 为什么要关闭读写端? --> 管道只需要单向通信 --> 定位:简单快速易上手 (不关闭也可以,但建议 --> 防止误操作!)
- 如果要让两个进程互相通信呢? --> 创建两个管道!


