共享內(nèi)存
-
共享內(nèi)存可以說(shuō)是最有用的進(jìn)程間通信方式,也是最快的ipc形式,兩個(gè)不同的進(jìn)程a、b共享內(nèi)存的意思就是:同一塊物理內(nèi)存被映射到進(jìn)程a、b各自的進(jìn)程地址空間,進(jìn)程a可以同時(shí)看到進(jìn)程b對(duì)共享內(nèi)存中數(shù)據(jù)的更新,反之亦然。
-
由于個(gè)多個(gè)進(jìn)程共享同一塊內(nèi)存區(qū)域,必然需要某種同步機(jī)制、互斥鎖和信號(hào)量都可以。
好處: 效率高,進(jìn)程可以直接讀寫內(nèi)存,而不需要復(fù)制任何數(shù)據(jù),而管道、消息隊(duì)列等通信方式,則需要在內(nèi)核和用戶空間進(jìn)行四次數(shù)據(jù)復(fù)制。
并且只有在解除映射時(shí),共享內(nèi)存的內(nèi)容才會(huì)寫會(huì)文紀(jì)念
共享內(nèi)存通過內(nèi)核對(duì)象,使得不同的進(jìn)程在自己的虛擬地址空間上分配一塊空間映射到相同的物理內(nèi)存空間上,這塊物理內(nèi)存空間對(duì)于映射到上面的每個(gè)進(jìn)程而言都是可以訪問的。(臨界資源)
共享內(nèi)存就是允許兩個(gè)不相關(guān)的進(jìn)程訪問同一個(gè)邏輯內(nèi)存。
共享內(nèi)存是在兩個(gè)正在運(yùn)行的進(jìn)程之間共享和傳遞數(shù)據(jù)的一種非常有效的方式。
不同進(jìn)程之間共享的內(nèi)存通常安排為同一段物理內(nèi)存。
進(jìn)程可以將同一段共享內(nèi)存連接到它們自己的地址空間中,所有進(jìn)程都可以訪問共享內(nèi)存中的地址,就好像它們是由用c語(yǔ)言函數(shù)malloc()分配的內(nèi)存一樣。
而如果某個(gè)進(jìn)程向共享內(nèi)存寫入數(shù)據(jù),所做的改動(dòng)將立即影響到可以 訪問同一段共享內(nèi)存的任何其他進(jìn)程。
mmap()及其相關(guān)的系統(tǒng)調(diào)用
mmap是linux操作系統(tǒng)提供給用戶空間調(diào)用的內(nèi)存映射函數(shù),很多人僅僅只是知道可以通過mmap完成進(jìn)程間的內(nèi)存共享和減少用戶態(tài)到內(nèi)核態(tài)的數(shù)據(jù)拷貝次數(shù),但是并沒有深入理解mmap在操作系統(tǒng)內(nèi)部是如何實(shí)現(xiàn)的,原理是什么
使用mmap()系統(tǒng)調(diào)用,進(jìn)程們可以通過映射同一個(gè)普通文件來(lái)實(shí)現(xiàn)內(nèi)存共享。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以訪問普通內(nèi)存一樣對(duì)文件進(jìn)行訪問,不必再調(diào)用read和write操作。?
注意: mmap并不是完全為了IPC而設(shè)計(jì)的,只是IPC的一種應(yīng)用方式,它本身提供了一種像訪問普通內(nèi)存一樣的訪問對(duì)普通文件進(jìn)行操作的方式。
通過使用帶有特殊權(quán)限集的虛擬內(nèi)存段來(lái)實(shí)現(xiàn)。當(dāng)對(duì)這種虛擬內(nèi)存段進(jìn)行讀寫時(shí),操作系統(tǒng)會(huì)去讀寫與之對(duì)應(yīng)的磁盤文件部分。
mmap 函數(shù)創(chuàng)建一個(gè)指向一段內(nèi)存區(qū)域的指針,該內(nèi)存區(qū)域與可以通過一個(gè)打開的文件描述符訪問的文件的內(nèi)容相關(guān)聯(lián)
解釋如下:
mmap()
#include?<sys> void?*mmap(void?*addr,?size_t?Length,?int?prot,?int?flags,?int?fd,?off_t?offset);</sys>
可以通過傳遞 offset 參數(shù)來(lái)改變經(jīng)共享內(nèi)存段訪問的文件中數(shù)據(jù)的起始偏移值。
打開的文件描述符由 fd 參數(shù)給出。
可以訪問的數(shù)據(jù)量(即內(nèi)存段的長(zhǎng)度)由 length 參數(shù)設(shè)置。
可以通過 addr 參數(shù)來(lái)請(qǐng)求使用某個(gè)特定的內(nèi)存地址。如果它的取值是零,結(jié)果指針就將自動(dòng)分配。若不遵循此做法,則會(huì)降低程序的可移植性,因?yàn)榭捎玫刂贩秶诓煌到y(tǒng)上是不同的。
prot 參數(shù)用于設(shè)置內(nèi)存段的訪問權(quán)限。它是下列常數(shù)值的按位或的結(jié)果
-
PROT_READ 內(nèi)存段可讀。
-
PROT_WRITE 內(nèi)存段可寫。
-
PROT_EXEC 內(nèi)存段可執(zhí)行。
-
PROT_NONE 內(nèi)存段不能被訪問。
flags 參數(shù)控制程序?qū)υ搩?nèi)存段的改變所造成的影響:
mmap()用于共享內(nèi)存的量和兩種方式如下:
使用普通文件提供的內(nèi)存映射,適用于任何進(jìn)程間,使用該方式需要先打開或者創(chuàng)建一個(gè)文件,再調(diào)用ngmmap,典型調(diào)用代碼如下:
fd?=?open(name.falg.mode); if(fd?<p>使用特殊文件提供的內(nèi)存映射,適用于具有親緣關(guān)系的進(jìn)程之間,由于父子進(jìn)程特殊的親緣關(guān)系,在父進(jìn)程中先調(diào)用mmap,調(diào)用fork,那么在代用fork之后,子進(jìn)程可以繼承父進(jìn)程匿名映射后的地址空間,同樣也繼承mmap返回的地址,這樣父子進(jìn)程就可以通過映射區(qū)域進(jìn)行通信了。(注意:一般來(lái)說(shuō),子進(jìn)程單獨(dú)維護(hù)從父進(jìn)程繼承而來(lái)的一些變量,而mmap()返回的地址由父子進(jìn)程共同維護(hù))【具體使用實(shí)現(xiàn)敬請(qǐng)期待博主整理】</p><h4>munmap()</h4><p>用于解除內(nèi)存映射,取消參數(shù)start所指的映射內(nèi)存的起始地址,參數(shù)length則是欲取消的內(nèi)存大小,當(dāng)進(jìn)程結(jié)束或者利用exec相關(guān)函數(shù)來(lái)執(zhí)行其他程序時(shí),映射內(nèi)存會(huì)自動(dòng)解除,但關(guān)閉對(duì)應(yīng)的文件描述符時(shí)不會(huì)解除映射。</p><pre class="brush:cpp;">#include?<sys> int?munmap(void?*addr,?size_t?length);</sys>
共享內(nèi)存的使用
與信號(hào)量一樣,在linux中也提供了一組函數(shù)接口用于使用共享內(nèi)存,而且使用共享共存的接口還與信號(hào)量的非常相似,而且比使用信號(hào)量的接口來(lái)得簡(jiǎn)單。它們聲明在頭文件 sys/shm.h 中。?
1.獲取或創(chuàng)建內(nèi)核對(duì)象,并且制定共享內(nèi)存的大小(系統(tǒng)分配物理空間是,按照頁(yè)進(jìn)行分配)
int?shmget(key_t?key,?int?size,?int?flag);
只是創(chuàng)建內(nèi)核對(duì)象,并申請(qǐng)物理空間
-
key_t key:與信號(hào)量的semget函數(shù)一樣,程序需要提供一個(gè)參數(shù)key(非0整數(shù)),它有效地為共享內(nèi)存段命名,不同的進(jìn)程通過相同的key值來(lái)訪問同一塊共享內(nèi)存
-
int size:size以字節(jié)為單位指定需要共享的內(nèi)存容量
-
int flag:falg是權(quán)限標(biāo)志,它的作用與open函數(shù)的mode參數(shù)一樣,如果要想在key標(biāo)識(shí)的共享內(nèi)存不存在時(shí),創(chuàng)建它的話,可以與IPC_CREAT做或操作。共享內(nèi)存的權(quán)限標(biāo)志與文件的讀寫權(quán)限一樣,舉例來(lái)說(shuō),0644,它表示允許一個(gè)進(jìn)程創(chuàng)建的共享內(nèi)存被內(nèi)存創(chuàng)建者所擁有的進(jìn)程向共享內(nèi)存讀取和寫入數(shù)據(jù),同時(shí)其他用戶創(chuàng)建的進(jìn)程只能讀取共享內(nèi)存。
返回值
-
調(diào)用成功后,shmget()函數(shù)會(huì)返回一個(gè)非負(fù)整數(shù),用作后續(xù)共享內(nèi)存函數(shù)的共享內(nèi)存標(biāo)識(shí)符,該標(biāo)識(shí)符與key相關(guān)。
-
調(diào)用失敗返回-1.
2.分配自己虛擬地址空間映射到共享內(nèi)存的物理空間上
void?*shmat(int?shmid,const?void?*addr,?int?flag);
-
shmid:shmid是由shmget()函數(shù)返回的共享內(nèi)存標(biāo)識(shí)。
-
void *addr:addr指定共享內(nèi)存連接到當(dāng)前進(jìn)程中的地址位置,通常為NULL,表示讓系統(tǒng)來(lái)選擇共享內(nèi)存的地址。
-
int flag:flag是一組標(biāo)志位,通常為0。
調(diào)用成功時(shí)返回一個(gè)指向共享內(nèi)存第一個(gè)字節(jié)的指針,如果調(diào)用失敗返回-1.
3.斷開當(dāng)前進(jìn)程與共享內(nèi)存的映射
不使用刪除而使用斷開的原因是因?yàn)椋阂苍S還存在其他的進(jìn)程會(huì)繼續(xù)使用這塊共享內(nèi)存
int?shmdt(const?void?*addr);
4.操作共享內(nèi)存的方法
int?shmctl(int?shmid,?int?cmd,?Struct?shmid_t?*buf);
-
int shmid:shmid是shmget()函數(shù)返回的共享內(nèi)存標(biāo)識(shí)符。
-
int cmd:command是要采取的操作,它可以取下面的三個(gè)值 :
IPC_STAT:把shmid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為共享內(nèi)存的當(dāng)前關(guān)聯(lián)值,即用共享內(nèi)存的當(dāng)前關(guān)聯(lián)值覆蓋shmid_ds的值。IPC_SET:如果進(jìn)程有足夠的權(quán)限,就把共享內(nèi)存的當(dāng)前關(guān)聯(lián)值設(shè)置為shmid_ds結(jié)構(gòu)中給出的值IPC_RMID:刪除共享內(nèi)存段
-
struct shmid_t *buf:buf是一個(gè)結(jié)構(gòu)指針,它指向共享內(nèi)存模式和訪問權(quán)限的結(jié)構(gòu)
因?yàn)橛羞B接計(jì)數(shù)器,除非最后一個(gè)進(jìn)程與該共享段斷開連接,則刪除該共享段。否則,并不會(huì)真正刪除該共享段,但是共享內(nèi)存的內(nèi)核對(duì)象會(huì)被立即刪除,不能使用shmat方法與該段連接。?
一個(gè)進(jìn)程調(diào)用該方法刪除后,不會(huì)影響之前已經(jīng)和該共享存儲(chǔ)段連接的進(jìn)程
下面我們利用共享內(nèi)存來(lái)進(jìn)行一個(gè)簡(jiǎn)單的測(cè)試:
完成下面的過程,
成功在共享內(nèi)存中讀到了數(shù)據(jù)
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<sys> #include<sys> #include<sys> #include"sem.h" #define?READSEM?1 #define?WRITESEM?0 int?main() { int?shmid?=?shmget((key_t)1234,128,0664?|?IPC_CREAT); assert(shmid?!=?-1); char?*ptr?=?(char*)shmat(shmid,NULL,0); assert(ptr?!=?(char*)-1); int?initVal[]?=?{1,0}; int?semid?=?SemGet(1234,intVal,2); assert(semid?!=?-1); //A進(jìn)程寫 while(1) { SemP(semid,WRITESEM); printf("Input:"); fgets(ptr,127,stdin); SemV(semid,READSEM); if(strncmp(ptr,"end",3)?==?0) { break; } } shmdt(ptr); exit(0); }</sys></sys></sys></unistd.h></string.h></assert.h></stdlib.h></stdio.h>
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<sys> #include<sys> #include<sys> #include"sem.h" #define?READSEM?1 #define?WRITESEM?0 int?main() { int?shmid?=?shmget((key_t)1234,128,0664?|?IPC_CREAT); assert(shmid?!=?-1); char?*ptr?=?(char*)shmat(shmid,NULL,0); assert(ptr?!=?(char*)-1); int?initVal[]?=?{1,0}; int?semid?=?SemGet(1234,intVal,2); assert(semid?!=?-1); //B進(jìn)程讀 while(1) { SemP(semid,READSEM); if(strncmp(ptr,"end",3)?==?0) { break; } int?i?=?0; for(;i?<p>從上面的代碼中我們可以看出:?</p> <p><strong>共享內(nèi)存是最快的IPC,在通信過程中少了兩次數(shù)據(jù)的拷貝。(相較于管道)</strong></p> <h3>命令管理共享內(nèi)存</h3> <ul class=" list-paddingleft-2"> <li><p>查看 ipcs -m</p></li> <li><p>刪除 ipcrm -m shmid</p></li> </ul></sys></sys></sys></unistd.h></string.h></assert.h></stdlib.h></stdio.h>