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
;}
輸出結果:
of read write 154 of 68Time
Time
●編號656,輸入編號直達本文
●輸入m獲取文章
目錄
推薦↓↓↓
運維
更多推薦
《
25個技術類公眾微信
》
涵蓋:程序人生、演算法與數據結構、黑客技術與網路安全、大數據技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。
※從微觀角度來看Linux內核設計
※linux.org被黑客攻擊了,或因對Linux行為準則不滿
TAG:Linux學習 |