內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功後,用戶對這段內存區域的修改能夠直接反映到內核空間,一樣,內核空間對這段區域的修改也直接反映用戶空間。那麼對於內核空間<---->用戶空間二者之間須要大量數據傳輸等操做的話效率是很是高的。linux
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
首先,「映射」這個詞,就和數學課上說的「一一映射」是一個意思,就是創建一種一一對應關係,在這裏主要是隻 硬盤上文件 的位置與進程 邏輯地址空間 中一塊大小相同的區域之間的一一對應,如圖1中過程1所示。這種對應關係純屬是邏輯上的概念,物理上是不存在的,緣由是進程的邏輯地址空間自己就是不存在的。在內存映射的過程當中,並無實際的數據拷貝,文件沒有被載入內存,只是邏輯上被放入了內存,具體到代碼,就是創建並初始化了相關的數據結構(struct address_space),這個過程有系統調用mmap()實現,因此創建內存映射的效率很高。數據結構
圖1.內存映射原理 函數
既然創建內存映射沒有進行實際的數據拷貝,那麼進程又怎麼能最終直接經過內存操做訪問到硬盤上的文件呢?那就要看內存映射以後的幾個相關的過程了。spa
mmap()會返回一個指針ptr,它指向進程邏輯地址空間中的一個地址,這樣之後,進程無需再調用read或write對文件進行讀寫,而只須要經過ptr就可以操做文件。可是ptr所指向的是一個邏輯地址,要操做其中的數據,必須經過MMU將邏輯地址轉換成物理地址,如圖1中過程2所示。這個過程與內存映射無關。設計
前面講過,創建內存映射並無實際拷貝數據,這時,MMU在地址映射表中是沒法找到與ptr相對應的物理地址的,也就是MMU失敗,將產生一個缺頁中斷,缺頁中斷的中斷響應函數會在swap中尋找相對應的頁面,若是找不到(也就是該文件歷來沒有被讀入內存的狀況),則會經過mmap()創建的映射關係,從硬盤上將文件讀取到物理內存中,如圖1中過程3所示。這個過程與內存映射無關。unix
若是在拷貝數據時,發現物理內存不夠用,則會經過虛擬內存機制(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的兩到三倍。get
1 #include<unistd.h> 2 3 #include<stdio.h> 4 5 #include<stdlib.h> 6 7 #include<string.h> 8 9 #include<sys/types.h> 10 11 #include<sys/stat.h> 12 13 #include<sys/time.h> 14 15 #include<fcntl.h> 16 17 #include<sys/mman.h> 18 19 20 21 #define MAX 10000 22 23 24 25 int main() 26 27 { 28 29 int i=0; 30 31 int count=0, fd=0; 32 33 struct timeval tv1, tv2; 34 35 int *array = (int *)malloc( sizeof(int)*MAX ); 36 37 38 39 /*read*/ 40 41 42 43 gettimeofday( &tv1, NULL ); 44 45 fd = open( "mmap_test", O_RDWR ); 46 47 if( sizeof(int)*MAX != read( fd, (void *)array, sizeof(int)*MAX ) ) 48 49 { 50 51 printf( "Reading data failed.../n" ); 52 53 return -1; 54 55 } 56 57 for( i=0; i<MAX; ++i ) 58 59 60 61 ++array[ i ]; 62 63 if( sizeof(int)*MAX != write( fd, (void *)array, sizeof(int)*MAX ) ) 64 65 { 66 67 printf( "Writing data failed.../n" ); 68 69 return -1; 70 71 } 72 73 free( array ); 74 75 close( fd ); 76 77 gettimeofday( &tv2, NULL ); 78 79 printf( "Time of read/write: %dms/n", tv2.tv_usec-tv1.tv_usec ); 80 81 82 83 /*mmap*/ 84 85 86 87 gettimeofday( &tv1, NULL ); 88 89 fd = open( "mmap_test", O_RDWR ); 90 91 array = mmap( NULL, sizeof(int)*MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 ); 92 93 for( i=0; i<MAX; ++i ) 94 95 96 97 ++array[ i ]; 98 99 munmap( array, sizeof(int)*MAX ); 100 101 msync( array, sizeof(int)*MAX, MS_SYNC ); 102 103 free( array ); 104 105 close( fd ); 106 107 gettimeofday( &tv2, NULL ); 108 109 printf( "Time of mmap: %dms/n", tv2.tv_usec-tv1.tv_usec ); 110 111 112 113 return 0; 114 115 }
輸出結果:
Time of read/write: 154ms
Time of mmap: 68ms