1. 程式人生 > >程序間通訊---共享記憶體

程序間通訊---共享記憶體

一、IPC(Inter-Process Communication,程序間通訊)物件的介紹

System V 的IPC物件有共享記憶體、訊息佇列、訊號燈。

注意:在IPC的通訊模式下,不管是使用訊息佇列還是共享記憶體,甚至是訊號燈,每個IPC的物件都有唯一的名字,稱為”鍵”(key)。通過”鍵”,程序能夠識別所用的物件。”鍵”與IPC物件的關係就如同檔名稱於檔案,通過檔名,程序能夠讀寫檔案內的資料,甚至多個程序能夠公用一個檔案。而在 IPC的通訊模式下,通過”鍵”的使用也使得一個IPC物件能為多個程序所共用。

二、共享記憶體的介紹

<1>共享記憶體是一種最為高效的程序間通訊方式,程序可以直接讀寫記憶體,而不需要任何資料的拷貝。

<2>為了在多個程序間交換資訊,核心專門留出了一塊記憶體區,可以由需要訪問的程序將其對映到自己的私有地址空間。程序就可以直接讀寫這一塊記憶體而不需要進行資料的拷貝,從而大大提高效率。

<3>由於多個程序共享一段記憶體,因此也需要依靠某種同步機制。

三、共享記憶體的特點

四、共享記憶體的操作流程

<1>建立/開啟共享記憶體

<2>對映共享記憶體,即把指定的共享記憶體對映到程序的地址空間用於訪問

<3>撤銷共享記憶體對映

<4>刪除共享記憶體物件

五、相關 API

A.獲取一塊共享記憶體

功能:分配一塊共享記憶體

返回值:

呼叫成功返回一個shmid(類似開啟一個或建立一個檔案獲得的檔案描述符一樣);

呼叫失敗返回-1。

引數說明:

<1>key標識共享記憶體的鍵值(就像檔案的標識是檔名):0  / IPC_PRIVATE。

當key的取值為IPC_PRIVATE,則函式shmget()將建立一塊新的共享記憶體;

如果key的取值為0,而引數shmflg中設定了IPC_CREATE這個標誌,則同樣建立一塊新的共享記憶體。

通過這種方式分配的共享記憶體,一般用來親緣關係的程序間通訊。

注意:我們一般是通過ftok這個函式獲取鍵值

功能 : 獲取一個IPC物件的鍵值

引數說明:

pthname就是你指定檔名的路徑(該檔案必須是存在而且可以訪問的),一般情況我們都寫一個目錄

proj_id  : 和pthname一起完成建立鍵值的引數,雖然為int,但是隻有8個位元被使用。一般我們寫一個字元代替。

例如:

案例:

執行的結果:

<2>size是要建立共享記憶體的長度。所有的記憶體分配操作都是以頁為單位的。所以如果一個程序只申請一塊只有一個位元組的記憶體,記憶體也會分配整整一頁(在i386機器中一頁的預設大小PACE_SIZE = 4096位元組)。

<3>shmflg有效的標誌包括IPC_CREAT 和IPC_EXCL,他們的功能與open()的O_CREAT和O_EXCL相當。

IPC_CREAT      如果共享記憶體不存在,則建立一個共享記憶體,否則直接開啟已存在的
IPC_EXCL        只有在共享記憶體不存在的時候,新的共享記憶體才建立,否則就產生錯誤

例子一:假設鍵值為key,建立一個共享記憶體大小為4k,訪問許可權為066,如果已經存在則返回其標識號

123456 intshmid;if((shmid=shmget(key,4*1024,0666|IPC_CREAT))<0){perror("Fail to shmget");exit(EXIT_FAILURE)}

例子二、假設鍵值為key,建立一個共享記憶體大小為1k,訪問許可權為0666,如果已經存在則報錯

123456 intshmid;if((shmid=shmget(key,1024,0666|IPC_CREAT|IPC_EXCL))<0){perror("Fail to shmget");exit(EXIT_FAILURE);}
B.共享記憶體的對映

函式shmat將標識號為shmid共享記憶體對映到呼叫程序的地址空間中。

引數說明:

shmid  :  要對映的共享記憶體區識別符號

shmaddr  :  將共享記憶體對映到指定地址(若為NULL,則表示由系統自動完成對映)

shmflg  :  SHM_RDONLY  共享記憶體只讀

預設0:共享記憶體可讀寫。

返回值 :呼叫成功放回對映後的地址 ,出錯放回(void *)-1;

C.取消共享記憶體與使用者程序之間的對映

引數shmaddr是shmat對映成功放回的地址。

注意:當一個程序不再需要共享記憶體段時,它將呼叫shmdt()系統呼叫取消這個段,但是,這並不是從核心真正地刪除這個段,而是把相關shmid_ds結構的shm_nattch域的值減1,當這個值為0時,核心才從物理上刪除這個共享段。

D.控制共享記憶體

引數說明:

shmid  共享記憶體標識ID

cmd      IPC_STAT得到共享記憶體的狀態
IPC_SET改變共享記憶體的狀態
IPC_RMID刪除共享記憶體

buf  是一個結構體指標。IPC_STAT的時候,取得的狀態放在這個結構體中。如果要改變共享記憶體的狀態,用這個結構體指定;


注意:

1.IPC_RMID命令實際上不從核心刪除一個段,而是僅僅把這個段標記為刪除,實際的刪除發生最後一個程序離開這個共享段時。

2.當cmd為IPC_RMID時,第三個引數應為NULL。呵呵,大部分我們都是這樣做,用這個函式刪除共享記憶體。

案例探究:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 #include <sys/shm.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <semaphore.h>#include <fcntl.h>#include <sys/stat.h>#define BUFF_SIZE 1024intfather_do_work(intshmid){char*buf;void*shmaddr;sem_t *prsem;sem_t *pwsem;//有名訊號量if((prsem=sem_open("rsem",O_CREAT,0666,0))==SEM_FAILED){perror("Fail to sem open");return-1;}//有名訊號量if((pwsem=sem_open("wsem",O_CREAT,0666,1))==SEM_FAILED){perror("Fail to sem open");return-1;}//對映共享記憶體if((shmaddr=shmat(shmid,NULL,0))==(void*)-1){perror("Fail to shmat");exit(EXIT_FAILURE);}buf=(char*)shmaddr;while(1){if(sem_wait(pwsem)<0){perror("Fail to sem wait");break;}printf(">");fgets(buf,BUFF_SIZE,stdin);buf[strlen(buf)-1]='\0';if(sem_post(prsem)<0){perror("Fail to sem post");break;}if(strncmp(buf,"quit",4)==0){if(shmdt(shmaddr)<0){perror("Fail to shmaddr");exit(EXIT_FAILURE);}break;}usleep(500);}return0;}intchild_do_work(intshmid){char*buf;void*shmaddr;sem_t *prsem;sem_t *pwsem;//if((prsem=sem_open("rsem",O_CREAT,0666,0))==SEM_FAILED){perror("Fail to sem open");return-1;}if((pwsem=sem_open("wsem",O_CREAT,0666,1))==SEM_FAILED){perror("Fail to sem open");return-1;}//對映共享記憶體if((shmaddr=shmat(shmid,NULL,0))==(void*)-1){perror("Fail to shmat");exit(EXIT_FAILURE);}buf=(char*)shmaddr;while(1){if(sem_wait(prsem)<0){perror("Fail to prsem");break;}printf("read buf : %s.\n",buf);if(sem_post(pwsem)<0){perror("Fail to pwsem");break;}if(strncmp(buf,"quit",4)==0){if(shmdt(shmaddr)<0){perror("Fail to shmaddr");exit(EXIT_FAILURE);}break;}}return0;}intmain(){intshmid;intpid;void*shmaddr;//建立共享記憶體if((shmid=shmget(IPC_PRIVATE,BUFF_SIZE,0666|IPC_CREAT))<0){perror("Fail to shmget");exit(EXIT_FAILURE);}if((pid=fork())<0){perror("Fail to fork");exit(EXIT_FAILURE);}elseif(pid==0){child_do_work(shmid);}else{father_do_work(shmid);wait(NULL);if(shmctl(shmid,IPC_RMID,NULL)<0){perror("Fail to shmctl");exit(EXIT_FAILURE);}}exit(EXIT_SUCCESS);}

執行結果: