Linux 內存映射 mmap 原理分析
本文轉自博客,我修改了一些筆誤,并劃了一些我自認為的重點。
原理
首先,“映射”這個詞,就和數學課上說的“一一映射”是一個意思,就是建立一種一一對應關系,在這里主要是指?硬盤上文件?的位置與 進程邏輯地址空間?中一塊大小相同的區域之間的一一對應,如?圖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 效率高
下面這個程序,通過 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 )
++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);
}
輸出結果:
Time?of?read/write:?154ms
Time?of?mmap:?68ms
linux 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。