標籤:style blog http color 使用 os io strong
轉載請註明出處:http://blog.csdn.net/suool/article/details/38515863
共用記憶體可以被描述成記憶體一個地區(段)的映射,這個地區可以被更多的進程所共用。這是IPC機制中最快的一種形式,因為它不需要中間環節,而是把資訊直接從一個記憶體段映射到調用進程的地址空間。 一個段可以直接由一個進程建立,隨後,可以有任意多的進程對其讀和寫。但是,一旦記憶體被共用之後,對共用記憶體的訪問同步需要由其他 IPC 機制,例如訊號量來實現。象所有的System V IPC 對象一樣,Linux 對共用記憶體的存取是通過對訪問鍵和存取權限的檢查來控制的.
共用空間資料結構
<bits/shm.h>/* 串連共用記憶體區的進程數的資料類型 */typedef unsigned long int shmatt_t;struct shmid_ds{ struct ipc_perm shm_perm; /* operation permission struct */ size_t shm_segsz; /* 共用儲存段的最大位元組數 */ __time_t shm_atime; /* time of last shmat() */ __time_t shm_dtime; /* time of last shmdt() */ __time_t shm_ctime; /* time of last change by shmctl() */ __pid_t shm_cpid; /* pid of creator */ __pid_t shm_lpid; /* pid of last shmop */ shmatt_t shm_nattch; /* 串連共用記憶體區的進程數 *///保留欄位#if __WORDSIZE == 32 unsigned long int __unused1;unsigned long int __unused2;unsigned long int __unused3;#endif unsigned long int __unused4; unsigned long int __unused5;};兩個進程在使用此共用空間之前需要在進程地址空間與共用記憶體空間之間建立聯絡,即是將共用記憶體空間掛在到進程中.
同時在使用共用記憶體進行資料存取的時候,為了避免出現讀取交叉等錯誤,常常需要使用二元訊號量來同步兩個進程實現對共用記憶體的讀取操作.
同時對於共用記憶體有一定的系統限制:
#include <bits/shm.h>struct shminfo{ unsigned long int shmmax;//一個共用記憶體區的最大位元組數 unsigned long int shmmin;//一個共用記憶體區的最小位元組數 unsigned long int shmmni;//系統範圍內的共用記憶體區對象的最大個數 unsigned long int shmseg;//每個進程串連的最大共用記憶體區的數目 unsigned long int shmall;//系統範圍內的共用記憶體區的最大頁數 unsigned long int __unused1; unsigned long int __unused2; unsigned long int __unused3; unsigned long int __unused4;};在Linux 下shmctl中可以指定IPC_INFO來擷取上面結構所示的系統範圍內的限制。在Linux下,具體的限制值可以通過sysctl來查看,如下:
[[email protected] program]# sysctl -a | grep shm...kernel.shmmni = 4096//系統範圍內的共用記憶體區對象的最大個數kernel.shmall = 4294967296//系統範圍內的共用記憶體區的最大頁數kernel.shmmax = 68719476736//一個共用記憶體區的最大位元組數
一般情況下不需要對System V共用記憶體區的系統限制進程修改,因為基本可以滿足應用需求,如果要在系統範圍內對核心限制進行修改,在Linux下面可以通過修改/etc/sysctl.conf 核心參數設定檔,然後配合sysctl命令來對核心參數進行設定。例如下面樣本:
[[email protected] program]#echo "kernel.shmmni= 1000" >>/etc/sysctl.conf[[email protected] program]#sysctl -p[[email protected] program]#sysctl -a |grep shmkernel.shmmni = 1000kernel.shmall = 4294967296kernel.shmmax = 68719476736
共用記憶體的處理過程
某個進程第一次訪問共用虛擬記憶體時將產生缺頁異常。這時,Linux找出描述該記憶體的 vm_area_struct 結構,該結構中包含用來處理這種共用虛擬記憶體段的處理函數地址。共用記憶體缺頁異常處理代碼對shmid_ds 的頁表項表進行搜尋,以便查看是否存在該共用虛擬記憶體的頁表項。如果沒有,系統將分配一個物理頁並建立頁表項,該頁表項加入 shmid_ds 結構的同時也添加到進程的頁表中。這就意味著當下一個進程試圖訪問這頁記憶體時出現缺頁異常,共用記憶體的缺頁異常處理代碼則把新建立的物理頁給這個進程。因此說,第一個進程對共用記憶體的存取引起建立新的物理頁面,而其它進程對共用記憶體的存取引起把那個頁加添加到它們的地址空間。
當某個進程不再共用其虛擬記憶體時,利用系統調用將共用段從自己的虛擬位址地區中移去,並更新進程頁表。當最後一個進程釋放了共用段之後,系統將釋放給共用段所分配的物理頁。
當共用的虛擬記憶體沒有被鎖定到實體記憶體時,共用記憶體也可能會被交換到交換區中。
System V共用記憶體區的建立和開啟
下面是建立共用記憶體的shmget函數的介面以及說明:
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg); //成功返回共用記憶體標識符,失敗返回-1
shmget函數用於建立或開啟一個共用記憶體區對象,shmget成功調用會返回一個共用記憶體區的標識符,供其它的共用記憶體區操作函數使用。
key:用於建立共用記憶體區的鍵值,這個在前面其他System IPC建立的時候已經討論過了,System IPC都有一個key,作為IPC的外部標識符,建立成功後返回的描述符作為IPC的內部標識符使用。key的主要目的就是使不同進程在同一IPC匯合。key具體說可以有三種方式產生:
- 不同的進程約定好的一個值;
- 通過相同的路徑名和項目ID,調用ftok()函數,產生一個鍵;
- 還可以設定為IPC_PRIVATE,這樣就會建立一個新的,唯一的IPC對象;然後將返回的描述符通過某種方式傳遞給其他進程;
size:指定建立共用記憶體區的大小,單位是位元組。如果實際操作為建立一個共用記憶體區時,必須指定一個非0值,如果實際操作是訪問一個已存在的共用記憶體區,那麼size應為0。
shmflg:指定建立或開啟訊息佇列的標誌和讀寫權限(ipc_perm中的mode成員)。我們知道System V IPC定義了自己的操作標誌和使用權限設定標誌,而且都是通過該參數傳遞,這和open函數存在差別,open函數第三個參數mode用於傳遞檔案的許可權標誌。System V IPC的操作標誌包含:IPC_CREAT,IPC_EXCL。讀寫權限如:
圖1 System V共用記憶體區的讀寫權限標誌
System V共用記憶體區在建立後,該size大小的記憶體區會被初始化為0,這和POSIX共用記憶體不同,POSIX標準並沒有規定新建立的POSIX共用記憶體區的初始內容。
System V共用記憶體區的串連和斷接
通過shmctl建立或開啟共用記憶體區對象後,並沒有將該共用記憶體區映射到調用進程的地址空間中,所以無法訪問該共用記憶體區,需要通過shmat函數,將該共用記憶體區串連到調用進程的地址空間中,才能進行訪問,當該進程完成對該共用記憶體區的訪問後,可以調用shmdt斷接這個共用記憶體區,當然進程結束後會自動斷接所有串連的共用記憶體區。下面是shmat和shmdt函數的介面以及說明:
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg); //成功返回映射區的起始地址,失敗返回-1int shmdt(const void *shmaddr); //成功返回0,失敗返回-1
shmat用於將一個共用記憶體區串連到調用進程的地址空間中。
shmid:開啟的System V共用記憶體對象的標識符;
shmaddr和shmflg參數共同決定了共用記憶體區串連到調用進程的具體地址,規則如下:
- shmaddr為空白指標:串連的地址由系統核心決定,這是推薦的方法,具有可移植性。
- shmaddr非空:此時還要根據shmflg參數是否指定SHM_RND標誌進行判斷:
- 沒有指定SHM_RND:共用記憶體區串連到調用進程的shmaddr指定的地址;
- 指定SHM_RND:共用記憶體區串連到shmaddr指定的地址向下舍入SHMLBA的位置。
shmflg:除了上面說的SHM_RND外,還有可以指定SHM_RDONLY標誌,限定唯讀訪問。一般該標誌置為0。
shmdt用於將一個共用記憶體區從該進程內斷接,當一個進程終止時,它串連的所有共用記憶體區會自動斷接。
System V共用記憶體區的控制操作
shmctl函數可以對共用記憶體區進行多種控制操作,下面是shmctl函數的介面以及說明:
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf); //成功返回0,失敗返回-1
shmid:共用記憶體區的標識符。
cmd:對共用記憶體區控制操作的命令,Open Group 的SUS定義了一下三個操作命令:
- IPC_SET:按第三個參數buf所指定的內容,設定共用記憶體區的操作許可權欄位的:shm_perm.uid,shm_perm.gid和shm_perm.mode。此命令只能由以下進程執行:有效使用者ID等於shm_perm.uid或shm_perm.cuid,以及有超級使用者權限的進程。
- IPC_STAT:擷取共用記憶體區的shmid_ds結構,存放到傳入的第三個參數中。
- IPC_RMID:從系統核心中刪除該共用記憶體區。因為每個共用記憶體區有一個串連資料的計數,除非串連該共用記憶體區的最後一個進程斷接或終止,否則該共用記憶體區不會被實際刪除。這和其他的System V IPC,例如System V訊息佇列的刪除差別很大,倒是和POSIX IPC的xxx_unlink刪除操作很相識。調用該命令後,該共用記憶體區標識符不能再繼續被串連。此命令也只能由以下進程執行:有效使用者ID等於shm_perm.uid或shm_perm.cuid,以及有超級使用者權限的進程。
在Linux中還定義了其他的控制命令,如:IPC_INFO,SHM_INFO,SHM_LOCK,SHM_UNLOCK等,具體可以參考Linux手冊。
下面是建立System V共用記憶體區和查看其屬性的測試代碼:
#include <iostream>#include <cstring>#include <errno.h>#include <unistd.h>#include <fcntl.h>#include <sys/shm.h>using namespace std;#define PATH_NAME "/tmp/shm"int main(){ int fd; if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0) { cout<<"open file "<<PATH_NAME<<"failed."; cout<<strerror(errno)<<endl; return -1; } close(fd); key_t key = ftok(PATH_NAME, 0); int shmID; if ((shmID = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0) { cout<<"shmget failed..."<<strerror(errno)<<endl; return -1; } shmid_ds shmInfo; shmctl(shmID, IPC_STAT, &shmInfo); cout<<"shm key:0x"<<hex<<key<<dec<<endl; cout<<"shm id:"<<shmID<<endl; cout<<"shm_segsz:"<<shmInfo.shm_segsz<<endl; cout<<"shm_nattch:"<<shmInfo.shm_nattch<<endl; return 0;}
共用記憶體樣本應用
此樣本程式實現沒有親緣關係的兩個進程之間通過共用記憶體通訊,同時使用訊號量保證兩個進程的讀寫同步:即是發送方在寫共用記憶體的時候,接收端不能讀記憶體區,反之亦同 .
在代碼中,使用共用記憶體傳遞資料,使用訊號量同步讀寫端基本思路如下:
- 首先設定訊號量初始為0,表示沒有寫入資料,不可讀.
- 發送端在訊號量位0的時候寫資料到共用記憶體,並阻塞讀進程,寫入完成後,設定訊號量位1,此時可以讀資料,不能寫資料.
- 接收端在訊號量位1的時候讀出記憶體區的資料並阻塞寫入端,讀出完成後,設定訊號量的值位0,表示獨處完成,可以再次寫資料.
下面是編譯和運行結果:
發送端:
接收端
代碼如下:
發送:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/shm.h>#include <sys/ipc.h>#include <sys/sem.h>#include <string.h>int main(int argc,char *argv[]){ int running=1; int shid; int semid; int value; char *sharem=NULL; struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_flg = SEM_UNDO; if((semid=semget((key_t)123456,1,0666|IPC_CREAT))==-1) // 建立訊號量 { perror("semget"); exit(EXIT_FAILURE); } if (semctl(semid, 0, SETVAL, 0) == -1) // 設定初始值位0 { printf("sem init error"); if(semctl(semid,0,IPC_RMID,0)!=0) { perror("semctl"); exit(EXIT_FAILURE); } exit(EXIT_FAILURE); } shid=shmget((key_t)654321,(size_t)2048,0600|IPC_CREAT); // 建立共用記憶體(讀取id) if(shid==-1) { perror("shmget"); exit(EXIT_FAILURE); } sharem=shmat(shid,NULL,0); // 掛載共用記憶體到進程 if(sharem==NULL) { perror("shmat"); exit(EXIT_FAILURE); } while(running) { if((value=semctl( semid, 0, GETVAL ))==0) // 讀取為0時 { printf("write data operate\n"); // 可寫 printf("please input something:"); scanf("%s",sharem); // 讀取到共用記憶體 sem_b.sem_op = 1; // 設定訊號量加一操作 if (semop(semid, &sem_b, 1) == -1) // 執行加一操作 { fprintf(stderr, "semaphore_p failed\n"); exit(EXIT_FAILURE); } } if(strcmp(sharem,"end")==0) // 輸入end時結束 running--; }shmdt(sharem); // 卸載記憶體return 0;}
接受:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <string.h>#include <sys/ipc.h>#include <sys/shm.h>#include <sys/sem.h>int main(int argc,char *argv[]){ int running=1; char *shm_p=NULL; int shmid; int semid; int value; struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_flg = SEM_UNDO; if((semid=semget((key_t)123456,1,0666|IPC_CREAT))==-1) { perror("semget"); exit(EXIT_FAILURE); } shmid=shmget((key_t)654321,(size_t)2048,0600|IPC_CREAT); if(shmid==-1) { perror("shmget"); exit(EXIT_FAILURE); } shm_p=shmat(shmid,NULL,0); // 掛接 if(shm_p==NULL) { perror("shmat"); exit(EXIT_FAILURE); } while(running) { if((value=semctl( semid, 0, GETVAL ))==1) { printf("read data operate\n"); sem_b.sem_op = -1; // 減一 if (semop(semid, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); exit(EXIT_FAILURE); } printf("%s\n",shm_p); } if(strcmp(shm_p,"end")==0) running--; } shmdt(shm_p); // 卸載 if(shmctl(shmid,IPC_RMID,0)!=0) // 刪除共用記憶體 { perror("shmctl"); exit(EXIT_FAILURE); } if(semctl(semid,0,IPC_RMID,0)!=0) // 刪除訊號量 { perror("semctl"); exit(EXIT_FAILURE); } return 0;}
轉載請註明出處:http://blog.csdn.net/suool/article/details/38515863