mmap Memory Mapping
原理
首先玖喘,“映射”這個(gè)詞茵休,就和數(shù)學(xué)課上說的“一一映射”是一個(gè)意思赛惩,就是建立一種一一對(duì)應(yīng)關(guān)系,在這里主要是只 硬盤上文件 的位置與進(jìn)程 邏輯地址空間 中一塊大小相同的區(qū)域之間的一一對(duì)應(yīng)搞旭,如圖1中過程1所示散怖。這種對(duì)應(yīng)關(guān)系純屬是邏輯上的概念,物理上是不存在的肄渗,原因是進(jìn)程的邏輯地址空間本身就是不存在的镇眷。在內(nèi)存映射的過程中,并沒有實(shí)際的數(shù)據(jù)拷貝翎嫡,文件沒有被載入內(nèi)存欠动,只是邏輯上被放入了內(nèi)存,具體到代碼惑申,就是建立并初始化了相關(guān)的數(shù)據(jù)結(jié)構(gòu)(struct address_space)具伍,這個(gè)過程有系統(tǒng)調(diào)用mmap()實(shí)現(xiàn),所以建立內(nèi)存映射的效率很高硝桩。
既然建立內(nèi)存映射沒有進(jìn)行實(shí)際的數(shù)據(jù)拷貝沿猜,那么進(jìn)程又怎么能最終直接通過內(nèi)存操作訪問到硬盤上的文件呢枚荣?那就要看內(nèi)存映射之后的幾個(gè)相關(guān)的過程了碗脊。
mmap()會(huì)返回一個(gè)指針ptr,它指向進(jìn)程邏輯地址空間中的一個(gè)地址橄妆,這樣以后衙伶,進(jìn)程無需再調(diào)用read或write對(duì)文件進(jìn)行讀寫,而只需要通過ptr就能夠操作文件害碾。但是ptr所指向的是一個(gè)邏輯地址矢劲,要操作其中的數(shù)據(jù),必須通過MMU將邏輯地址轉(zhuǎn)換成物理地址慌随,如圖1中過程2所示芬沉。這個(gè)過程與內(nèi)存映射無關(guān)。
前面講過阁猜,建立內(nèi)存映射并沒有實(shí)際拷貝數(shù)據(jù)丸逸,這時(shí),MMU在地址映射表中是無法找到與ptr相對(duì)應(yīng)的物理地址的剃袍,也就是MMU失敗黄刚,將產(chǎn)生一個(gè)缺頁中斷,缺頁中斷的中斷響應(yīng)函數(shù)會(huì)在swap中尋找相對(duì)應(yīng)的頁面民效,如果找不到(也就是該文件從來沒有被讀入內(nèi)存的情況)憔维,則會(huì)通過mmap()建立的映射關(guān)系涛救,從硬盤上將文件讀取到物理內(nèi)存中,如圖1中過程3所示业扒。這個(gè)過程與內(nèi)存映射無關(guān)检吆。
如果在拷貝數(shù)據(jù)時(shí),發(fā)現(xiàn)物理內(nèi)存不夠用程储,則會(huì)通過虛擬內(nèi)存機(jī)制(swap)將暫時(shí)不用的物理頁面交換到硬盤上咧栗,如圖1中過程4所示。這個(gè)過程也與內(nèi)存映射無關(guān)虱肄。
效率
從代碼層面上看致板,從硬盤上將文件讀入內(nèi)存,都要經(jīng)過文件系統(tǒng)進(jìn)行數(shù)據(jù)拷貝咏窿,并且數(shù)據(jù)拷貝操作是由文件系統(tǒng)和硬件驅(qū)動(dòng)實(shí)現(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)核空間的一個(gè)緩沖區(qū)凤粗,如圖2中過程1酥泛,然后再將這些數(shù)據(jù)拷貝到用戶空間,如圖2中過程2嫌拣,在這個(gè)過程中柔袁,實(shí)際上完成了 兩次數(shù)據(jù)拷貝 ;而mmap()也是系統(tǒng)調(diào)用异逐,如前所述捶索,mmap()中沒有進(jìn)行數(shù)據(jù)拷貝,真正的數(shù)據(jù)拷貝是在缺頁中斷處理時(shí)進(jìn)行的灰瞻,由于mmap()將文件直接映射到用戶空間腥例,所以中斷處理函數(shù)根據(jù)這個(gè)映射關(guān)系,直接將文件從硬盤拷貝到用戶空間酝润,只進(jìn)行了 一次數(shù)據(jù)拷貝 燎竖。因此,內(nèi)存映射的效率要比read/write效率高袍祖。
下面這個(gè)程序底瓣,通過read和mmap兩種方法分別對(duì)硬盤上一個(gè)名為“mmap_test”的文件進(jìn)行操作,文件中存有10000個(gè)整數(shù),程序兩次使用不同的方法將它們讀出捐凭,加1拨扶,再寫回硬盤。通過對(duì)比可以看出茁肠,read消耗的時(shí)間將近是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 10000
int 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