在操作系统中,进程间通信是指不同进程之间进行信息共享、数据传输和消息通知等交互的过程。每个进程在创建时都有自己独立的虚拟地址空间,但它们共享内核空间。因此,要实现进程间的通信,必须通过内核来进行中介,如下图所示:
在Linux系统中,提供了多种进程间通信的机制,包括管道、消息队列、共享内存、信号量、信号、套接字等。这些机制允许进程之间共享数据、传输消息以及进行进程间的同步与通信。下面我们详细讲解下。
管道是一种进程间通信机制,它可以将一个进程的输出直接作为另一个进程的输入。在Linux系统中,管道可以用于将命令的输出传递给另一个命令进行处理。
ps -ef |grep java
使用Linux系统练手的时候,想必大家都是用这样的一种命令查看java进程,命令中的 |
就是管道命令,但是这个是匿名管道,用完了就销毁,匿名管道只能在有父子关系的进程之间进行通信。他的功能也很好理解,一个进程的输出直接作为另一个进程的输入,所以才能只展现java进程,所以他的传输方式是单向传输。
那么既然有匿名管道,就有命名管道,被叫做 FIFO
,因为数据是先进先出的传输方式。命名管道具有读写两个端口,进程可以通过打开管道的文件来进行读取或写入。当一个进程写入数据到管道时,另一个进程可以从管道中读取数据。
在使用命名管道前,先需要通过 mkfifo
命令来创建,并且指定管道名字:
$ mkfifo myPipe
myPipe 是管道的名称,在 Linux 中一切皆文件的原则下,管道也以文件的形式存在。我们可以使用 ll 命令查看一下,该文件的类型是 p,表示为管道(pipe)。
接下来,我们将数据写入名为 myPipe 的管道中:
在执行完写入操作后,你可能会发现命令执行后一直停留在那里。这是因为管道中的数据没有被读取,只有当管道中的数据被完全读取后,命令才能正常退出。因此,我们需要执行另一个命令来读取管道中的数据:
可以观察到,管道中的内容已经被成功读取并打印在终端上,另外,echo命令也正常退出了。
从中我们可以得知,匿名管道的通信范围限定在具有父子关系的进程之间。由于管道本身没有实体,也就是没有管道文件,所以只能通过fork来复制父进程的文件描述符,以实现进程间的通信。(fork是一个操作系统调用,用于创建一个新的进程。当调用fork时,操作系统会复制当前进程的副本)
在shell中执行A | B命令时,A进程和B进程都是由shell创建的子进程。A和B之间不存在父子关系,它们的父进程都是shell。
此外,对于命名管道,它可以在不相关的进程之间进行通信。这是因为命名管道事先创建了一个特定类型的设备文件,在进程中只需要使用该设备文件,就可以实现进程之间的通信。
消息队列是一种进程间通信的机制,它相比于管道具有更高的效率和灵活性。消息队列是通过在内核中创建一个消息链表来实现的,进程可以将数据放入消息队列中,然后其他进程可以从队列中读取这些数据。
例如,当进程A需要向进程B发送消息时,进程A将数据放入B进程对应的消息队列后即可正常返回。而进程B可以在需要时去读取数据。同样地,当进程B需要向进程A发送消息时,也可以按照相同的方式进行操作。
与管道不同的是,消息队列是有格式的,每个消息体都是固定大小的存储块,进程在读取数据时需要约定好消息体的数据类型。消息队列的优势在于可以支持进程间的异步通信,发送方和接收方不需要同时运行,消息可以在队列中等待对方读取。不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
消息队列的生命周期与内核相关,如果没有显式地释放消息队列或关闭操作系统,消息队列将一直存在。而管道的生命周期是随着进程的创建和结束而动态建立和销毁。
然而,消息队列也存在一些缺点。由于数据在用户态和内核态之间进行拷贝,消息队列通信过程中存在一定的开销。当进程将数据写入消息队列时,需要将数据从用户态拷贝到内核态;而另一个进程从消息队列中读取数据时,需要将数据从内核态拷贝到用户态。这种数据拷贝开销会影响通信的效率。
共享内存是一种高效的进程间通信机制,它允许多个进程共享同一块内存区域,避免了数据的拷贝过程,提高了通信速度。
在共享内存机制中,操作系统将一块共享内存区域映射到多个进程的虚拟地址空间中,使得它们可以直接访问同一块物理内存。这样,一个进程对共享内存的写入操作,其他进程可以立即看到更新后的数据,而不需要进行数据的拷贝传输。
由于共享内存不进行数据拷贝,因此在进程间通信的过程中,它具有较低的开销和较高的传输速度。然而,共享内存机制需要通过同步机制来保证多个进程之间的数据一致性,以免出现竞争条件和数据不一致的问题。
本篇文章总结了进程间通信的三种常见机制:管道、消息队列和共享内存。它介绍了每种机制的特点、优缺点以及适用场景。管道适用于父子进程之间的通信,但只能在有亲缘关系的进程之间使用。消息队列可以用于异步通信,并且支持多个进程之间的通信,但是消息的格式需要事先定义。共享内存是一种高效的通信方式,可以实现多个进程共享同一块内存区域,但需要处理进程间的同步和互斥。根据实际需求,可以选择合适的机制进行进程间通信。
下一篇文章将继续探讨信号量、信号和套接字的知识点!