1. 程式人生 > >【Linux】Linux程序通訊與System V IPC機制

【Linux】Linux程序通訊與System V IPC機制

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()。