?
8.5??共享內(nèi)存
8.5.1??共享內(nèi)存概述
可以說(shuō),共享內(nèi)存是一種最為高效的進(jìn)程間通信方式。因?yàn)檫M(jìn)程可以直接讀寫(xiě)內(nèi)存,不需要任何數(shù)據(jù)的復(fù)制。為了在多個(gè)進(jìn)程間交換信息,內(nèi)核專(zhuān)門(mén)留出了一塊內(nèi)存區(qū)。這段內(nèi)存區(qū)可以由需要訪(fǎng)問(wèn)的進(jìn)程將其映射到自己的私有地址空間。因此,進(jìn)程就可以直接讀寫(xiě)這一內(nèi)存區(qū)而不需要進(jìn)行數(shù)據(jù)的復(fù)制,從而大大提高了效率。當(dāng)然,由于多個(gè)進(jìn)程共享一段內(nèi)存,因此也需要依靠某種同步機(jī)制,如互斥鎖和信號(hào)量等(請(qǐng)參考本章的共享內(nèi)存實(shí)驗(yàn))。其原理示意圖如圖8.8所示。
圖8.8??共享內(nèi)存原理示意圖
8.5.2??共享內(nèi)存的應(yīng)用
1.函數(shù)說(shuō)明
共享內(nèi)存的實(shí)現(xiàn)分為兩個(gè)步驟,第一步是創(chuàng)建共享內(nèi)存,這里用到的函數(shù)是shmget(),也就是從內(nèi)存中獲得一段共享內(nèi)存區(qū)域,第二步映射共享內(nèi)存,也就是把這段創(chuàng)建的共享內(nèi)存映射到具體的進(jìn)程空間中,這里使用的函數(shù)是shmat()。到這里,就可以使用這段共享內(nèi)存了,也就是可以使用不帶緩沖的I/O讀寫(xiě)命令對(duì)其進(jìn)行操作。除此之外,當(dāng)然還有撤銷(xiāo)映射的操作,其函數(shù)為shmdt()。這里就主要介紹這3個(gè)函數(shù)。
2.函數(shù)格式
表8.20列舉了shmget()函數(shù)的語(yǔ)法要點(diǎn)。
表8.20 shmget()函數(shù)語(yǔ)法要點(diǎn)
所需頭文件 |
#include?<sys/types.h> |
函數(shù)原型 |
int?shmget(key_t?key,?int?size,?int?shmflg) |
函數(shù)傳入值 |
key:共享內(nèi)存的鍵值,多個(gè)進(jìn)程可以通過(guò)它訪(fǎng)問(wèn)同一個(gè)共享內(nèi)存,其中有個(gè)特殊值IPC_PRIVATE。它用于創(chuàng)建當(dāng)前進(jìn)程的私有共享內(nèi)存 |
size:共享內(nèi)存區(qū)大小 |
|
shmflg:同open()函數(shù)的權(quán)限位,也可以用八進(jìn)制表示法 |
|
函數(shù)返回值 |
成功:共享內(nèi)存段標(biāo)識(shí)符 |
出錯(cuò):-1 |
表8.21列舉了shmat()函數(shù)的語(yǔ)法要點(diǎn)。
表8.21 shmat()函數(shù)語(yǔ)法要點(diǎn)
所需頭文件 |
#include?<sys/types.h> |
|
函數(shù)原型 |
char?*shmat(int?shmid,?const?void?*shmaddr,?int?shmflg) |
|
函數(shù)傳入值 |
shmid:要映射的共享內(nèi)存區(qū)標(biāo)識(shí)符 |
|
shmaddr:將共享內(nèi)存映射到指定地址(若為0則表示系統(tǒng)自動(dòng)分配地址并把該段共享內(nèi)存映射到調(diào)用進(jìn)程的地址空間) |
||
shmflg? |
SHM_RDONLY:共享內(nèi)存只讀 |
|
默認(rèn)0:共享內(nèi)存可讀寫(xiě) |
||
函數(shù)返回值 |
成功:被映射的段地址 |
|
出錯(cuò):-1 |
表8.22列舉了shmdt()函數(shù)的語(yǔ)法要點(diǎn)。
表8.22 shmdt()函數(shù)語(yǔ)法要點(diǎn)
所需頭文件 |
#include?<sys/types.h> |
函數(shù)原型 |
int?shmdt(const?void?*shmaddr) |
函數(shù)傳入值 |
shmaddr:被映射的共享內(nèi)存段地址 |
函數(shù)返回值 |
成功:0 |
出錯(cuò):-1 |
?
3.使用實(shí)例
該實(shí)例說(shuō)明如何使用基本的共享內(nèi)存函數(shù)。首先是創(chuàng)建一個(gè)共享內(nèi)存區(qū)(采用的共享內(nèi)存的鍵值為IPC_PRIVATE,是因?yàn)楸緦?shí)例中創(chuàng)建的共享內(nèi)存是父子進(jìn)程之間的共用部分),之后創(chuàng)建子進(jìn)程,在父子兩個(gè)進(jìn)程中將共享內(nèi)存分別映射到各自的進(jìn)程地址空間之中。
父進(jìn)程先等待用戶(hù)輸入,然后將用戶(hù)輸入的字符串寫(xiě)入到共享內(nèi)存,之后往共享內(nèi)存的頭部寫(xiě)入“WROTE”字符串表示父進(jìn)程已成功寫(xiě)入數(shù)據(jù)。子進(jìn)程一直等到共享內(nèi)存的頭部字符串為“WROTE”,然后將共享內(nèi)存的有效數(shù)據(jù)(在父進(jìn)程中用戶(hù)輸入的字符串)在屏幕上打印。父子兩個(gè)進(jìn)程在完成以上工作之后,分別解除與共享內(nèi)存的映射關(guān)系。
最后在子進(jìn)程中刪除共享內(nèi)存。因?yàn)楣蚕韮?nèi)存自身并不提供同步機(jī)制,所以應(yīng)該額外實(shí)現(xiàn)不同進(jìn)程之間的同步(例如:信號(hào)量)。為了簡(jiǎn)單起見(jiàn),在本實(shí)例中用標(biāo)志字符串來(lái)實(shí)現(xiàn)非常簡(jiǎn)單的父子進(jìn)程之間的同步。
這里要介紹的一個(gè)命令是ipcs,這是用于報(bào)告進(jìn)程間通信機(jī)制狀態(tài)的命令。它可以查看共享內(nèi)存、消息隊(duì)列等各種進(jìn)程間通信機(jī)制的情況,這里使用了system()函數(shù)用于調(diào)用shell命令“ipcs”。程序源代碼如下所示:
/*?shmem.c?*/
#include?<sys/types.h>
#include?<sys/ipc.h>
#include?<sys/shm.h>
#include?<stdio.h>
#include?<stdlib.h>
#include?<string.h>
#define?BUFFER_SIZE?2048
int?main()
{
????pid_t?pid;
????int?shmid;
????char?*shm_addr;
????char?flag[]?=?"WROTE";
????char?*buff;
????
????/*?創(chuàng)建共享內(nèi)存?*/
????if?((shmid?=?shmget(IPC_PRIVATE,?BUFFER_SIZE,?0666))?<?0)
????{
????????perror("shmget");
????????exit(1);
????}
????else
????{
????????printf("Create?shared-memory:?%dn",shmid);
????}
????
????/*?顯示共享內(nèi)存情況?*/
????system("ipcs?-m");
????
????pid?=?fork();
????if?(pid?==?-1)
????{
????????perror("fork");
????????exit(1);
????}
????else?if?(pid?==?0)?/*?子進(jìn)程處理?*/
????{
????????/*映射共享內(nèi)存*/
????????if?((shm_addr?=?shmat(shmid,?0,?0))?==?(void*)-1)
????????{
????????????perror("Child:?shmat");
????????????exit(1);
????????}
????????else
????????{
????????????printf("Child:?Attach?shared-memory:?%pn",?shm_addr);
????????}
????????system("ipcs?-m");
????????
????????/*?通過(guò)檢查在共享內(nèi)存的頭部是否標(biāo)志字符串"WROTE"來(lái)確認(rèn)
父進(jìn)程已經(jīng)向共享內(nèi)存寫(xiě)入有效數(shù)據(jù)?*/
????????while?(strncmp(shm_addr,?flag,?strlen(flag)))
????????{
????????????printf("Child:?Wait?for?enable?data...n");
????????????sleep(5);
????????}
????????
????????/*?獲取共享內(nèi)存的有效數(shù)據(jù)并顯示?*/
????????strcpy(buff,?shm_addr?+?strlen(flag));
????????printf("Child:?Shared-memory?:%sn",?buff);
????????
????????/*?解除共享內(nèi)存映射?*/
????????if?((shmdt(shm_addr))?<?0)
????????{
????????????perror("shmdt");
????????????exit(1);
????????}
????????else
????????{
????????????printf("Child:?Deattach?shared-memoryn");
????????}
????????system("ipcs?-m");
??????????
????????/*?刪除共享內(nèi)存?*/
????????if?(shmctl(shmid,?IPC_RMID,?NULL)?==?-1)
????????{
????????????perror("Child:?shmctl(IPC_RMID)n");
????????????exit(1);
????????}
????????else
????????{
????????????printf("Delete?shared-memoryn");
????????}
????????
????????system("ipcs?-m");
????}
????else?/*?父進(jìn)程處理?*/
????{
????????/*映射共享內(nèi)存*/
????????if?((shm_addr?=?shmat(shmid,?0,?0))?==?(void*)-1)
????????{
????????????perror("Parent:?shmat");
????????????exit(1);
????????}
????????else
????????{
????????????printf("Parent:?Attach?shared-memory:?%pn",?shm_addr);
????????}
????????
????????sleep(1);
????????printf("nInput?some?string:n");
????????fgets(buff,?BUFFER_SIZE,?stdin);
????????strncpy(shm_addr?+?strlen(flag),?buff,?strlen(buff));
????????strncpy(shm_addr,?flag,?strlen(flag));
????????
????????/*?解除共享內(nèi)存映射?*/
????????if?((shmdt(shm_addr))?<?0)
????????{
????????????perror("Parent:?shmdt");
????????????exit(1);
????????}
????????else
????????{
????????????printf("Parent:?Deattach?shared-memoryn");
????????}
????????system("ipcs?-m");
????????
????????waitpid(pid,?NULL,?0);????????
????????printf("Finishedn");
????}
????exit(0);
}
?
下面是運(yùn)行結(jié)果。從該結(jié)果可以看出,nattch的值隨著共享內(nèi)存狀態(tài)的變化而變化,共享內(nèi)存的值根據(jù)不同的系統(tǒng)會(huì)有所不同。
$?./shmem
Create?shared-memory:?753665
/*?在剛創(chuàng)建共享內(nèi)存時(shí)(尚未有任何地址映射)共享內(nèi)存的情況?*/
------?Shared?Memory?Segments?--------
key????????shmid??????owner??????perms??????bytes??????nattch?????status??????
0x00000000?753665?????david?????666????????2048???????0???????????????????????
Child:?Attach?shared-memory:?0xb7f59000?/*?共享內(nèi)存的映射地址?*/
Parent:?Attach?shared-memory:?0xb7f59000
/*?在父子進(jìn)程中進(jìn)行共享內(nèi)存的地址映射之后共享內(nèi)存的情況*/
------?Shared?Memory?Segments?--------
key????????shmid??????owner??????perms??????bytes??????nattch?????status??????
0x00000000?753665?????david?????666????????2048???????2???????????????????????
Child:?Wait?for?enable?data...
Input?some?string:
Hello?/*?用戶(hù)輸入字符串“Hello”?*/
Parent:?Deattach?shared-memory
/*?在父進(jìn)程中解除共享內(nèi)存的映射關(guān)系之后共享內(nèi)存的情況?*/
------?Shared?Memory?Segments?--------
key????????shmid??????owner??????perms??????bytes??????nattch?????status??????
0x00000000?753665?????david?????666????????2048???????1???????????????????????
/*在子進(jìn)程中讀取共享內(nèi)存的有效數(shù)據(jù)并打印*/
Child:?Shared-memory?:hello
Child:?Deattach?shared-memory
/*?在子進(jìn)程中解除共享內(nèi)存的映射關(guān)系之后共享內(nèi)存的情況?*/
------?Shared?Memory?Segments?--------
key????????shmid??????owner??????perms??????bytes??????nattch?????status??????
0x00000000?753665?????david?????666????????2048???????0???????????????????????
Delete?shared-memory
/*?在刪除共享內(nèi)存之后共享內(nèi)存的情況?*/
------?Shared?Memory?Segments?--------
key????????shmid??????owner??????perms??????bytes??????nattch?????status??????
Finished