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

Linux環境程序間通訊(五): 共享記憶體(下)(轉)

轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html, 作者:鄭彥興

系統呼叫mmap()通過對映一個普通檔案實現共享記憶體。系統V則是通過對映特殊檔案系統shm中的檔案實現程序間的共享記憶體通訊。也就是說,每個共享記憶體區域對應特殊檔案系統shm中的一個檔案(這是通過shmid_kernel結構聯絡起來的),後面還將闡述。

程序間需要共享的資料被放在一個叫做IPC共享記憶體區域的地方,所有需要訪問該共享區域的程序都要把該共享區域對映到本程序的地址空間 中去。系統V共享記憶體通過shmget獲得或建立一個IPC共享記憶體區域,並返回相應的識別符號。核心在保證shmget獲得或建立一個共享記憶體區,初始化 該共享記憶體區相應的shmid_kernel結構注同時,還將在特殊檔案系統shm中,建立並開啟一個同名檔案,並在記憶體中建立起該檔案的相應 dentry及inode結構,新開啟的檔案不屬於任何一個程序(任何程序都可以訪問該共享記憶體區)。所有這一切都是系統呼叫shmget完成的。

注:每一個共享記憶體區都有一個控制結構struct shmid_kernel,shmid_kernel是共享記憶體區域中非常重要的一個數據結構,它是儲存管理和檔案系統結合起來的橋樑,定義如下:


  1. struct shmid_kernel /* private to the kernel */
  2. {    
  3.     struct kern_ipc_perm    shm_perm;
  4.     struct file *        shm_file;
  5.     int            id;
  6.     unsigned long        shm_nattch;
  7.     unsigned long        shm_segsz;

  8.     time_t            shm_atim;
  9.     time_t            shm_dtim;
  10.     time_t            shm_ctim;
  11.     pid_t            shm_cprid;
  12.     pid_t            shm_lprid;
  13. };

該結構中最重要的一個域應該是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:shmget()、shmat()、shmdt()及shmctl()。


  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>

shmget()用來獲得共享記憶體區域的ID,如果不存在指定的共享區域就建立相應的區域。shmat()把共享記憶體區域對映到呼叫進 程的地址空間中去,這樣,程序就可以方便地對共享區域進行訪問操作。shmdt()呼叫用來解除程序對共享記憶體區域的對映。shmctl實現對共享記憶體區 域的控制操作。這裡我們不對這些系統呼叫作具體的介紹,讀者可參考相應的手冊頁面,後面的範例中將給出它們的呼叫方法。

注:shmget的內部實現包含了許多重要的系統V共享記憶體機制;shmat在把共享記憶體區域對映到程序空間時,並不真正改變程序的頁 表。當程序第一次訪問記憶體對映區域訪問時,會因為沒有物理頁表的分配而導致一個缺頁異常,然後核心再根據相應的儲存管理機制為共享記憶體對映區域分配相應的 頁表。

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

在[2]中,給出了這些限制的測試方法,不再贅述。

本部分將給出系統V共享記憶體API的使用方法,並對比分析系統V共享記憶體機制與mmap()對映普通檔案實現共享記憶體之間的差異,首先給出兩個程序通過系統V共享記憶體通訊的範例:


  1. /***** testwrite.*******/
  2. #include <sys/ipc.h>
  3. #include <sys/shm.h>
  4. #include <sys/types.h>
  5. #include <unistd.h>
  6. typedef struct{
  7.     char name[4];
  8.     int age;
  9. } people;
  10. main(int argc, char** argv)
  11. {
  12.     int shm_id,i;
  13.     key_t key;
  14.     char temp;
  15.     people *p_map;
  16.     char* name = "/dev/shm/myshm2";
  17.     key = ftok(name,0);
  18.     if(key==-1)
  19.         perror("ftok error");
  20.     shm_id=shmget(key,4096,IPC_CREAT);    
  21.     if(shm_id==-1)
  22.     {
  23.         perror("shmget error");
  24.         return;
  25.     }
  26.     p_map=(people*)shmat(shm_id,NULL,0);
  27.     temp='a';
  28.     for(= 0;i<10;i++)
  29.     {
  30.         temp+=1;
  31.         memcpy((*(p_map+i)).name,&temp,1);
  32.         (*(p_map+i)).age=20+i;
  33.     }
  34.     if(shmdt(p_map)==-1)
  35.         perror(" detach error ");
  36. }
  37. /********** testread.************/
  38. #include <sys/ipc.h>
  39. #include <sys/shm.h>
  40. #include <sys/types.h>
  41. #include <unistd.h>
  42. typedef struct{
  43.     char name[4];
  44.     int age;
  45. } people;
  46. main(int argc, char** argv)
  47. {
  48.     int shm_id,i;
  49.     key_t key;
  50.     people *p_map;
  51.     char* name = "/dev/shm/myshm2";
  52.     key = ftok(name,0);
  53.     if(key == -1)
  54.         perror("ftok error");
  55.     shm_id = shmget(key,4096,IPC_CREAT);    
  56.     if(shm_id == -1)
  57.     {
  58.         perror("shmget error");
  59.         return;
  60.     }
  61.     p_map = (people*)shmat(shm_id,NULL,0);
  62.     for(= 0;i<10;i++)
  63.     {
  64.     printf( "name:%s\n",(*(p_map+i)).name );
  65.     printf( "age %d\n",(*(p_map+i)).age );
  66.     }
  67.     if(shmdt(p_map) == -1)
  68.         perror(" detach error ");
  69. }

