共享內存可以說是最有用的進程間通信方式,也是最快的IPC形式, 因為進程可以直接讀寫內存,而不需要任何
數據的拷貝。對于像管道和消息隊列等通信方式,則需要在內核和用戶空間進行四次的數據拷貝,而共享內存則
只拷貝兩次數據: 一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件。實際上,進程之間在共享內
存時,并不總是讀寫少量數據后就解除映射,有新的通信時,再重新建立共享內存區域。而是保持共享區域,直
到通信完畢為止,這樣,數據內容一直保存在共享內存中,并沒有寫回文件。共享內存中的內容往往是在解除映
射時才寫回文件的。因此,采用共享內存的通信方式效率是非常高的。
?
一. 傳統文件訪問
UNIX訪問文件的傳統方法是用open打開它們, 如果有多個進程訪問同一個文件, 則每一個進程在自己的地址空間都包含有該
文件的副本,這不必要地浪費了存儲空間. 下圖說明了兩個進程同時讀一個文件的同一頁的情形. 系統要將該頁從磁盤讀到高
速緩沖區中, 每個進程再執行一個存儲器內的復制操作將數據從高速緩沖區讀到自己的地址空間.
?
二. 共享存儲映射
現在考慮另一種處理方法: 進程A和進程B都將該頁映射到自己的地址空間, 當進程A第一次訪問該頁中的數據時, 它生成一
個缺頁中斷. 內核此時讀入這一頁到內存并更新頁表使之指向它.以后, 當進程B訪問同一頁面而出現缺頁中斷時, 該頁已經在
內存, 內核只需要將進程B的頁表登記項指向次頁即可. 如下圖所示:?
?
三、mmap()及其相關系統調用
mmap()系統調用使得進程之間通過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間后,進程可以向訪
問普通內存一樣對文件進行訪問,不必再調用read(),write()等操作。
?
mmap()系統調用形式如下:
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )?
mmap的作用是映射文件描述符fd指定文件的 [off,off + len]區域至調用進程的[addr, addr + len]的內存區域, 如下圖所示:
?
參數fd為即將映射到進程空間的文件描述字,一般由open()返回,同時,fd可以指定為-1,此時須指定flags參數中的
MAP_ANON,表明進行的是匿名映射(不涉及具體的文件名,避免了文件的創建及打開,很顯然只能用于具有親緣關系的
進程間通信)。
len是映射到調用進程地址空間的字節數,它從被映射文件開頭offset個字節開始算起。
prot 參數指定共享內存的訪問權限。可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執行), PROT_NONE(不可訪問)。
flags由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必
選其一,而MAP_FIXED則不推薦使用。
offset參數一般設為0,表示從文件頭開始映射。
參數addr指定文件應被映射到進程空間的起始地址,一般被指定一個空指針,此時選擇起始地址的任務留給內核來完成。函
數的返回值為最后文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。
四. 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"); }
五. 使用共享映射實現兩個進程之間的通信
兩個程序映射同一個文件到自己的地址空間, 進程A先運行, 每隔兩秒讀取映射區域, 看是否發生變化.?
進程B后運行, 它修改映射區域, 然后推出, 此時進程A能夠觀察到存儲映射區的變化
進程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;??}??
?
六. 通過匿名映射實現父子進程通信
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參數指定,注意,進程并不一定能夠對全部新增空間都能進行有效訪問。進程能夠訪問的有效地址大小取決于文件被映射部分的大小。簡單的說,能夠容納文件被映射部分大小的最少頁面個數決定了進程從mmap()返回的地址開始,能夠有效訪問的地址空間大小。超過這個空間大小,內核會根據超過的嚴重程度返回發送不同的信號給進程。可用如下圖示說明:
?
總結一下就是, 文件大小, mmap的參數 len 都不能決定進程能訪問的大小, 而是容納文件被映射部分的最小頁面數決定
進程能訪問的大小. 下面看一個實例:
?
?
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;??}
總結
以上是生活随笔為你收集整理的mmap原理简析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。