进程间通信(IPC):共享内存和消息队列原理详解
操作系统内的并发执行进程可以是独立的也可以是协作的:
提供环境允许进程协作,具有许多理由:
协作进程需要有一种进程间通信机制(简称 IPC),以允许进程相互交换数据与信息。进程间通信有两种基本模型:共享内存和消息传递(消息队列):
图 1 给出了这两种模型的对比。

图 1 通信模型
上述两种模型在操作系统中都常见,而且许多系统也实现了这两种模型。消息传递对于交换较少数量的数据很有用,因为无需避免冲突。对于分布式系统,消息传递也比共享内存更易实现。共享内存可以快于消息传递,这是因为消息传递的实现经常采用系统调用,因此需要消耗更多时间以便内核介入。与此相反,共享内存系统仅在建立共享内存区域时需要系统调用;一旦建立共享内存,所有访问都可作为常规内存访问,无需借助内核。
对具有多个处理核系统的最新研究表明,在这类系统上,消息传递的性能要优于共享内存。共享内存会有高速缓存一致性问题,这是由共享数据在多个高速缓存之间迁移而引起的。随着系统的处理核的数量的日益增加,可能导致消息传递作为 IPC 的首选机制。
回忆一下,通常操作系统试图阻止一个进程访问另一进程的内存。共享内存需要两个或更多的进程同意取消这一限制,这样它们通过在共享区域内读出或写入来交换信息。数据的类型或位置取决于这些进程,而不是受控于操作系统。另外,进程负责确保它们不向同一位置同时写入数据。
为了说明协作进程的概念,我们来看一看生产者-消费者问题,这是协作进程的通用范例。生产者进程生成信息,以供消费者进程消费。例如,编译器生成的汇编代码可供汇编程序使用,而且汇编程序又可生成目标模块以供加载程序使用。
生产者-消费者问题同时还为客户机-服务器范例提供了有用的比喻。通常,将服务器当作生产者,而将客户机当作消费者。例如,一个 Web 服务器生成(提供)HTML 文件和图像,以供请求资源的 Web 客户浏览器使用(读取)。
解决生产者-消费者问题的方法之一是采用共享内存。为了允许生产者进程和消费者进程并发执行,应有一个可用的缓冲区,以被生产者填充和被消费者清空。这个缓冲区驻留在生产者进程和消费者进程的共享内存区域内。当消费者使用一项时,生产者可产生另一项。生产者和消费者必须同步,这样消费者不会试图消费一个尚未生产出来的项。
缓冲区类型可分两种:
下面深入分析,有界缓冲区如何用于通过共享内存的进程间通信。以下变量驻留在由生产者和消费者共享的内存区域中:
生产者进程和消费者进程的代码为:
- 如果一个进程不能影响其他进程或受其他进程影响,那么该进程是独立的,换句话说,不与任何其他进程共享数据的进程是独立的;
- 如果一个进程能影响其他进程或受其他进程所影响,那么该进程是协作的。换句话说,与其他进程共享数据的进程为协作进程。
提供环境允许进程协作,具有许多理由:
- 信息共享:由于多个用户可能对同样的信息感兴趣(例如共享文件),所以应提供环境以允许并发访问这些信息。
- 计算加速:如果希望一个特定任务快速运行,那么应将它分成子任务,而每个子任务可以与其他子任务一起并行执行。注意,如果要实现这样的加速,那么计算机需要有多个处理核。
- 模块化:可能需要按模块化方式构造系统,即将系统功能分成独立的进程或线程。
- 方便:即使单个用户也可能同时执行许多任务。例如,用户可以并行地编辑、收听音乐、编译。
协作进程需要有一种进程间通信机制(简称 IPC),以允许进程相互交换数据与信息。进程间通信有两种基本模型:共享内存和消息传递(消息队列):
- 共享内存模型会建立起一块供协作进程共享的内存区域,进程通过向此共享区域读出或写入数据来交换信息。
- 消息传递模型通过在协作进程间交换消息来实现通信。
图 1 给出了这两种模型的对比。

图 1 通信模型
对具有多个处理核系统的最新研究表明,在这类系统上,消息传递的性能要优于共享内存。共享内存会有高速缓存一致性问题,这是由共享数据在多个高速缓存之间迁移而引起的。随着系统的处理核的数量的日益增加,可能导致消息传递作为 IPC 的首选机制。
共享内存系统
采用共享内存的进程间通信,需要通信进程建立共享内存区域。通常,共享内存区域驻留在创建共享内存段的进程地址空间内。其他希望使用这个共享内存段进行通信的进程应将其附加到自己的地址空间。回忆一下,通常操作系统试图阻止一个进程访问另一进程的内存。共享内存需要两个或更多的进程同意取消这一限制,这样它们通过在共享区域内读出或写入来交换信息。数据的类型或位置取决于这些进程,而不是受控于操作系统。另外,进程负责确保它们不向同一位置同时写入数据。
为了说明协作进程的概念,我们来看一看生产者-消费者问题,这是协作进程的通用范例。生产者进程生成信息,以供消费者进程消费。例如,编译器生成的汇编代码可供汇编程序使用,而且汇编程序又可生成目标模块以供加载程序使用。
生产者-消费者问题同时还为客户机-服务器范例提供了有用的比喻。通常,将服务器当作生产者,而将客户机当作消费者。例如,一个 Web 服务器生成(提供)HTML 文件和图像,以供请求资源的 Web 客户浏览器使用(读取)。
解决生产者-消费者问题的方法之一是采用共享内存。为了允许生产者进程和消费者进程并发执行,应有一个可用的缓冲区,以被生产者填充和被消费者清空。这个缓冲区驻留在生产者进程和消费者进程的共享内存区域内。当消费者使用一项时,生产者可产生另一项。生产者和消费者必须同步,这样消费者不会试图消费一个尚未生产出来的项。
缓冲区类型可分两种:
- 无界缓冲区没有限制缓冲区的大小。消费者可能不得不等待新的项,但生产者总是可以产生新项。
- 有界缓冲区假设固定大小的缓冲区。对于这种情况,如果缓冲区空,那么消费者必须等待;并且如果缓冲区满,那么生产者必须等待。
下面深入分析,有界缓冲区如何用于通过共享内存的进程间通信。以下变量驻留在由生产者和消费者共享的内存区域中:
#define BUFFER_SIZE 10 typedef struct { ... }item; item buffer [BUFFER_SIZE]; int in = 0; int out = 0;共享 buffer 的实现采用一个循环数组和两个逻辑指针:in 和 out。变量 in 指向缓冲区的下一个空位;变量 out 指向缓冲区的第一个满位。当
in == out
时,缓冲区为空;当 (in + 1)%BUFFER SIZE == out
时,缓冲区为满。生产者进程和消费者进程的代码为:
//生产者进程 while (true) { /* produce an item in next .produced */ while (((in + 1) %BUFFER_SIZE) == out) ;/* do nothing */ buffer [in] = next_produced; in = (in + 1) % BUFFER.SIZE; } //消费者进程 item next_consumed; while (true) { while (in == out) ;/* do nothing */ next_consumed = buffer[out]; out = (out + 1) %BUFFER_SIZE; /* consume the item in next-consumed */ }生产者进程有一个局部变量 next_produced,以便存储生成的新项;消费者进程有一个局部变量 next_consumed,以便存储所要使用的新项。