Linux進程通信基本概念
從原理上來看,進程通信的關鍵技術就是在進程間建立某種共享區,利用進程都可以訪問共享區的特點來建立一些通信通道。如下圖所示:
其實,以前設計程序時使用的全局變量,就是一種可以在各個函數之間進行通信的手段,它所佔用的內存空間就是程序中各個函數的共享區。但那時,由於各個函數都同屬於一個進程,因此沒有進程空間的障礙。所以,解決進程之間通信的就在於如何突破進程空間的障礙。
其實,在某些特殊情況下,兩個進程之間並沒有空間障礙。例如:用fork()創建了一個子進程之後,子進程與父進程尚未完全脫離關係之前,子進程和父進程處於同一個進程空間,這時是完全可以向定義一個全局變量來定義一個通信通道的,以實現父子進程之間的通信。雖然這是自然形成的一個結果,但並不是操作系統的功勞,卻給人們一個啓示:可以利用子進程創建伊始會與父進程共享一些資源(例如文件)的特點來構建通信通道,Linux在這方面的成果就是“管道”。
父子進程利用共享資源進行通信的示意圖如下:
對於那些具有進程空間障礙的進程通信可以利用頁框共享技術來實現。例如,一個由3個進程共享的頁框示意圖如下:
另外,還可以當進程處於內核期間利用內核空間來進行通信,例如:
總之,通信的關鍵就是想辦法在進程之間創建共享區域來傳遞數據。
System V IPC機制簡介
Linux的通信手段基本都繼承自Unix。歷史上對Unix的發展做出重大貢獻的兩大主力:AT&T的貝爾實驗室和加州大學伯克利分校(BSD)。在通信方面,前者主要對單個計算機內的進程通信機制進行了改進和擴充,形成了System V IPC;後者則主要在計算機間,基於嵌套字(socket)的進程間通信機制方面做出了重要貢獻。
早期的Unix IPC包括管道、FIFO和信號,後期的System V IPC則主要包括System V消息隊列、System V信號量集和System V共享內存。
Linux從一開始就嚴格遵守Unix的設計思路,從而形成了Linux通信機制,如下圖所示:
Linux進程通信的主要手段有:
- 匿名管道(pipe)以及命名管道(named pipe):匿名管道可用於具有親屬關係進程間的通信;命名管道克服了匿名管道沒有名稱的限制,從而允許無親屬關係進程間的通信;
- 信號(signal):信號是一種通知性的通信方式,當某種事件發生時,用於向接收進程發出通知,根據需要,接收進程可以對通知做出某種反應,也可以不加任何理睬;
- 消息隊列:消息隊列是由多個信號組成的鏈表,而消息是一種有結構的數據。與信號比,消息隊列能承受更大的信息量;與管道比,消息隊列克服了管道只能承載無格式字節流以及緩衝區大小受限的缺點。比較完備;
- 共享內存:共享內存是多個進程可以共同訪問一塊內存空間,是最快的通信方式。但它不具備同步和互斥機制,往往需要程序上使用信號量或鎖與它配合;
- 信號量集(semaphore):主要作爲進程間以及同一進程不同線程之間的同步方式;
- 嵌套字(socket):主要用來實現網絡上不同機器之間的通信。
在上述System V IPC通信機制中,通常將通信裝置叫做IPC(Inter-Process Communication,進程間通信)對象。爲了對這些對象進行統一管理,系統爲他們定義了一些統一的數據結構和標識。
IPC標識符
在IPC中,對於消息隊列、共享內存和信號量集這些IPC對象,都用非負的整數來作爲對象的標識。不同種類對象的標識是各自獨立編制的,即同一個標識符可能代表共享內存,也可能代表消息隊列或信號量集。
IPC鍵值
爲了能唯一的標識一個對象,在IPC中,每創建一個對象還要爲其指定一個鍵值,這個鍵值是一個長整型數。
kern_ipc_perm結構
系統爲每一個IPC對象,都爲其創建一個描述該對象基本信息的ipc_perm結構。該結構主要用於訪問權限檢查。kern_ipc_perm的定義如下:
struct kern_ipc_perm
{
spinlock_t lock;
int deleted;
int id;
key_t key; //對象的鍵值
uid_t uid; //用戶標識
gid_t gid; //用戶組標識
uid_t cuid; //對象創建者的用戶標識
gid_t cgid; //對象創建者的用戶組標識
mode_t mode; //對象的操作權限
unsigned long seq; //已創建的IPC對象的數目
void *security; //與對象標識相關的序列號
};
當一個對象被創建時,kern_ipc_perm除了seq之外的所有域都會被賦予相應的數值。在對象存在期間,IPC對象的創建者和超級用戶,可以調整控制函數xxxctl()來修改kern_ipc_perm結構中的屬性。控制函數名稱中的xxx的含義如下圖所示:
IPC對象的生命期
IPC對象是全局對象,對象一經創建就由系統進行管理,它們的生命期與創建它的進程生命期無關。
IPC對象的操作函數
爲了實現對象的操作,系統在函數庫中爲用戶提供了一個IPC對象操作函數ipc()。其原型如下:
int ipc(uint call, int first, int second, int third, void __user *ptr, long fifth);
ipc()中的第一個參數call叫做操作碼。不同的操作碼對應着不同的IPC對象的不同操作函數。該操作碼定義如下:
#define SEMOP 1
#define SEMGET 2
#define SEMCTL 3
#define SEMTIMEDOP 4
#define MSGSND 11
#define MSGRCV 12
#define MSGGET 13
#define MSGCTL 14
#define SHMAT 21
#define SHMDT 22
#define SHMGET 23
#define SHMCTL 24
凡是以SEM開頭的操作碼都表示信號量集操作函數,以MSG開頭的都表示是消息隊列操作函數,以SHM開頭的都表示是內存共享操作函數。
其實,上述這種用操作碼來區分不同對象的不同操作並不方便(至少使程序的可讀性變差)。所以,Linux系統函數庫又分別定義了12個函數來完成不同的操作。其中,用於信號量集的函數爲semop()、semget()、semctl()、semtimedop();用於消息隊列的函數爲msgsnd()、msgrcv()、msgget()、msgctl();用於共享內存的函數爲shmat()、shmdt()、shmgat()、shmctl()。