新聞中心
內(nèi)存映射,簡而言之就是將用戶空間的一段內(nèi)存區(qū)域映射到內(nèi)核空間,映射成功后,用戶對這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間,相反,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間。那么對于內(nèi)核空間用戶空間兩者之間需要大量數(shù)據(jù)傳輸?shù)炔僮鞯脑捫适欠浅8叩摹?/p>

成都創(chuàng)新互聯(lián)主營港口網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都app軟件開發(fā),港口h5小程序開發(fā)搭建,港口網(wǎng)站營銷推廣歡迎港口等地區(qū)企業(yè)咨詢
一.概述
內(nèi)存映射是在調(diào)用進程的虛擬地址空間創(chuàng)建一個新的內(nèi)存映射。
內(nèi)存映射分為2種:
1.文件映射:將一個普通文件的全部或者一部分映射到進程的虛擬內(nèi)存中。映射后,進程就可以直接在對應(yīng)的內(nèi)存區(qū)域操作文件內(nèi)容!
2.匿名映射:匿名映射沒有對應(yīng)的文件或者對應(yīng)的文件時虛擬文件(如:/dev/zero),映射后會把內(nèi)存分頁全部初始化為0。
當(dāng)多個進程映射了同一個內(nèi)存區(qū)域時,它們會共享物理內(nèi)存的相同分頁。通過fork()創(chuàng)建的子進程也會繼承父進程的映射副本!?。?/p>
如果多個進程都會同一個內(nèi)存區(qū)域操作時,會根據(jù)映射的特性,會有不同的行為。映射特征可分為私有映射和共享映射:
1.私有映射:映射的內(nèi)容對其他進程不可見。對于文件映射來說,某一個進程在映射內(nèi)存中改變文件的內(nèi)容不會反映到被映射的底層文件中。內(nèi)核會使用copy-on-write(寫時復(fù)制)技術(shù)來解決這個問題:只要有一個進程修改了分頁中的內(nèi)容,內(nèi)核會為該進程重新創(chuàng)建一個新的分頁,并將需要修改的內(nèi)容復(fù)制到新分頁中。
2.共享映射:某一個進程對共享的內(nèi)存區(qū)域操作都對其他進程可見!?。τ谖募成?,操作的內(nèi)容會反映到底層文件中。
注意:進程執(zhí)行exec()調(diào)用后,先前的內(nèi)存映射會丟失,而fork()創(chuàng)建的子進程會繼承父進程的,映射的特征(私有和共享)也會被繼承。
異常信號:
1.當(dāng)映射內(nèi)存的屬性設(shè)置只讀時,如果進行寫操作會產(chǎn)生SIGSEGV信號。
2.當(dāng)映射內(nèi)存的字節(jié)數(shù)大于被映射文件的大小,且大于該文件當(dāng)前的內(nèi)存分頁大小時。如果訪問的區(qū)域超過了該文件分頁大小,會產(chǎn)生SIGBUS信號。
有點繞口,舉個簡單的例子:假設(shè)內(nèi)核維護的內(nèi)存分頁是4k(一般都是4k,4096字節(jié)),一個普通文件a.txt的大小是10字節(jié)。如果創(chuàng)建一個映射內(nèi)存為4097字節(jié),并映射該文件。此時,因為a.txt的大小用一個分頁就可以完全映射,10字節(jié)遠小于一個分頁的4096字節(jié),所以內(nèi)核只會給它一個分頁。內(nèi)存地址是從0開始,0-9區(qū)間的內(nèi)容對應(yīng)a.txt文件的數(shù)據(jù),我們也是可以訪問10-4095的區(qū)間。但如果訪問4096區(qū)間時,已經(jīng)超過一個分頁的大小了,此時會產(chǎn)生SIGBUS信號?。?!
等會我們用個簡單的例子演示下這2個異常。
二.函數(shù)接口
1.創(chuàng)建映射
1 #include
2
3 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:映射后要存放的虛擬內(nèi)存地址。如果是NULL,內(nèi)核會自動幫你選擇。
length:映射內(nèi)存的字節(jié)數(shù)。
prot:權(quán)限保護:PROT_NONE(無法訪問),PROT_READ(可讀),PROT_WRITE(可寫),PROT_EXEC(可執(zhí)行)。
flags:映射特征:MAP_PRIVATE(私有),MAP_SHARED(共享),MAP_ANONYMOUS。還有一些其他的可查詢man手冊。
fd:要映射的文件描述符。
offset:文件的偏移量,如果為0,且length為文件長度,代表映射整個文件。
2.解除映射
1 #include
2
3 int munmap(void *addr, size_t length);
addr:要解除內(nèi)存的起始地址。如果addr不在剛剛映射區(qū)域的開始位置,解除一部分后內(nèi)存區(qū)域可能會分成兩半?。?!
length:要解除的字節(jié)數(shù)。
3.同步映射區(qū)
1 #include
2
3 int msync(void *addr, size_t length, int flags);
addr:要同步的內(nèi)存起始地址。
length:要同步的字節(jié)長度。
flags:MS_SYNC(執(zhí)行同步文件寫入),此操作內(nèi)核會把內(nèi)容直接寫到磁盤。MS_ASYNC(執(zhí)行異步文件寫入),此操作內(nèi)核會先把內(nèi)容寫到內(nèi)核的緩沖區(qū),某個合適的時候再寫到磁盤。
三.文件映射實例
/**
\* @file mmap_file.c
*/
\#include
\#include
\#include
\#include
\#include
\#include
\#include
\#define MMAP_FILE_NAME "a.txt"
\#define MMAP_FILE_SIZE 10
void err_exit(const char *err_msg)
{
printf("error:%s\n", err_msg);
exit(1);
}
/* 信號處理器 */
void signal_handler(int signum)
{
if (signum == SIGSEGV)
printf("\nSIGSEGV handler!!!\n");
else if (signum == SIGBUS)
printf("\nSIGBUS handler!!!\n");
exit(1);
}
int main(int argc, const char *argv[])
{
if (argc printf("usage:%s text\n", argv[0]);
exit(1);
}
char *addr;
int file_fd, text_len;
long int sys_pagesize;
/* 設(shè)置信號處理器 */
if (signal(SIGSEGV, signal_handler) == SIG_ERR)
err_exit("signal()");
if (signal(SIGBUS, signal_handler) == SIG_ERR)
err_exit("signal()");
if ((file_fd = open(MMAP_FILE_NAME, O_RDWR)) == -1)
err_exit("open()");
/* 系統(tǒng)分頁大小 */
sys_pagesize = sysconf(_SC_PAGESIZE);
printf("sys_pagesize:%ld\n", sys_pagesize);
/* 內(nèi)存只讀 */
//addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ, MAP_SHARED, file_fd, 0);
/* 映射大于文件長度,且大于該文件分頁大小 */
//addr = (char *)mmap(NULL, sys_pagesize + 1, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
/* 正常分配 */
addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
if (addr == MAP_FAILED)
err_exit("mmap()");
/* 原始數(shù)據(jù) */
printf("old text:%s\n", addr);
/* 越界訪問 */
//addr += sys_pagesize + 1;
//printf("out of range:%s\n", addr);
/* 拷貝新數(shù)據(jù) */
text_len = strlen(argv[1]);
memcpy(addr, argv[1], text_len);
/* 同步映射區(qū)數(shù)據(jù) */
//if (msync(addr, text_len, MS_SYNC) == -1)
// err_exit("msync()");
/* 打印新數(shù)據(jù) */
printf("new text:%s\n", addr);
/* 解除映射區(qū)域 */
if (munmap(addr, MMAP_FILE_SIZE) == -1)
err_exit("munmap()");
return 0;
}
1.首先創(chuàng)建一個10字節(jié)的文件:
1 $:dd if=/dev/zero of=a.txt bs=1 count=10
2.把程序編譯運行后,依次執(zhí)行2寫入:
可以看到本機的分頁大小是4096字節(jié)。第一次寫入9個字節(jié),原來用dd命令創(chuàng)建的文件為空,old text為空。第二次寫入4個字節(jié),只覆蓋了最前面的1234。
3.驗證可訪問現(xiàn)有分頁的內(nèi)存。寫入超過10字節(jié)的數(shù)據(jù):
上面我們寫入了17個字節(jié),雖然64行的mmap()映射了MMAP_FILE_SIZE=10字節(jié)。但從輸入new text可以看出,我們???然可以訪問10字節(jié)后面的內(nèi)存,因為該數(shù)據(jù)都在一個分頁(4096)里面。cat查看a.txt后,只有前10個字節(jié)寫入了a.txt。
4.驗證SIGSEGV信號。把64行注釋調(diào),58行打開,設(shè)置映射屬性為只讀,編譯后訪問:
設(shè)置只讀屬性后,第77行有寫操作。我們自定義的信號處理器就捕捉到了該信號。如果沒有自定義信號處理器,終端就會輸出Segmentation fault
5.驗證SIGBUS信號。用61行的方法來映射內(nèi)存。映射了一個分頁大小再加1字節(jié)的內(nèi)存,并放開72,73行的代碼,讓指針指向一個分頁后的區(qū)域。編譯后運行:
SIGBUS信號被自定義處理器捕捉到了。如果沒有自定義信號處理器,終端就會輸出Bus error
四.匿名映射
匿名映射有2種方式:
1.指定mmap()的flags參數(shù)為MAP_ANONYMOUS,在linux上當(dāng)指定這個值后會忽略fd參數(shù)的值。不過在有的UNIX上還需要把fd指定為-1。
2.把/dev/zero當(dāng)做文件描述符打開,從/dev/zero讀取數(shù)據(jù)時它會給你提供無窮無盡的0,向它寫數(shù)據(jù),它會丟棄。丟棄這點跟/dev/null一樣,只是/dev/null不跟你提供數(shù)據(jù)。
3.匿名映射的使用跟上面的文件映射差不多。這里不再給例子。
網(wǎng)頁名稱:講解一下Linux內(nèi)存映射
標題URL:http://fisionsoft.com.cn/article/cddhgoi.html


咨詢
建站咨詢
