程序間通訊---共享記憶體
一、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);} |
執行結果: