1. 程式人生 > >Linux環境程序間通訊:共享記憶體

Linux環境程序間通訊:共享記憶體

共享記憶體簡介

共享記憶體允許兩個或多個程序共享一給定的儲存區。因為資料不需要在客戶程序和伺服器程序之間複製,所以這是最快的一種IPC。共享記憶體的方式有兩種:mmap()系統呼叫和系統V共享記憶體。

mmap()系統呼叫

mmap()系統呼叫使得程序之間通過對映同一個普通檔案實現共享記憶體。普通檔案被對映到程序地址空間後,程序可以向訪問普通記憶體一樣對檔案進行訪問,不必再呼叫read(),write()等操作。
注:實際上,mmap()系統呼叫並不是完全為了用於共享記憶體而設計的。它本身提供了不同於一般對普通檔案的訪問方式,程序可以像讀寫記憶體一樣對普通檔案的操作。而Posix或系統V的共享記憶體IPC則純粹用於共享目的,當然mmap()實現共享記憶體也是其主要應用之一。
mmap()系統呼叫原型如下:
#include <sys/mman.h>
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) 

addr引數用於指定對映儲存區的起始地址。通常將其設定為0,這表示由系統選擇該對映區的起始地址。此函式的返回地址是該對映區的起始地址。 fd指定要被對映檔案的描述符。在對映該檔案到一個地址空間之前,要先開啟該檔案。len是對映的位元組數。offset是要對映位元組在檔案中的起始偏移。 prot引數說明對對映儲存區的保護要求,可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執行), PROT_NONE(不可訪問)。 flags引數影響對映儲存區的多種屬性,由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。

mmap()用於共享記憶體的兩種方式

(1)使用普通檔案提供的記憶體對映:適用於任何程序之間; 此時,需要開啟或建立一個檔案,然後再呼叫mmap();典型呼叫程式碼如下:
fd=open(name, flag, mode);
if(fd<0)
	...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 
(2)使用特殊檔案提供匿名記憶體對映:適用於具有親緣關係的程序之間; 由於父子程序特殊的親緣關係,在父程序中先呼叫mmap(),然後呼叫fork()。那麼在呼叫fork()之後,子程序繼承父程序匿名對映後的地址空間,同樣也繼承mmap()返回的地址,這樣,父子程序就可以通過對映區域進行通訊了。注意,這裡不是一般的繼承關係。一般來說,子程序單獨維護從父程序繼承下來的一些變數。而mmap()返回的地址,卻由父子程序共同維護。 
對於具有親緣關係的程序實現共享記憶體最好的方式應該是採用匿名記憶體對映的方式。此時,不必指定具體的檔案,只要設定相應的標誌即可。

mmap()系統呼叫小結

1、最終被對映檔案的內容的長度不會超過檔案本身的初始大小,即對映不能改變檔案的大小;
2、可以用於程序通訊的有效地址空間大小大體上受限於被對映檔案的大小,但不完全受限於檔案大小。 
注:在linux中,記憶體的保護是以頁為基本單位的,即使被對映檔案只有一個位元組大小,核心也會為對映分配一個頁面大小的記憶體。當被對映檔案小於一個頁面大小時,程序可以對從mmap()返回地址開始的一個頁面大小進行訪問,而不會出錯;但是,如果對一個頁面以外的地址空間進行訪問,則導致錯誤發生。因此,可用於程序間通訊的有效地址空間大小不會超過檔案大小及一個頁面大小的和。
3、檔案一旦被對映後,呼叫mmap()的程序對返回地址的訪問是對某一記憶體區域的訪問,暫時脫離了磁碟上檔案的影響。所有對mmap()返回地址空間的操作只在記憶體中有意義,只有在呼叫了munmap()後或者msync()時,才把記憶體中的相應內容寫回磁碟檔案,所寫內容仍然不能超過檔案的大小。

系統V共享記憶體

系統V共享記憶體原理

