共享內(nèi)存可以說是最有用的進程間通信方式,也是最快的IPC形式, 因為進程可以直接讀寫內(nèi)存,而不需要任何
數(shù)據(jù)的拷貝。對于像管道和消息隊列等通信方式,則需要在內(nèi)核和用戶空間進行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則
只拷貝兩次數(shù)據(jù): 一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。實際上,進程之間在共享內(nèi)
存時,并不總是讀寫少量數(shù)據(jù)后就解除映射,有新的通信時,再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直
到通信完畢為止,這樣,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并沒有寫回文件。共享內(nèi)存中的內(nèi)容往往是在解除映
射時才寫回文件的。因此,采用共享內(nèi)存的通信方式效率是非常高的。
?
一. 傳統(tǒng)文件訪問
UNIX訪問文件的傳統(tǒng)方法是用open打開它們, 如果有多個進程訪問同一個文件, 則每一個進程在自己的地址空間都包含有該
文件的副本,這不必要地浪費了存儲空間. 下圖說明了兩個進程同時讀一個文件的同一頁的情形. 系統(tǒng)要將該頁從磁盤讀到高
速緩沖區(qū)中, 每個進程再執(zhí)行一個存儲器內(nèi)的復(fù)制操作將數(shù)據(jù)從高速緩沖區(qū)讀到自己的地址空間.
?
二. 共享存儲映射
現(xiàn)在考慮另一種處理方法: 進程A和進程B都將該頁映射到自己的地址空間, 當(dāng)進程A第一次訪問該頁中的數(shù)據(jù)時, 它生成一
個缺頁中斷. 內(nèi)核此時讀入這一頁到內(nèi)存并更新頁表使之指向它.以后, 當(dāng)進程B訪問同一頁面而出現(xiàn)缺頁中斷時, 該頁已經(jīng)在
內(nèi)存, 內(nèi)核只需要將進程B的頁表登記項指向次頁即可. 如下圖所示:?
?
三、mmap()及其相關(guān)系統(tǒng)調(diào)用
mmap()系統(tǒng)調(diào)用使得進程之間通過映射同一個普通文件實現(xiàn)共享內(nèi)存。普通文件被映射到進程地址空間后,進程可以向訪
問普通內(nèi)存一樣對文件進行訪問,不必再調(diào)用read(),write()等操作。
?
mmap()系統(tǒng)調(diào)用形式如下:
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )?
mmap的作用是映射文件描述符fd指定文件的 [off,off + len]區(qū)域至調(diào)用進程的[addr, addr + len]的內(nèi)存區(qū)域, 如下圖所示:
?
參數(shù)fd為即將映射到進程空間的文件描述字,一般由open()返回,同時,fd可以指定為-1,此時須指定flags參數(shù)中的
MAP_ANON,表明進行的是匿名映射(不涉及具體的文件名,避免了文件的創(chuàng)建及打開,很顯然只能用于具有親緣關(guān)系的
進程間通信)。
len是映射到調(diào)用進程地址空間的字節(jié)數(shù),它從被映射文件開頭offset個字節(jié)開始算起。
prot 參數(shù)指定共享內(nèi)存的訪問權(quán)限。可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執(zhí)行), PROT_NONE(不可訪問)。
flags由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必
選其一,而MAP_FIXED則不推薦使用。
offset參數(shù)一般設(shè)為0,表示從文件頭開始映射。
參數(shù)addr指定文件應(yīng)被映射到進程空間的起始地址,一般被指定一個空指針,此時選擇起始地址的任務(wù)留給內(nèi)核來完成。函
數(shù)的返回值為最后文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。
四. mmap的兩個例子
范例中使用的測試文件 data.txt:?
Xml代碼 ?
aaaaaaaaa??bbbbbbbbb??ccccccccc??ddddddddd?? ?
1 通過共享映射的方式修改文件
C代碼 ?
#include?<sys/mman.h>??#include?<sys/stat.h>??#include?<fcntl.h>??#include?<stdio.h>??#include?<stdlib.h>??#include?<unistd.h>??#include?<error.h>????#define?BUF_SIZE?100????int?main(int?argc,?char?**argv)??{??????int?fd,?nread,?i;??????struct?stat?sb;??????char?*mapped,?buf[BUF_SIZE];????????for?(i?=?0;?i?<?BUF_SIZE;?i++)?{??????????buf[i]?=?'#';??????}??????????????if?((fd?=?open(argv[1],?O_RDWR))?<?0)?{??????????perror("open");??????}??????????????if?((fstat(fd,?&sb))?==?-1)?{??????????perror("fstat");??????}??????????????if?((mapped?=?(char?*)mmap(NULL,?sb.st_size,?PROT_READ?|???????????????????????PROT_WRITE,?MAP_SHARED,?fd,?0))?==?(void?*)-1)?{??????????perror("mmap");??????}??????????????close(fd);????????printf("%s",?mapped);??????????????mapped[20]?=?'9';??????if?((msync((void?*)mapped,?sb.st_size,?MS_SYNC))?==?-1)?{??????????perror("msync");??????}??????????????if?((munmap((void?*)mapped,?sb.st_size))?==?-1)?{??????????perror("munmap");??????}????????return?0;??}?? ?
2 私有映射無法修改文件
/* 將文件映射至進程的地址空間 */ if ((mapped = (char *)mmap(NULL, sb.st_size, PROT_READ |? ? ? ? ? ? ? ? ? ? ? PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void *)-1) { ? ? perror("mmap"); }
五. 使用共享映射實現(xiàn)兩個進程之間的通信
兩個程序映射同一個文件到自己的地址空間, 進程A先運行, 每隔兩秒讀取映射區(qū)域, 看是否發(fā)生變化.?
進程B后運行, 它修改映射區(qū)域, 然后推出, 此時進程A能夠觀察到存儲映射區(qū)的變化
進程A的代碼:
C代碼 ?
#include?<sys/mman.h>??#include?<sys/stat.h>??#include?<fcntl.h>??#include?<stdio.h>??#include?<stdlib.h>??#include?<unistd.h>??#include?<error.h>????#define?BUF_SIZE?100????int?main(int?argc,?char?**argv)??{??????int?fd,?nread,?i;??????struct?stat?sb;??????char?*mapped,?buf[BUF_SIZE];????????for?(i?=?0;?i?<?BUF_SIZE;?i++)?{??????????buf[i]?=?'#';??????}??????????????if?((fd?=?open(argv[1],?O_RDWR))?<?0)?{??????????perror("open");??????}??????????????if?((fstat(fd,?&sb))?==?-1)?{??????????perror("fstat");??????}??????????????if?((mapped?=?(char?*)mmap(NULL,?sb.st_size,?PROT_READ?|???????????????????????PROT_WRITE,?MAP_SHARED,?fd,?0))?==?(void?*)-1)?{??????????perror("mmap");??????}??????????????close(fd);??????????????????while?(1)?{??????????printf("%s\n",?mapped);??????????sleep(2);??????}????????return?0;??}?? ?
進程B的代碼:
C代碼 ?
#include?<sys/mman.h>??#include?<sys/stat.h>??#include?<fcntl.h>??#include?<stdio.h>??#include?<stdlib.h>??#include?<unistd.h>??#include?<error.h>????#define?BUF_SIZE?100????int?main(int?argc,?char?**argv)??{??????int?fd,?nread,?i;??????struct?stat?sb;??????char?*mapped,?buf[BUF_SIZE];????????for?(i?=?0;?i?<?BUF_SIZE;?i++)?{??????????buf[i]?=?'#';??????}??????????????if?((fd?=?open(argv[1],?O_RDWR))?<?0)?{??????????perror("open");??????}??????????????if?((fstat(fd,?&sb))?==?-1)?{??????????perror("fstat");??????}??????????????if?((mapped?=?(char?*)mmap(NULL,?sb.st_size,?PROT_READ?|???????????????????????PROT_WRITE,?MAP_PRIVATE,?fd,?0))?==?(void?*)-1)?{??????????perror("mmap");??????}??????????????close(fd);??????????????mapped[20]?=?'9';?????????return?0;??}??
?
六. 通過匿名映射實現(xiàn)父子進程通信
C代碼 ?
#include?<sys/mman.h>??#include?<stdio.h>??#include?<stdlib.h>??#include?<unistd.h>????#define?BUF_SIZE?100????int?main(int?argc,?char**?argv)??{??????char????*p_map;??????????????p_map?=?(char?*)mmap(NULL,?BUF_SIZE,?PROT_READ?|?PROT_WRITE,??????????????MAP_SHARED?|?MAP_ANONYMOUS,?-1,?0);????????if(fork()?==?0)?{??????????sleep(1);??????????printf("child?got?a?message:?%s\n",?p_map);??????????sprintf(p_map,?"%s",?"hi,?dad,?this?is?son");??????????munmap(p_map,?BUF_SIZE);???????????exit(0);??????}????????sprintf(p_map,?"%s",?"hi,?this?is?father");??????sleep(2);??????printf("parent?got?a?message:?%s\n",?p_map);????????return?0;??}?? ?
?
七. 對mmap()返回地址的訪問
linux采用的是頁式管理機制。對于用mmap()映射普通文件來說,進程會在自己的地址空間新增一塊空間,空間大
小由mmap()的len參數(shù)指定,注意,進程并不一定能夠?qū)θ啃略隹臻g都能進行有效訪問。進程能夠訪問的有效地址大小取決于文件被映射部分的大小。簡單的說,能夠容納文件被映射部分大小的最少頁面?zhèn)€數(shù)決定了進程從mmap()返回的地址開始,能夠有效訪問的地址空間大小。超過這個空間大小,內(nèi)核會根據(jù)超過的嚴重程度返回發(fā)送不同的信號給進程。可用如下圖示說明:
?
總結(jié)一下就是, 文件大小, mmap的參數(shù) len 都不能決定進程能訪問的大小, 而是容納文件被映射部分的最小頁面數(shù)決定
進程能訪問的大小. 下面看一個實例:
?
?
C代碼 ?
#include?<sys/mman.h>??#include?<sys/types.h>??#include?<sys/stat.h>??#include?<fcntl.h>??#include?<unistd.h>??#include?<stdio.h>????int?main(int?argc,?char**?argv)??{??????int?fd,i;??????int?pagesize,offset;??????char?*p_map;??????struct?stat?sb;??????????????pagesize?=?sysconf(_SC_PAGESIZE);??????printf("pagesize?is?%d\n",pagesize);??????????????fd?=?open(argv[1],?O_RDWR,?00777);??????fstat(fd,?&sb);??????printf("file?size?is?%zd\n",?(size_t)sb.st_size);????????offset?=?0;???????p_map?=?(char?*)mmap(NULL,?pagesize?*?2,?PROT_READ|PROT_WRITE,???????????????MAP_SHARED,?fd,?offset);??????close(fd);????????????p_map[sb.st_size]?=?'9';????????p_map[pagesize]?=?'9';????????????munmap(p_map,?pagesize?*?2);????????return?0;??}
總結(jié)
以上是生活随笔為你收集整理的mmap原理简析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。