Linux内存映射mmap原理分析
來自:Joe James
鏈接:https://blog.csdn.net/joejames/article/details/37958017
一直都對內(nèi)存映射文件這個概念很模糊,不知道它和虛擬內(nèi)存有什么區(qū)別,而且映射這個詞也很讓人迷茫,今天終于搞清楚了。。。下面,我先解釋一下我對映射這個詞的理解,再區(qū)分一下幾個容易混淆的概念,之后,什么是內(nèi)存映射就很明朗了。
原理
首先,“映射”這個詞,就和數(shù)學(xué)課上說的“一一映射”是一個意思,就是建立一種一一對應(yīng)關(guān)系,在這里主要是只?硬盤上文件?的位置與進(jìn)程?邏輯地址空間?中一塊大小相同的區(qū)域之間的一一對應(yīng),如圖1中過程1所示。這種對應(yīng)關(guān)系純屬是邏輯上的概念,物理上是不存在的,原因是進(jìn)程的邏輯地址空間本身就是不存在的。在內(nèi)存映射的過程中,并沒有實際的數(shù)據(jù)拷貝,文件沒有被載入內(nèi)存,只是邏輯上被放入了內(nèi)存,具體到代碼,就是建立并初始化了相關(guān)的數(shù)據(jù)結(jié)構(gòu)(struct?address_space),這個過程有系統(tǒng)調(diào)用mmap()實現(xiàn),所以建立內(nèi)存映射的效率很高。
圖1.內(nèi)存映射原理???
既然建立內(nèi)存映射沒有進(jìn)行實際的數(shù)據(jù)拷貝,那么進(jìn)程又怎么能最終直接通過內(nèi)存操作訪問到硬盤上的文件呢?那就要看內(nèi)存映射之后的幾個相關(guān)的過程了。
mmap()會返回一個指針ptr,它指向進(jìn)程邏輯地址空間中的一個地址,這樣以后,進(jìn)程無需再調(diào)用read或write對文件進(jìn)行讀寫,而只需要通過ptr就能夠操作文件。但是ptr所指向的是一個邏輯地址,要操作其中的數(shù)據(jù),必須通過MMU將邏輯地址轉(zhuǎn)換成物理地址,如圖1中過程2所示。這個過程與內(nèi)存映射無關(guān)。?
前面講過,建立內(nèi)存映射并沒有實際拷貝數(shù)據(jù),這時,MMU在地址映射表中是無法找到與ptr相對應(yīng)的物理地址的,也就是MMU失敗,將產(chǎn)生一個缺頁中斷,缺頁中斷的中斷響應(yīng)函數(shù)會在swap中尋找相對應(yīng)的頁面,如果找不到(也就是該文件從來沒有被讀入內(nèi)存的情況),則會通過mmap()建立的映射關(guān)系,從硬盤上將文件讀取到物理內(nèi)存中,如圖1中過程3所示。這個過程與內(nèi)存映射無關(guān)。
如果在拷貝數(shù)據(jù)時,發(fā)現(xiàn)物理內(nèi)存不夠用,則會通過虛擬內(nèi)存機(jī)制(swap)將暫時不用的物理頁面交換到硬盤上,如圖1中過程4所示。這個過程也與內(nèi)存映射無關(guān)。
效率
從代碼層面上看,從硬盤上將文件讀入內(nèi)存,都要經(jīng)過文件系統(tǒng)進(jìn)行數(shù)據(jù)拷貝,并且數(shù)據(jù)拷貝操作是由文件系統(tǒng)和硬件驅(qū)動實現(xiàn)的,理論上來說,拷貝數(shù)據(jù)的效率是一樣的。但是通過內(nèi)存映射的方法訪問硬盤上的文件,效率要比read和write系統(tǒng)調(diào)用高,這是為什么呢?原因是read()是系統(tǒng)調(diào)用,其中進(jìn)行了數(shù)據(jù)拷貝,它首先將文件內(nèi)容從硬盤拷貝到內(nèi)核空間的一個緩沖區(qū),如圖2中過程1,然后再將這些數(shù)據(jù)拷貝到用戶空間,如圖2中過程2,在這個過程中,實際上完成了?兩次數(shù)據(jù)拷貝?;而mmap()也是系統(tǒng)調(diào)用,如前所述,mmap()中沒有進(jìn)行數(shù)據(jù)拷貝,真正的數(shù)據(jù)拷貝是在缺頁中斷處理時進(jìn)行的,由于mmap()將文件直接映射到用戶空間,所以中斷處理函數(shù)根據(jù)這個映射關(guān)系,直接將文件從硬盤拷貝到用戶空間,只進(jìn)行了?一次數(shù)據(jù)拷貝?。因此,內(nèi)存映射的效率要比read/write效率高。
圖2.read系統(tǒng)調(diào)用原理?
下面這個程序,通過read和mmap兩種方法分別對硬盤上一個名為“mmap_test”的文件進(jìn)行操作,文件中存有10000個整數(shù),程序兩次使用不同的方法將它們讀出,加1,再寫回硬盤。通過對比可以看出,read消耗的時間將近是mmap的兩到三倍。
#include<unistd.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<sys/time.h> #include<fcntl.h> #include<sys/mman.h>#define?MAX?10000int?main() { int?i=0; int?count=0,?fd=0; struct?timeval?tv1,?tv2; int?*array?=?(int?*)malloc(?sizeof(int)*MAX?);/*read*/gettimeofday(?&tv1,?NULL?); fd?=?open(?"mmap_test",?O_RDWR?); if(?sizeof(int)*MAX?!=?read(?fd,?(void?*)array,?sizeof(int)*MAX?)?) { printf(?"Reading?data?failed.../n"?); return?-1; } for(?i=0;?i<MAX;?++i?)++array[?i?]; if(?sizeof(int)*MAX?!=?write(?fd,?(void?*)array,?sizeof(int)*MAX?)?) { printf(?"Writing?data?failed.../n"?); return?-1; } free(?array?); close(?fd?); gettimeofday(?&tv2,?NULL?); printf(?"Time?of?read/write:?%dms/n",?tv2.tv_usec-tv1.tv_usec?);/*mmap*/gettimeofday(?&tv1,?NULL?); fd?=?open(?"mmap_test",?O_RDWR?); array?=?mmap(?NULL,?sizeof(int)*MAX,?PROT_READ|PROT_WRITE,?MAP_SHARED,?fd,?0?); for(?i=0;?i<MAX;?++i?)++array[?i?]; munmap(?array,?sizeof(int)*MAX?); msync(?array,?sizeof(int)*MAX,?MS_SYNC?); free(?array?); close(?fd?); gettimeofday(?&tv2,?NULL?); printf(?"Time?of?mmap:?%dms/n",?tv2.tv_usec-tv1.tv_usec?);return?0; }輸出結(jié)果:
Time?of?read/write:?154ms Time?of?mmap:?68ms總結(jié)
以上是生活随笔為你收集整理的Linux内存映射mmap原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 后Hadoop时代的大数据技术思考:数据
- 下一篇: 死磕Synchronized底层实现--