一般来说,每个进程都是独立的,操作系统为每个进程之间提供了「隔离」。所以进程之间不能直接互相访问,但可以借助它们共享的「内核空间」来实现通信。

什么是进程通信
进程是分配系统资源的基本单位,各个进程拥有独立的内存地址,且彼此之间不能互相访问对方的内存地址。
进程的数据都保存在自身的内存块中,而进程之间经常需要信息交换。所谓信息交换,就是读取对方进程的数据,进程通信就是进程之间的信息交换。
操作系统为了保证进程各自的安全性,限制进程不能访问其他进程的地址空间,由此操作系统提供了一些进程间的安全通信机制。
进程通信机制
1.管道
在Linux命令中很常见一种通信机制,用「|」这个竖线符号表示。
举个栗子:
Linux中用「ps -aufx」可以查看当前进程。

这里截图只截了一部分,但实际上进程列出了很多,假如我们要看mysql进程在后台有没有运行,一个一个看就太麻烦了。
管道「|」登场:
输入命令「ps -aufx | grep mysql」,此时则只显示mysql相关进程:

解释一下,「ps -aufx」是查看进程,「grep」命令是过滤文本,后面跟上要筛选的文本,而中间的「|」就被称为管道。
就像是一根管子,前面的命令的输出经过这跟管子传递给后面命令的输入。

管道就是将一个进程的输出传递给另一个进程的输入。
但我们发现管道只能单向传输数据,想要实现进程之间的双向传输则需要创建两个管道。

2.消息队列
消息是存储在内核中的消息链表,遵循队列的先进先出原则。

进程A给进程B传输消息,进程A直接写入消息队列就可以了,然后进程B在需要的时候去队列中取消息。
写消息就往队列中入队一个消息块,读消息就删除队列中队头的消息块。
而消息块遵循进程双方自定义的数据类型,有固定大小。消息队列不适合传输较大的数据,因为每个消息块的大小有限制。
由于消息队列存在于内核中,所以进程的读写消息需要在用户态与内核态频繁切换,系统开销较大。
3.共享内存
操作系统采用虚拟内存管理,即每个进程都有各自的虚拟内存。
例如进程A的虚拟内存地址范围可以是#0x0001~#0x2000,B进程同样也可以占用这个地址,操作系统把它们的虚拟内存映射到了不同的物理内存。

而共享内存就是借助这个映射机制,分配一块共享的内存块,使进程A和进程B的虚拟地址映射到同一个物理地址,这样就可以互相读取和写入数据了。

4.信号量
前面的共享内存虽然速度快,但有一个缺陷,多个进程同时修改一块共享内存,会导致数据被覆盖而产生冲突。
而信号量有效解决了进程间竞争共享资源而导致的数据错误,用于实现进程的访问共享资源的互斥与同步。
可以把信号量理解为一个整数,信号量有两个原子操作,分别为减1和加1,分别称为P操作和V操作。
- P操作:把信号量减1,如果此时信号量<0,则当前进程被阻塞,如果信号量>=0,当前进程正常执行。
- V操作:把信号量加1,如果此时信号量<=0,则表明阻塞队列中有进程,会唤醒阻塞中的进程执行,如果信号量>0,则表明没有进程被阻塞。
看一个场景,假设进程A在访问共享内存时不允许进程B访问,则可以用信号量实现互斥:

信号量初始值为1,若进程A先访问共享内存,则先执行了P操作,此时信号量为0。表示资源可用,正常访问。
而进程B此时也想访问共享内存,进程B经过P操作后,信号量为-1,小于0,进程B被阻塞。
接着进程A访问完毕,执行V操作,信号量为0,表示有进程被阻塞,则唤醒阻塞队列中的进程B,然后进程B访问共享内存,执行V操作,信号量变为初始值1。
实现了进程间访问共享资源的互斥问题,接下来看如何实现进程同步问题,所谓进程同步,就是有时候需要进程按一定顺序执行。
例如进程A实现读取数据,进程B实现运算数据,所以进程A一定要在进程B之前执行,同样可以用信号量实现。

设信号量初始值为0,如果进程B先执行,则经过P操作后,信号量为-1,小于0,此时进程B被阻塞。
而当进程A读取数据完数据,执行了V操作,信号量加1变为0,表明阻塞队列中有进程,则唤醒进程B,接着进程B正常执行,运算数据。
总结来说,PV操作是成对出现的,解决互斥问题时信号量初始值为共享资源的数量,解决同步问题时信号量初始值为0。
5.信号
信号可以理解为给进程发送一个命令。在Linux终端中按ctrl + c可不是复制,而是给当前进程发送终止信号,终止运行当前进程。
通过kill命令也可以杀死一个正在运行的进程,可以在任何时候给进程发送信号,信号是一种异步通信机制。
6.Socket
以上所说的5种通信机制都是本机之间的进程通信方式,如果要实现跨越网络的进程通信,就需要用到Socket。
Socket是一种系统调用,主要参数有两个,分别指定IP格式和通信方式。
- IP格式:ipv4或ipv6
- 通信方式:TCP或UDP
常说的C/S模式就是指客户端(Client)/服务端(Server)的通信模式,它们之间采用Socket通信。

首先客户端和服务端初始化Socket。
接着服务端调用bind绑定IP地址和端口,之后调用listen在这个IP和端口进行监听,然后调用accept等待客户端发起连接。
客户端调用connect向服务端端IP和端口发起连接请求,建立连接后在调用read和write分别读取和发送数据,完成客户端与服务端的数据传递。
END
操作系统为了进程之间的安全性,不允许进程直接访问其他进程的内存地址。
而进程之间需要传递数据,也就是进程通信,所以产生了许多进程通信机制。
评论区