本文是對UNIX IPC工具的宏觀總結和對比,細節的使用方法請參考相關手冊。
UNIX IPC工具分類
通信工具
數據傳輸
爲了進行通信,一個進程將數據寫入
到IPC工具中,另一個進程從中讀取
數據。這些工具要求在用戶內存
和內核內存
之間進行兩次數據傳輸。
一次傳輸是在寫入的時候,從用戶內存到內核內存。
另一次傳輸是在讀取的時候,從內核內存到用戶內存。
可以進一步將數據傳輸工具分成以下類別
:
- 字節流
通過管道,FIFO,以及流socket,交換的數據是一個無分隔符的字節流。每個讀取操作可能會從IPC工具中讀取任意數量的字節,不管寫者寫入的塊的大小是多少。
- 消息
通過System V消息隊列,POSIX消息隊列,以及數據報socket,交換的數據是以分隔符分隔的消息。每個讀取操作讀取由寫者寫入的一整條消息,無法只讀取部分消息,而把剩餘部分留在IPC工具中,也無法在一個讀取操作中讀取多條消息。
- 僞終端
僞終端是一種在特殊情況下使用的通信工具。
共享內存
進程通過將數據放到由進程間共享的一塊內存中以完成信息的交換。
內核通過將每個進程中的頁表條目指向同一個RAM分頁來實現這一功能。
一個進程可以通過將數據放到共享內存塊中使得其他進程讀取這些數據,由於通信無需系統調用(即,沒有用戶內存和內核內存之間的數據傳輸),因此共享內存的速度非常快。
大多數現代UNIX系統提供了三種形式
的共享內存。
- System V共享內存
- POSIX共享內存
- 內存映射
使用共享內存時的注意點
:
儘管共享內存的通信速度更快,但速度上的優勢是用來彌補需要對共享內存上發生的操作進行同步的不足。例如,當一個進程正在更新共享內存中的一個數據結構時,另一個進程就不應該試圖讀取這個數據結構,在共享內存中,信號量通常用來作爲同步方法。
放入共享內存的數據,對所有共享這塊內存的進程可見。這與傳輸工具中具有的破壞性語義不同。
數據傳輸和共享內存之間的差別
- 破壞性
一個數據傳輸工具可能會有多個讀者,但讀取操作是具有破壞性的。即,讀取操作會消耗數據,其他進程無法獲取所消耗的數據。
在socket中可以使用
MSG_PEEK
標記來執行非破壞性讀取
。UDP socket允許將一條消息廣播或組播到多個接收者。
- 原子的
讀者和寫者進程之間的同步是原子的。如果一個讀者試圖從一個當前不包含數據的數據傳輸工具中讀取數據,那麼在默認情況下,讀取操作會被阻塞直至一些進程向該工具寫入了數據。
同步工具
同步工具,可以防止
進程執行(諸如)同時更新一塊共享內存或同時更新文件的同一個數據塊等操作。UNIX系統提供了下列同步工具:
- 信號量
一個信號量,是一個由內核維護的整數,其值永遠不會小於0。一個進程可以增加或減小一個信號量的值。如果一個進程試圖將信號量的值減小到小於0,那麼內核會阻塞該操作直至信號量的值增長到允許執行該操作爲止。(或者,進程可以要求執行一個非阻塞操作,那麼就不會發生阻塞,內核會讓該操作立即返回一個表示無法立即執行該操作的錯誤)。
- 信號量的含義是由應用程序來確定的。一個進程減小一個信號量,是爲了預約對某些共享資源的獨佔訪問,在完成了資源的使用之後可以增加信號量來釋放共享資源,以供其他進程使用。
- 最常用的信號量,是
二元信號量
。一個值只能是0或1的信號量,但處理一類共享資源擁有多個實例的應用程序需要使用最大值等於共享資源數量的信號量。- Linux提供了System V和POSIX兩種信號量,它們功能類似。
- 文件鎖
文件鎖是設計用來協調操作同一文件的多個進程的動作的一種同步方法。它也可以用來協調對其他共享資源的訪問。
文件鎖分爲兩類
:
- 讀鎖/共享鎖
- 寫鎖/互斥鎖
- 任意進程都可以持有同一個文件的讀鎖,但當一個進程持有了一個文件的寫鎖後,其他進程將無法獲取該文件上的讀鎖和寫鎖。
- Linux通過flock()和fcntl()系統調用來提供文件加鎖工具。其中:
flock()系統調用提供了一種簡單的加鎖機制,允許進程將一個共享或互斥鎖加到整個文件上。由於功能有限,現在已經很少使用flock()這個加鎖工具了。
fcntl()系統調用提供了紀錄加鎖,允許進程在同一個文件的不同區域上加多個讀鎖和寫鎖。
- 互斥體和條件變量
這些同步工具通常用於POSIX線程。
IPC工具比較
工具類型 | 用於標識對象的名稱 | 用於在程序中引用對象的句柄 |
---|---|---|
管道 | 沒有名稱 | 文件描述符(支持I/O多路複用) |
FIFO | 路徑名 | 文件描述符(支持I/O多路複用) |
UNIX domain socket | 路徑名 | 文件描述符(支持I/O多路複用) |
Internet domain socket | IP地址+端口號 | 文件描述符(支持I/O多路複用) |
System V消息隊列 | System V IPC鍵 | System V IPC標識符(不支持I/O多路複用) |
System V信號量 | System V IPC鍵 | System V IPC標識符(不支持I/O多路複用) |
System V共享內存 | System V IPC鍵 | System V IPC標識符(不支持I/O多路複用) |
POSIX 消息隊列 | POSIX IPC路徑名 | mqd_t(消息隊列描述符)(支持I/O多路複用) |
POSIX 命名信號量 | POSIX IPC路徑名 | sem_t(信號量指針) |
POSIX 無名信號量 | 沒有名稱 | sem_t(信號量指針) |
POSIX 共享內存 | POSIX IPC路徑名 | 文件描述符 |
匿名映射 | 沒有名稱 | 無 |
內存映射文件 | 路徑名 | 文件描述符 |
flock()文件鎖 | 路徑名 | 文件描述符 |
fcntl()文件鎖 | 路徑名 | 文件描述符 |
網絡通信
在給出的所有IPC方法中,只有socket允許進程通過網絡來通信。socket一般用於兩個域中:
- UNIX domain
它允許位於同一個系統上的進程進行通信。
- Internet domain
它允許位於通過TCP/IP網絡進行連接的不同主機上的進程進行通信。
可移植性
現代UNIX實現支持大部分IPC工具,但是,POSIX IPC工具(消息隊列,信號量,以及共享內存)的普及程度遠遠不如System V IPC,特別是在較早的UNIX系統上。因此,從可移植性的角度來看,System V IPC要優於POSIX IPC
。
System V IPC設計問題
System V IPC工具被設計成獨立於傳統的UNIX I/O模型,其結果是其中一些特性使得它的編程接口的用法更加複雜。相應的POSIX IPC工具被設計用來解決這些問題。
- System V IPC工具是無連接的,它沒有提供引用一個打開的IPC對象的句柄(類似,文件描述符)的概念。
- System V IPC工具的編程接口與傳統的UNIX I/O模型時不一致的,並且這個編程接口過於複雜。
相反,內核會爲POSIX IPC對象紀錄打開的引用數,這樣就簡化了何時刪除對象的決策。此外,POSIX IPC提供的編程接口也更加簡單,與傳統的UNIX模型也更加一致。
可訪問性和持久性
- 可訪問性
權限模型控制着哪些進程能夠訪問對象。
- 持久性
指一個IPC工具的生命週期。有三種:
進程持久性:只要存在一個進程持有進程持久的IPC對象,那麼該對象的生命週期就不會終止。如果所有進程都關閉了對象,那麼與該對象的所有內核資源都會被釋放,所有未讀的數據會被銷燬。
PS: FIFO的數據持久性與其名稱的持久性是不同的。FIFO在文件系統中擁有一個名稱,當所有引用FIFO的文件描述符都被關閉之後,該名稱也是持久的。內核持久性:只有當顯示地刪除內核持久的IPC對象或系統關閉時,該對象纔會銷燬,這種對象的生命週期與是否有進程打開該對象無關。
文件系統持久性:具備文件系統持久性的IPC對象會在系統重啓的時候保持其中的信息,這種對象一直存在直至被顯示地刪除。唯一一種具備文件系統持久性的IPC對象是
基於內存映射文件的共享內存
。
工具類型 | 可訪問性 | 持久性 |
---|---|---|
管道 | 僅允許相關進程 | 進程 |
FIFO | 權限掩碼 | 進程 |
UNIX domain socket | 權限掩碼 | 進程 |
Internet domain socket | 任意進程 | 進程 |
System V消息隊列 | 權限掩碼 | 內核 |
System V信號量 | 權限掩碼 | 內核 |
System V共享內存 | 權限掩碼 | 內核 |
POSIX 消息隊列 | 權限掩碼 | 內核 |
POSIX 命名信號量 | 權限掩碼 | 內核 |
POSIX 無名信號量 | 相應內存的權限 | 依情況而定 |
POSIX 共享內存 | 權限掩碼 | 內核 |
匿名映射 | 僅允許相關進程 | 進程 |
內存映射文件 | 權限掩碼 | 文件系統 |
flock()文件鎖 | 文件的open()操作 | 進程 |
fcntl()文件鎖 | 文件的open()操作 | 進程 |