共享內存和消息隊列原理概述

操做系統內的併發執行進程能夠是獨立的也能夠是協做的:算法

  • 若是一個進程不能影響其餘進程或受其餘進程影響,那麼該進程是獨立的,換句話說,不與任何其餘進程共享數據的進程是獨立的;
  • 若是一個進程能影響其餘進程或受其餘進程所影響,那麼該進程是協做的。換句話說,與其餘進程共享數據的進程爲協做進程。

提供環境容許進程協做,具備許多理由:編程

  • 信息共享:因爲多個用戶可能對一樣的信息感興趣(例如共享文件),因此應提供環境以容許併發訪問這些信息。
  • 計算加速:若是但願一個特定任務快速運行,那麼應將它分紅子任務,而每一個子任務能夠與其餘子任務一塊兒並行執行。注意,若是要實現這樣的加速,那麼計算機須要有多個處理核。
  • 模塊化:可能須要按模塊化方式構造系統,即將系統功能分紅獨立的進程或線程。
  • 方便:即便單個用戶也可能同時執行許多任務。例如,用戶能夠並行地編輯、收聽音樂、編譯。


協做進程須要有一種進程間通訊機制(簡稱 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,以便存儲所要使用的新項。異步

消息傳遞系統(消息隊列)

  前面講解了協做進程如何能夠經過共享內存進行通訊。此方案要求這些進程共享一個內存區域,而且應用程序開發人員須要明確編寫代碼,以訪問和操做共享內存。達到一樣效果的另外一種方式是,操做系統提供機制,以便協做進程經過消息傳遞功能進行通訊。消息傳遞提供一種機制,以便容許進程沒必要經過共享地址空間來實現通訊和同步。對分佈式環境(通訊進程可能位於經過網絡鏈接的不一樣計算機),這特別有用。例如,能夠設計一個互聯網的聊天程序以便聊天參與者經過交換消息相互通訊。消息傳遞工具提供至少兩種操做:send(message) 和 receive(message)。進程發送的消息能夠是定長的或變長的。若是隻能發送定長消息,那麼系統級實現就簡單。不過,這一限制使得編程任務更加困難。相反,變長消息要求更復雜的系統級實現,可是編程任務變得更爲簡單。在整個操做系統設計中,這種折中很常見。若是進程 P 和 Q 須要通訊,那麼它們必須互相發送消息和接收消息:它們之間要有通訊鏈路。該鏈路的實現有多種方法。這裏不關心鏈路的物理實現(如共享內存、硬件總線或網絡等),而只關心鏈路的邏輯實現。這裏有幾個方法,用於邏輯實現鏈路和操做 send()/receive():分佈式

  • 直接或間接的通訊;
  • 同步或異步的通訊;
  • 自動或顯式的緩衝;

下面研究這些特徵的相關問題。

命名

  須要通訊的進程應有一個方法,以便互相引用。它們可使用直接或間接的通訊。對於直接通訊,須要通訊的每一個進程必須明確指定通訊的接收者或發送者。採用這種方案,原語 send() 和 receive() 定義以下:

  • send(P,message):向進程P發送 message。
  • receive(Q,message):從進程 Q 接收 message。

  這種方案的通訊鏈路具備如下屬性:

  • 在須要通訊的每對進程之間,自動創建鏈路。進程僅需知道對方身份就可進行交流。
  • 每一個鏈路只與兩個進程相關。
  • 每對進程之間只有一個鏈路。

  這種方案展現了尋址的對稱性,即發送和接收進程必須指定對方,以便通訊。這種方案的一個變形採用尋址的非對稱性,即只要發送者指定接收者,而接收者不須要指定發送者。採用這種方案,原語 send() 和 receive() 的定義以下:

  • send(P,message):向進程 P 發送 message。
  • receive(id, message):從任何進程,接收 message,這裏變量 id 被設置成與其通訊進程的名稱。

   這兩個方案(對稱和非對稱的尋址)的缺點是:生成進程定義的有限模塊化。更改進程的標識符可能須要分析全部其餘進程定義。全部舊的標識符的引用都應找到,以便修改爲爲新標識符。一般,任何這樣的硬編碼技術(其中標識符須要明確指定),與下面所述的採用間接的技術相比要差。在間接通訊中,經過郵箱或端口來發送和接收消息。郵箱能夠抽象成一個對象,進程能夠向其中存放消息,也可從中刪除消息,每一個郵箱都有一個惟一的標識符。例如,POSIX 消息隊列採用一個整數值來標識一個郵箱。一個進程能夠經過多個不一樣郵箱與另外一個進程通訊,可是兩個進程只有擁有一個共享郵箱時才能通訊。原語 send() 和 receive() 定義以下:

  • send(A, message):向郵箱 A 發送 message。
  • receive(A,message):從郵箱 A 接收 message。

  對於這種方案,通訊鏈路具備以下特色:

  • 只有在兩個進程共享一個郵箱時,才能創建通訊鏈路。
  • 一個鏈路能夠與兩個或更多進程相關聯。
  • 兩個通訊進程之間可有多個不一樣鏈路,每一個鏈路對應於一個郵箱。

  如今假設進程 P一、P2 和 P3 都共享郵箱 A。進程 P1 發送一個消息到 A,而進程 P2 和 P3 都對 A 執行 receive()。哪一個進程會收到 P1 發送的消息?
  答案取決於所選擇的方案:

  • 容許一個鏈路最多隻能與兩個進程關聯。
  • 容許一次最多一個進程執行操做 receive ()。
  • 容許系統隨意選擇一個進程以便接收消息(即進程 P2 和 P3 二者之一均可以接收消息,但不能兩個均可以)。系統一樣能夠定義一個算法來選擇哪一個進程是接收者(如輪轉,進程輪流接收消息)。系統可讓發送者指定接收者。

  郵箱能夠爲進程或操做系統擁有。若是郵箱爲進程擁有(即郵箱是進程地址空間的一部分),那麼須要區分全部者(只能從郵箱接收消息)和使用者(只能向郵箱發送消息)。因爲每一個郵箱都有惟一的標識符,因此關於誰能接收發到郵箱的消息沒有任何疑問。當擁有郵箱的進程終止,那麼郵箱消失。任何進程後來向該郵箱發送消息,都會得知郵箱再也不存在。與此相反,操做系統擁有的郵箱是獨立存在的;它不屬於某個特定進程。所以,操做系統必須提供機制,以便容許進程進行以下操做:

  1. 建立新的郵箱。
  2. 經過郵箱發送和接收消息。
  3. 刪除郵箱。

  建立新郵箱的進程缺省爲郵箱的全部者。開始時,全部者是惟一能經過該郵箱接收消息的進程。不過,經過系統調用,擁有權和接收特權能夠傳給其餘進程。固然,這樣能夠致使每一個郵箱具備多個接收者。

同步

  進程間通訊能夠經過調用原語 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 */
}

緩存

  無論通訊是直接的仍是間接的,通訊進程交換的消息老是駐留在臨時隊列中。簡單地講,隊列實現有三種方法:

  1. 零容量:隊列的最大長度爲 0。所以,鏈路中不能有任何消息處於等待。對於這種狀況,發送者應阻塞,直到接收者接收到消息。
  2. 有限容量:隊列長度爲有限的 n。所以,最多隻能有 n 個消息駐留其中。若是在發送新消息時隊列未滿,那麼該消息能夠放在隊列中(或者複製消息或者保存消息的指針),且發送者能夠繼續執行而沒必要等待。若是鏈路已滿,那麼發送者應阻塞,直到隊列空間有可用的爲止。
  3. 無限容量:隊列長度能夠無限,所以,無論多少消息均可在其中等待。發送者從不阻塞。

  零容量狀況稱爲無緩衝的消息系統,其餘狀況稱爲自動緩衝的消息系統。

相關文章
相關標籤/搜索