內存映射文件機制處理大文件

先說結論:使用內存映射文件來處理大文件可以提高效率。 

爲什麼呢?

我們先來看看如果不使用內存映射文件的處理流程是怎樣的,首先我們得先讀出磁盤文件的內容到內存中,然後修改,最後回寫到磁盤上。第一步讀磁盤文件是要經過一次系統調用的,它首先將文件內容從磁盤拷貝到內核空間的一個緩衝區,然後再將這些數據拷貝到用戶空間,實際上是兩次數據拷貝。第三步回寫也一樣也要經過兩次數據拷貝。

所以我們基本上會有四次數據的拷貝了,因爲大文件數據量很大,幾十GB甚至更大,所以拷貝的開銷是非常大的。


而內存映射文件是操作系統的提供的一種機制,可以減少這種不必要的數據拷貝,從而提高效率。它由mmap()將文件直接映射到用戶空間,mmap()並沒有進行數據拷貝,真正的數據拷貝是在缺頁中斷處理時進行的,由於mmap()將文件直接映射到用戶空間,所以中斷處理函數根據這個映射關係,直接將文件從硬盤拷貝到用戶空間,所以只進行了一次數據拷貝 ,比read進行兩次數據拷貝要好上一倍,因此,內存映射的效率要比read/write效率高。


一般來說,read write操作可以滿足大多數文件操作的要求,但是對於某些特殊應用領域所需要的幾十GB甚至更大的存儲,這種通常的文件處理方法進行處理顯然是行不通的。目前,對於上述大文件的操作一般是以內存映射文件的方式來加以處理的。


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

 

原理

首先,“映射”這個詞,就和數學課上說的“一一映射”是一個意思,就是建立一種一一對應關係,在這裏主要是隻 硬盤上文件 的位置與進程 邏輯地址空間 中 一塊大小相同的區域之間的一一對應,如圖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

#include

#include

#include

#include

#include

#include

#include

#include

 

#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 )< span="" style="word-wrap: break-word;">

 

++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 )< span="" style="word-wrap: break-word;">

 

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

}

 

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章