testwrite.c建立一個系統V共享記憶體區,並在其中寫入格式化資料;testread.c訪問同一個系統V共享記憶體區,讀出其 中的格式化資料。分別把兩個程式編譯為testwrite及testread,先後執行./testwrite及./testread 則./testread輸出結果如下:


  1. name: b    age 20;    name: c    age 21;    name: d    age 22;    name: e    age 23;    name: f    age 24;
  2. name: g    age 25;    name: h    age 26;    name: I    age 27;    name: j    age 28;    name: k    age 29;

通過對試驗結果分析,對比系統V與mmap()對映普通檔案實現共享記憶體通訊,可以得出如下結論:

1、 系統V共享記憶體中的資料,從來不寫入到實際磁碟檔案中去;而通過mmap()對映普通檔案實現的共享記憶體通訊可以指定何時將資料寫入磁碟檔案中。 注:前面講到,系統V共享記憶體機制實際是通過對映特殊檔案系統shm中的檔案實現的,檔案系統shm的安裝點在交換分割槽上,系統重新引導後,所有的內容都丟失。

2、 系統V共享記憶體是隨核心持續的,即使所有訪問共享記憶體的程序都已經正常終止,共享記憶體區仍然存在(除非顯式刪除共享記憶體),在核心重新引導之前,對該共享記憶體區域的任何改寫操作都將一直保留。

3、 通過呼叫mmap()對映普通檔案進行程序間通訊時,一定要注意考慮程序何時終止對通訊的影響。而通過系統V共享記憶體實現通訊的程序則不然。 注:這裡沒有給出shmctl的使用範例,原理與訊息佇列大同小異。

結論:

共享記憶體允許兩個或多個程序共享一給定的儲存區,因為資料不需要來回複製,所以是最快的一種程序間通訊機制。共享記憶體可以通過 mmap()對映普通檔案(特殊情況下還可以採用匿名對映)機制實現,也可以通過系統V共享記憶體機制實現。應用介面和原理很簡單,內部機制複雜。為了實現 更安全通訊,往往還與訊號燈等同步機制共同使用。

共享記憶體涉及到了儲存管理以及檔案系統等方面的知識,深入理解其內部機制有一定的難度,關鍵還要緊緊抓住核心使用的重要資料結構。系統 V共享記憶體是以檔案的形式組織在特殊檔案系統shm中的。通過shmget可以建立或獲得共享記憶體的識別符號。取得共享記憶體識別符號後,要通過shmat將這 個記憶體區對映到本程序的虛擬地址空間。


參考資料

[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 對各主題闡述得重點突出,脈絡清晰。

[2] UNIX網路程式設計第二卷:程序間通訊,作者:W.Richard Stevens,譯者:楊繼張,清華大學出版社。對mmap()有詳細闡述。

[3] Linux核心原始碼情景分析(上),毛德操、胡希明著,浙江大學出版社,給出了mmap()相關的原始碼分析。

[4]shmget、shmat、shmctl、shmdt手冊