操做系统内的并发执行进程能够是独立的也能够是协做的:算法
提供环境容许进程协做,具备许多理由:编程
协做进程须要有一种进程间通讯机制(简称 IPC),以容许进程相互交换数据与信息。进程间通讯有两种基本模型:共享内存和消息传递(消息队列):数组
图 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,以便存储所要使用的新项。异步
前面讲解了协做进程如何能够经过共享内存进行通讯。此方案要求这些进程共享一个内存区域,而且应用程序开发人员须要明确编写代码,以访问和操做共享内存。达到一样效果的另外一种方式是,操做系统提供机制,以便协做进程经过消息传递功能进行通讯。消息传递提供一种机制,以便容许进程没必要经过共享地址空间来实现通讯和同步。对分布式环境(通讯进程可能位于经过网络链接的不一样计算机),这特别有用。例如,能够设计一个互联网的聊天程序以便聊天参与者经过交换消息相互通讯。消息传递工具提供至少两种操做:send(message) 和 receive(message)。进程发送的消息能够是定长的或变长的。若是只能发送定长消息,那么系统级实现就简单。不过,这一限制使得编程任务更加困难。相反,变长消息要求更复杂的系统级实现,可是编程任务变得更为简单。在整个操做系统设计中,这种折中很常见。若是进程 P 和 Q 须要通讯,那么它们必须互相发送消息和接收消息:它们之间要有通讯链路。该链路的实现有多种方法。这里不关心链路的物理实现(如共享内存、硬件总线或网络等),而只关心链路的逻辑实现。这里有几个方法,用于逻辑实现链路和操做 send()/receive():分布式
下面研究这些特征的相关问题。
须要通讯的进程应有一个方法,以便互相引用。它们可使用直接或间接的通讯。对于直接通讯,须要通讯的每一个进程必须明确指定通讯的接收者或发送者。采用这种方案,原语 send() 和 receive() 定义以下:
这种方案的通讯链路具备如下属性:
这种方案展现了寻址的对称性,即发送和接收进程必须指定对方,以便通讯。这种方案的一个变形采用寻址的非对称性,即只要发送者指定接收者,而接收者不须要指定发送者。采用这种方案,原语 send() 和 receive() 的定义以下:
这两个方案(对称和非对称的寻址)的缺点是:生成进程定义的有限模块化。更改进程的标识符可能须要分析全部其余进程定义。全部旧的标识符的引用都应找到,以便修改为为新标识符。一般,任何这样的硬编码技术(其中标识符须要明确指定),与下面所述的采用间接的技术相比要差。在间接通讯中,经过邮箱或端口来发送和接收消息。邮箱能够抽象成一个对象,进程能够向其中存放消息,也可从中删除消息,每一个邮箱都有一个惟一的标识符。例如,POSIX 消息队列采用一个整数值来标识一个邮箱。一个进程能够经过多个不一样邮箱与另外一个进程通讯,可是两个进程只有拥有一个共享邮箱时才能通讯。原语 send() 和 receive() 定义以下:
对于这种方案,通讯链路具备以下特色:
如今假设进程 P一、P2 和 P3 都共享邮箱 A。进程 P1 发送一个消息到 A,而进程 P2 和 P3 都对 A 执行 receive()。哪一个进程会收到 P1 发送的消息?
答案取决于所选择的方案:
邮箱能够为进程或操做系统拥有。若是邮箱为进程拥有(即邮箱是进程地址空间的一部分),那么须要区分全部者(只能从邮箱接收消息)和使用者(只能向邮箱发送消息)。因为每一个邮箱都有惟一的标识符,因此关于谁能接收发到邮箱的消息没有任何疑问。当拥有邮箱的进程终止,那么邮箱消失。任何进程后来向该邮箱发送消息,都会得知邮箱再也不存在。与此相反,操做系统拥有的邮箱是独立存在的;它不属于某个特定进程。所以,操做系统必须提供机制,以便容许进程进行以下操做:
建立新邮箱的进程缺省为邮箱的全部者。开始时,全部者是惟一能经过该邮箱接收消息的进程。不过,经过系统调用,拥有权和接收特权能够传给其余进程。固然,这样能够致使每一个邮箱具备多个接收者。
进程间通讯能够经过调用原语 send() 和 receive() 来进行。实现这些原语有不一样的设计方案。消息传递能够是阻塞或非阻塞,也称为同步或异步:
不一样组合的 send() 和 receive() 都有可能。当 send() 和 receive() 都是阻塞的,则在发送者和接收者之间就有一个交会。当采用阻塞的 send() 和 receive()时,生产者-消费者问题的解决就简单了。生产者仅需调用阻塞 send() 而且等待,直到消息被送到接收者或邮箱。一样,当消费者调用 receive() 时,它会阻塞直到有一个消息可用。这种状况以下代码所示:
//采用消息传递的生产者进程 message next_produced; while (true) { /* produce an item in next_produced */ send (next_produced); } //采用消息队列的消费者进程 message next_consumed; while (true) { receive (next_consumed); /* consume the item in next .consumed */ }
无论通讯是直接的仍是间接的,通讯进程交换的消息老是驻留在临时队列中。简单地讲,队列实现有三种方法:
零容量状况称为无缓冲的消息系统,其余状况称为自动缓冲的消息系统。