进程间通讯原理
现代操作系统的运行模式都是在保护模式。进程运行在虚拟内存中,进程之间相互隔离,进程成为了操作系统分配内存的单位。在保护模式下,任何一个进程的变量在另外一个进程中都是看不到的。所以进程之间通信,就只能通过操作系统帮忙了,操作系统通过内核分配一块内存,可以理解为缓存区,进程A将数据从用户空间写入到缓冲区,进程B再从缓冲区中将数据拿走,这种机制就是进程间通信机制。
综上所述,进程间通信的本质是内存共享。
注意:
进程间通讯是面向数据的,也就是只能传递数据,不能传递指针。因为每个进程都有各自的内存空间,同一个指针,在进程A中指向的数据与进程B中指向的数据是不一样的。
层层递进的进程间通信机制
1、管道
我们来看一条 Linux 的语句
netstat -tulnp | grep 8080
学过 Linux 命名的估计都懂这条语句的含义,其中”|“是管道的意思,它的作用就是把前一条命令的输出作为后一条命令的输入。在这里就是把 netstat -tulnp 的输出结果作为 grep 8080 这条命令的输入。如果两个进程要进行通信的话,就可以用这种管道来进行通信了,并且我们可以知道这条竖线是没有名字的,所以我们把这种通信方式称之为匿名管道。
并且这种通信方式是单向的,只能把第一个命令的输出作为第二个命令的输入,如果进程之间想要互相通信的话,那么需要创建两个管道。
居然有匿名管道,那也意味着有命名管道,下面我们来创建一个命名管道。
mkfifo test
这条命令创建了一个名字为 test 的命名管道。
接下来我们用一个进程向这个管道里面写数据,然后有另外一个进程把里面的数据读出来。
echo"this is a pipe">test// 写数据
这个时候管道的内容没有被读出的话,那么这个命令就会一直停在这里,只有当另外一个进程把 test 里面的内容读出来的时候这条命令才会结束。接下来我们用另外一个进程来读取
cat
我们可以看到,test 里面的数据被读取出来了。上一条命令也执行结束了。
从上面的例子可以看出,管道的通知机制类似于缓存,就像一个进程把数据放在某个缓存区域,然后等着另外一个进程去拿,并且是管道是单向传输的。
这种通信方式有什么缺点呢?显然,这种通信方式效率低下,你看,a 进程给 b 进程传输数据,只能等待 b 进程取了数据之后 a 进程才能返回。
所以管道不适合频繁通信的进程。当然,他也有它的优点,例如比较简单,能够保证我们的数据已经真的被其他进程拿走了。我们平时用 Linux 的时候,也算是经常用。
优点:
内部保证了同步机制,确保通信数据的一致性
面向字节流
缺点:
父子间通信
单向数据传输(可以双向传输,需要建立两个管道)
进程在,管道就在;进程消失,对应端口的管道消失;两个进程消失,对应的管道也就消失
不适合频繁通信
2、消息队列MQ
那我们能不能把进程的数据放在某个内存之后就马上让进程返回呢?无需等待其他进程来取就返回呢?
答是可以的,我们可以用消息队列的通信模式来解决这个问题,例如 a 进程要给 b 进程发送消息,只需要把消息放在对应的消息队列里就行了,b 进程需要的时候再去对应的
消息队列里取出来。同理,b 进程要个 a 进程发送消息也是一样。这种通信方式也类似于缓存吧。
这种通信方式有缺点吗?答是有的,如果 a 进程发送的数据占的内存比较大,并且两个进程之间的通信特别频繁的话,消息队列模型就不大适合了。因为 a 发送的数据很大的话,意味发送消息(拷贝)这个过程需要花很多时间来读内存。
哪有没有什么解决方案呢?答是有的,请继续往下看。
特点:
生产者消费者模型,面向二进制块(进程可以控制块大小、快类型)
3、共享内存mmap
共享内存这个通信方式就可以很好着解决拷贝所消耗的时间了。
原理:
我们都知道,系统加载一个进程的时候,分配给进程的内存并不是实际物理内存,而是虚拟内存空间。那么我们可以让两个进程各自拿出一块虚拟地址空间来,然后映射到相同的物理内存中,这样,两个进程虽然有着独立的虚拟内存空间,但有一部分却是映射到相同的物理内存,这就完成了内存共享机制了。
适用场景:
适用于进程间需要频繁进行数据交互的场景。有时候为了频繁进行数据交互,我们并不能在度写完一部分数据之后,就解除内存映射,而是一直保持共享区域,直到通信结束为止。
优点:
1.速度最快,进程只需将数据写入内存,几乎无需进行硬盘读写,只需硬盘映射
2.进程退出的时候,自动解除磁盘映射,操作系统会将共享内存中的数据写入磁盘。
缺点:
进程之间竞争内存,需要进程内部控制互斥访问
适用场景举例:
日志系统:需要频繁的数据写入。
4、信号量
共享内存最大的问题是什么?没错,就是多进程竞争内存的问题,就像类似于我们平时说的线程安全问题。如何解决这个问题?这个时候我们的信号量就上场了。
信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。
场景举例:
ios:dispatch_semaphore android:semaphore
5、Socket
上面我们说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?
答是必须的,这个时候 Socket 这家伙就派上用场了,例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。
场景举例:
websocket
总结
进程之间的通信方式有:
1、管道
2、消息队列
3、共享内存
4、信号量
5、Socket