1、常見的通信方式
管道pipe:管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
命名管道FIFO:有名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
消息隊列MessageQueue:消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點。
共享存儲SharedMemory:共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
信號量Semaphore:信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
套接字Socket:套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。
信號 ( sinal ) : 信號是一種比較復雜的通信方式,用於通知接收進程某個事件已經發生。
3、詳解
3.1 管道
管道,通常指無名管道,是 UNIX 系統IPC最古老的形式。
1、特點:
它是半雙工的(即數據只能在一個方向上流動),具有固定的讀端和寫端。
它只能用於具有親緣關系的進程之間的通信(也是父子進程或者兄弟進程之間)。
它可以看成是一種特殊的文件,對於它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,並不屬於其他任何文件系統,並且只存在於內存中。
管道分為pipe(無名管道)和fifo(命名管道)兩種,除了建立、打開、刪除的方式不同外,這兩種管道幾乎是一樣的。他們都是通過內核緩沖區實現數據傳輸。
pipe用於相關進程之間的通信,例如父進程和子進程,它通過pipe()系統調用來創建並打開,當最后一個使用它的進程關閉對他的引用時,pipe將自動撤銷。
FIFO即命名管道,在磁盤上有對應的節點,但沒有數據塊——換言之,只是擁有一個名字和相應的訪問權限,通過mknode()系統調用或者mkfifo()函數來建立的。一旦建立,任何進程都可以通過文件名將其打開和進行讀寫,而不局限於父子進程,當然前提是進程對FIFO有適當的訪問權。當不再被進程使用時,FIFO在內存中釋放,但磁盤節點仍然存在。
管道的實質是一個內核緩沖區,進程以先進先出的方式從緩沖區存取數據:管道一端的進程順序地將進程數據寫入緩沖區,另一端的進程則順序地讀取數據,該緩沖區可以看做一個循環隊列,讀和寫的位置都是自動增加的,一個數據只能被讀一次,讀出以后再緩沖區都不復存在了。當緩沖區讀空或者寫滿時,有一定的規則控制相應的讀進程或寫進程是否進入等待隊列,當空的緩沖區有新數據寫入或慢的緩沖區有數據讀出時,就喚醒等待隊列中的進程繼續讀寫。
3.2 無名管道
pipe的例子:父進程創建管道,並在管道中寫入數據,而子進程從管道讀出數據
3.3 命名管道
和無名管道的主要區別在於,命名管道有一個名字,命名管道的名字對應於一個磁盤索引節點,有了這個文件名,任何進程有相應的權限都可以對它進行訪問。
而無名管道卻不同,進程只能訪問自己或祖先創建的管道,而不能訪任意訪問已經存在的管道——因為沒有名字。
Linux中通過系統調用mknod()或makefifo()來創建一個命名管道。最簡單的方式是通過直接使用shell
mkfifo myfifo
1
等價於
mknod myfifo p
1
以上命令在當前目錄下創建了一個名為myfifo的命名管道。用ls -p命令查看文件的類型時,可以看到命名管道對應的文件名后有一條豎線"|",表示該文件不是普通文件而是命名管道。
使用open()函數通過文件名可以打開已經創建的命名管道,而無名管道不能由open來打開。當一個命名管道不再被任何進程打開時,它沒有消失,還可以再次被打開,就像打開一個磁盤文件一樣。
可以用刪除普通文件的方法將其刪除,實際刪除的事磁盤上對應的節點信息。
例子:用命名管道實現聊天程序,一個張三端,一個李四端。兩個程序都建立兩個命名管道,fifo1,fifo2,張三寫fifo1,李四讀fifo1;李四寫fifo2,張三讀fifo2。
用select把,管道描述符和stdin假如集合,用select進行阻塞,如果有i/o的時候喚醒進程。(粉紅色部分為select部分,黃色部分為命名管道部分)
3.4 消息隊列
消息隊列,就是一個消息的鏈表,是一系列保存在內核中消息的列表。用戶進程可以向消息隊列添加消息,也可以向消息隊列讀取消息。
消息隊列與管道通信相比,其優勢是對每個消息指定特定的消息類型,接收的時候不需要按照隊列次序,而是可以根據自定義條件接收特定類型的消息。
可以把消息看做一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向消息隊列中按照一定的規則添加新消息,對消息隊列有讀權限的進程可以從消息隊列中讀取消息。
消息隊列的常用函數如下表:
進程間通過消息隊列通信,主要是:創建或打開消息隊列,添加消息,讀取消息和控制消息隊列。
3.5 共享內存
共享內存允許兩個或多個進程共享一個給定的存儲區,這一段存儲區可以被兩個或兩個以上的進程映射至自身的地址空間中,一個進程寫入共享內存的信息,可以被其他使用這個共享內存的進程,通過一個簡單的內存讀取錯做讀出,從而實現了進程間的通信。
采用共享內存進行通信的一個主要好處是效率高,因為進程可以直接讀寫內存,而不需要任何數據的拷貝,對於像管道和消息隊里等通信方式,則需要再內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次:一次從輸入文件到共享內存區,另一次從共享內存到輸出文件。
一般而言,進程之間在共享內存時,並不總是讀寫少量數據后就解除映射,有新的通信時在重新建立共享內存區域;而是保持共享區域,直到通信完畢為止,這樣,數據內容一直保存在共享內存中,並沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件,因此,采用共享內存的通信方式效率非常高。
共享內存有兩種實現方式:1、內存映射 2、共享內存機制
3.6 信號量
信號量(semaphore)與已經介紹過的 IPC 結構不同,它是一個計數器。信號量用於實現進程間的互斥與同步,而不是用於存儲進程間通信數據。
1、特點
信號量用於進程間同步,若要在進程間傳遞數據需要結合共享內存。
信號量基於操作系統的 PV 操作,程序對信號量的操作都是原子操作。
每次對信號量的 PV 操作不僅限於對信號量值加 1 或減 1,而且可以加減任意正整數。
支持信號量組。
2、原型
最簡單的信號量是只能取 0 和 1 的變量,這也是信號量最常見的一種形式,叫做二值信號量(Binary Semaphore)。而可以取多個正整數的信號量被稱為通用信號量。
Linux 下的信號量函數都是在通用的信號量數組上進行操作,而不是在一個單一的二值信號量上進行操作。
#include <sys/sem.h>
// 創建或獲取一個信號量組:若成功返回信號量集ID,失敗返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信號量的相關信息
int semctl(int semid, int sem_num, int cmd, ...);