記憶體映射 mmap 是 Linux 系統中重要的記憶體管理機制,它可以讓使用者空間的進程直接存取實體記憶體或文件,而不需要透過系統呼叫或複製資料。這樣可以提高記憶體的使用率和存取效率,節省系統資源和時間。但是,你真的了解 mmap 的工作原理嗎?你知道如何在 Linux 下使用 mmap 嗎?你知道 mmap 的優點和限制嗎?本文將為你詳細介紹 Linux 下的記憶體映射 mmap 的相關知識,讓你在 Linux 下更好地使用和理解這個強大的記憶體管理工具。
二、基本函數
#mmap函數是unix/linux下的系統調用,詳細內容可參考《Unix Netword programming》卷二12.2節。
mmap系統呼叫並不是完全為了用於共享記憶體而設計的。它本身提供了不同於一般對普通文件的存取方式,進程可以像讀寫記憶體一樣對普通文件的操作。而Posix或系統V的共享記憶體IPC則純粹用於共享目的,當然mmap()實作共享記憶體也是其主要應用之一。
<code style="display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px"> **mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射** </code>
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
mmap用來把檔案映射到記憶體空間中,簡單說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效率高。
图2.read系统调用原理
下面这个程序,通过read和mmap两种方法分别对硬盘上一个名为“mmap_test”的文件进行操作,文件中存有10000个整数,程序两次使用不同的方法将它们读出,加1,再写回硬盘。通过对比可以看出,read消耗的时间将近是mmap的两到三倍。
1 #include 2 3 #include 4 5 #include 6 7 #include 8 9 #include 10 11 #include 12 13 #include 14 15 #include 16 17 #include 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; iif( 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; iprintf( "Time of mmap: %dms/n", tv2.tv_usec-tv1.tv_usec ); 110 111 112 113 return 0; 114 115 }
通过本文,你应该对 Linux 下的内存映射 mmap 有了一个深入的了解,知道了它的定义、原理、用法和优势。你也应该明白了 mmap 的适用场景和注意事项,以及如何在 Linux 下正确地使用 mmap。我们建议你在需要高效地访问物理内存或者文件时,使用 mmap 来提高程序的性能和可移植性。同时,我们也提醒你在使用 mmap 时要注意一些潜在的风险和问题,如同步、保护、错误处理等。希望本文能够帮助你更好地使用 Linux 系统,让你在 Linux 下享受内存映射的便利和快感。
以上是Linux 下的記憶體映射 mmap:原理、用法和優勢的詳細內容。更多資訊請關注PHP中文網其他相關文章!