一、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,如果已經存在則返回其標識號
1 2 3 4 5 6 |
int shmid; if( (shmid = shmget(key,4 * 1024,0666 | IPC_CREAT)) < 0) { perror("Fail to shmget"); exit(EXIT_FAILURE) } |
例子二、假設鍵值為key,建立一個共享記憶體大小為1k,訪問許可權為0666,如果已經存在則報錯
1 2 3 4 5 6 |
int shmid; 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。呵呵,大部分我們都是這樣做,用這個函式刪除共享記憶體。
案例探究:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
#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 1024 int father_do_work(int shmid) { 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); } return 0; } int child_do_work(int shmid) { 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; } } return 0; } int main() { int shmid; int pid; 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); }else if(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); } |
執行結果: