先說結論:使用內存映射文件來處理大文件可以提高效率。
爲什麼呢?
我們先來看看如果不使用內存映射文件的處理流程是怎樣的,首先我們得先讀出磁盤文件的內容到內存中,然後修改,最後回寫到磁盤上。第一步讀磁盤文件是要經過一次系統調用的,它首先將文件內容從磁盤拷貝到內核空間的一個緩衝區,然後再將這些數據拷貝到用戶空間,實際上是兩次數據拷貝。第三步回寫也一樣也要經過兩次數據拷貝。
所以我們基本上會有四次數據的拷貝了,因爲大文件數據量很大,幾十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;
}