Linux内存映射mmap原理分析
來自:Joe James
鏈接:https://blog.csdn.net/joejames/article/details/37958017
一直都對內存映射文件這個概念很模糊,不知道它和虛擬內存有什么區別,而且映射這個詞也很讓人迷茫,今天終于搞清楚了。。。下面,我先解釋一下我對映射這個詞的理解,再區分一下幾個容易混淆的概念,之后,什么是內存映射就很明朗了。
原理
首先,“映射”這個詞,就和數學課上說的“一一映射”是一個意思,就是建立一種一一對應關系,在這里主要是只?硬盤上文件?的位置與進程?邏輯地址空間?中一塊大小相同的區域之間的一一對應,如圖1中過程1所示。這種對應關系純屬是邏輯上的概念,物理上是不存在的,原因是進程的邏輯地址空間本身就是不存在的。在內存映射的過程中,并沒有實際的數據拷貝,文件沒有被載入內存,只是邏輯上被放入了內存,具體到代碼,就是建立并初始化了相關的數據結構(struct?address_space),這個過程有系統調用mmap()實現,所以建立內存映射的效率很高。
圖1.內存映射原理???
既然建立內存映射沒有進行實際的數據拷貝,那么進程又怎么能最終直接通過內存操作訪問到硬盤上的文件呢?那就要看內存映射之后的幾個相關的過程了。
mmap()會返回一個指針ptr,它指向進程邏輯地址空間中的一個地址,這樣以后,進程無需再調用read或write對文件進行讀寫,而只需要通過ptr就能夠操作文件。但是ptr所指向的是一個邏輯地址,要操作其中的數據,必須通過MMU將邏輯地址轉換成物理地址,如圖1中過程2所示。這個過程與內存映射無關。?
前面講過,建立內存映射并沒有實際拷貝數據,這時,MMU在地址映射表中是無法找到與ptr相對應的物理地址的,也就是MMU失敗,將產生一個缺頁中斷,缺頁中斷的中斷響應函數會在swap中尋找相對應的頁面,如果找不到(也就是該文件從來沒有被讀入內存的情況),則會通過mmap()建立的映射關系,從硬盤上將文件讀取到物理內存中,如圖1中過程3所示。這個過程與內存映射無關。
如果在拷貝數據時,發現物理內存不夠用,則會通過虛擬內存機制(swap)將暫時不用的物理頁面交換到硬盤上,如圖1中過程4所示。這個過程也與內存映射無關。
效率
從代碼層面上看,從硬盤上將文件讀入內存,都要經過文件系統進行數據拷貝,并且數據拷貝操作是由文件系統和硬件驅動實現的,理論上來說,拷貝數據的效率是一樣的。但是通過內存映射的方法訪問硬盤上的文件,效率要比read和write系統調用高,這是為什么呢?原因是read()是系統調用,其中進行了數據拷貝,它首先將文件內容從硬盤拷貝到內核空間的一個緩沖區,如圖2中過程1,然后再將這些數據拷貝到用戶空間,如圖2中過程2,在這個過程中,實際上完成了?兩次數據拷貝?;而mmap()也是系統調用,如前所述,mmap()中沒有進行數據拷貝,真正的數據拷貝是在缺頁中斷處理時進行的,由于mmap()將文件直接映射到用戶空間,所以中斷處理函數根據這個映射關系,直接將文件從硬盤拷貝到用戶空間,只進行了?一次數據拷貝?。因此,內存映射的效率要比read/write效率高。
圖2.read系統調用原理?
下面這個程序,通過read和mmap兩種方法分別對硬盤上一個名為“mmap_test”的文件進行操作,文件中存有10000個整數,程序兩次使用不同的方法將它們讀出,加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; }輸出結果:
Time?of?read/write:?154ms Time?of?mmap:?68ms總結
以上是生活随笔為你收集整理的Linux内存映射mmap原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 后Hadoop时代的大数据技术思考:数据
- 下一篇: 死磕Synchronized底层实现--