首页 > 编程笔记 > 操作系统笔记 阅读:45105

进程间通信(IPC):共享内存和消息队列原理详解

操作系统内的并发执行进程可以是独立的也可以是协作的:
提供环境允许进程协作,具有许多理由:
协作进程需要有一种进程间通信机制(简称 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,以便存储所要使用的新项。

消息传递系统(消息队列)

前面讲解了协作进程如何可以通过共享内存进行通信。此方案要求这些进程共享一个内存区域,并且应用程序开