系統V是通過對映特殊檔案系統shm中的檔案實現程序間的共享記憶體通訊。也就是說,每個共享記憶體區域對應特殊檔案系統shm中的一個檔案(這是通過shmid_kernel結構聯絡起來的)。
程序間需要共享的資料被放在一個叫做IPC共享記憶體區域的地方,所有需要訪問該共享區域的程序都要把該共享區域對映到本程序的地址空間中去。系統V共享記憶體通過shmget獲得或建立一個IPC共享記憶體區域,並返回相應的識別符號。核心在保證shmget獲得或建立一個共享記憶體區,初始化該共享記憶體區相應的shmid_kernel結構注同時,還將在特殊檔案系統shm中,建立並開啟一個同名檔案,並在記憶體中建立起該檔案的相應dentry及inode結構,新開啟的檔案不屬於任何一個程序(任何程序都可以訪問該共享記憶體區)。所有這一切都是系統呼叫shmget完成的。
注:每一個共享記憶體區都有一個控制結構struct shmid_kernel,shmid_kernel是共享記憶體區域中非常重要的一個數據結構,它是儲存管理和檔案系統結合起來的橋樑,定義如下:
struct shmid_kernel /* private to the kernel */
{	
	struct kern_ipc_perm	shm_perm;
	struct file *		shm_file;
	int			id;
	unsigned long		shm_nattch;
	unsigned long		shm_segsz;
	time_t			shm_atim;
	time_t			shm_dtim;
	time_t			shm_ctim;
	pid_t			shm_cprid;
	pid_t			shm_lprid;
};
該結構中最重要的一個域應該是shm_file,它儲存了將被對映檔案的地址。每個共享記憶體區物件都對應特殊檔案系統shm中的一個檔案,一般情況下,特殊檔案系統shm中的檔案是不能用read()、write()等方法訪問的,當採取共享記憶體的方式把其中的檔案對映到程序地址空間後,可直接採用訪問記憶體的方式對其訪問。 這裡我們採用[1]中的圖表給出與系統V共享記憶體相關資料結構: 正如訊息佇列和訊號燈一樣,核心通過資料結構struct ipc_ids shm_ids維護系統中的所有共享記憶體區域。上圖中的shm_ids.entries變數指向一個ipc_id結構陣列,而每個ipc_id結構陣列中有個指向kern_ipc_perm結構的指標。到這裡讀者應該很熟悉了,對於系統V共享記憶體區來說,kern_ipc_perm的宿主是shmid_kernel結構,shmid_kernel是用來描述一個共享記憶體區域的,這樣核心就能夠控制系統中所有的共享區域。同時,在shmid_kernel結構的file型別指標shm_file指向檔案系統shm中相應的檔案,這樣,共享記憶體區域就與shm檔案系統中的檔案對應起來。
在建立了一個共享記憶體區域後,還要將它對映到程序地址空間,系統呼叫shmat()完成此項功能。由於在呼叫shmget()時,已經建立了檔案系統shm中的一個同名檔案與共享記憶體區域相對應,因此,呼叫shmat()的過程相當於對映檔案系統shm中的同名檔案過程,原理與mmap()大同小異。

系統V共享記憶體API

對於系統V共享記憶體,主要有以下幾個API:shmget()、shmat()、shmdt()及shmctl()。
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
void *shmat(int shmid, const void *addr, int flag);
int shmdt(void *addr);

shmget()用來獲得共享記憶體區域的ID,如果不存在指定的共享區域就建立相應的區域。shmat()把共享記憶體區域對映到呼叫程序的地址空間中去,這樣,程序就可以方便地對共享區域進行訪問操作。shmdt()呼叫用來解除程序對共享記憶體區域的對映。shmctl實現對共享記憶體區域的控制操作。這裡我們不對這些系統呼叫作具體的介紹,讀者可參考相應的手冊頁面,後面的範例中將給出它們的呼叫方法。
注:shmget的內部實現包含了許多重要的系統V共享記憶體機制;shmat在把共享記憶體區域對映到程序空間時,並不真正改變程序的頁表。當程序第一次訪問記憶體對映區域訪問時,會因為沒有物理頁表的分配而導致一個缺頁異常,然後核心再根據相應的儲存管理機制為共享記憶體對映區域分配相應的頁表。

系統V共享記憶體限制

在/proc/sys/kernel/目錄下,記錄著系統V共享記憶體的一下限制,如一個共享記憶體區的最大位元組數shmmax,系統範圍內最大共享記憶體區識別符號數shmmni等,可以手工對其調整,但不推薦這樣做。

系統V共享記憶體小結

1、系統V共享記憶體中的資料,從來不寫入到實際磁碟檔案中去;而通過mmap()對映普通檔案實現的共享記憶體通訊可以指定何時將資料寫入磁碟檔案中。 注:前面講到,系統V共享記憶體機制實際是通過對映特殊檔案系統shm中的檔案實現的,檔案系統shm的安裝點在交換分割槽上,系統重新引導後,所有的內容都丟失。
2、系統V共享記憶體是隨核心持續的,即使所有訪問共享記憶體的程序都已經正常終止,共享記憶體區仍然存在(除非顯式刪除共享記憶體),在核心重新引導之前,對該共享記憶體區域的任何改寫操作都將一直保留。
3、通過呼叫mmap()對映普通檔案進行程序間通訊時,一定要注意考慮程序何時終止對通訊的影響。而通過系統V共享記憶體實現通訊的程序則不然。

參考文獻