一直都對記憶體對映檔案這個概念很模糊,不知道它和虛擬記憶體有什麼區別,而且對映這個詞也很讓人迷茫,今天終於搞清楚了。。。下面,我先解釋一下我對對映這個詞的理解,再區分一下幾個容易混淆的概念,之後,什麼是記憶體對映就很明朗了。

原理

首先,“對映”這個詞,就和數學課上說的“一一對映”是一個意思,就是建立一種一一對應關係,在這裡主要是隻 硬碟上檔案 的位置與程序 邏輯地址空間 中一塊大小相同的區域之間的一一對應,如圖1中過程1所示。這種對應關係純屬是邏輯上的概念,物理上是不存在的,原因是程序的邏輯地址空間本身就是不存在的。在記憶體對映的過程中,並沒有實際的資料複製,檔案沒有被載入記憶體,只是邏輯上被放入了記憶體,具體到程式碼,就是建立並初始化了相關的資料結構(struct address_space),這個過程有系統呼叫mmap()實現,所以建立記憶體對映的效率很高。

Linux記憶體對映mmap原理分析

既然建立記憶體對映沒有進行實際的資料複製,那麼程序又怎麼能最終直接透過記憶體操作訪問到硬碟上的檔案呢?那就要看記憶體對映之後的幾個相關的過程了。

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效率高。

Linux記憶體對映mmap原理分析

下面這個程式,透過read和mmap兩種方法分別對硬碟上一個名為“mmap_test”的檔案進行操作,檔案中存有10000個整數,程式兩次使用不同的方法將它們讀出,加1,再寫回硬碟。透過對比可以看出,read消耗的時間將近是mmap的兩到三倍。

WR );

if( sizeof(int)*MAX != read( fd, (void *)array, sizeof(int)*MAX ) )

{

printf( “Reading data failed。。。/n” );

return -1;

}

for( i=0; 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

++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