轉自:https://www.cnblogs.com/huxiao-tee/p/4660352.html
內存映射是一種重要的抽象手段。在內核中大量使用,也可以用於用戶應用程序。映射方法可以將任意來源的數據傳輸到進程的虛擬地址空間中。作為映射目標的地址空間區域,可以像普通內存那樣用通常的方法訪問。但任何修改都會自動傳輸到原數據源。這樣就可以使用相同的函數來處理完全不同的目標對象。例如,文件的內容可以映射到內存中。處理只需讀取相應的內存即可訪問文件內容,或向內存寫入數據來修改文件的內容。內核將保證任何修改都會自動同步到文件中。
mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系。實現這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫臟頁面到對應的文件磁盤上,即完成了對文件的操作而不必再調用read,write等系統調用函數。相反,內核空間對這段區域的修改也直接反映用戶空間,從而可以實現不同進程間的文件共享。如下圖所示:
由上圖可以看出,進程的虛擬地址空間,由多個虛擬內存區域構成。虛擬內存區域是進程的虛擬地址空間中的一個同質區間,即具有同樣特性的連續地址范圍。上圖中所示的text數據段(代碼段)、初始數據段、BSS數據段、堆、棧和內存映射,都是一個獨立的虛擬內存區域。而為內存映射服務的地址空間處在堆棧之間的空余部分。
linux內核使用vm_area_struct結構來表示一個獨立的虛擬內存區域,由於每個不同質的虛擬內存區域功能和內部機制都不同,因此一個進程使用多個vm_area_struct結構來分別表示不同類型的虛擬內存區域。各個vm_area_struct結構使用鏈表或者樹形結構鏈接,方便進程快速訪問,如下圖所示:
mmap原理:
(一)進程啟動映射過程,並在虛擬地址空間中為映射創建虛擬映射區域
1、進程在用戶空間調用庫函數mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
2、在當前進程的虛擬地址空間中,尋找一段空閑的滿足要求的連續的虛擬地址;
3、為此虛擬區分配一個vm_area_struct結構,接着對這個結構的各個域進行了初始化;
4、將新建的虛擬區結構(vm_area_struct)插入進程的虛擬地址區域鏈表或樹中;
(二)調用內核空間的系統調用函數mmap(不同於用戶空間函數),實現文件物理地址和進程虛擬地址的一一映射關系
5、為映射分配了新的虛擬地址區域后,通過待映射的文件指針,在文件描述符表中找到對應的文件描述符,通過文件描述符,鏈接到內核“已打開文件集”中該文件的文件結構體(struct file),每個文件結構體維護着和這個已打開文件相關各項信息。
6、通過該文件的文件結構體,鏈接到file_operations模塊,調用內核函數mmap,其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同於用戶空間庫函數。
7、內核mmap函數通過虛擬文件系統inode模塊定位到文件磁盤物理地址。
8、通過remap_pfn_range函數建立頁表,即實現了文件地址和虛擬地址區域的映射關系。此時,這片虛擬地址並沒有任何數據關聯到主存中。
(三)進程發起對這片映射空間的訪問,引發缺頁異常,實現文件內容到物理內存(主存)的拷貝
注:前兩個階段僅在於創建虛擬區間並完成地址映射,但是並沒有將任何文件數據的拷貝至主存。真正的文件讀取是當進程發起讀或寫操作時。
9、進程的讀或寫操作訪問虛擬地址空間這一段映射地址,通過查詢頁表,發現這一段地址並不在物理頁面上。因為目前只建立了地址映射,真正的硬盤數據還沒有拷貝到內存中,因此引發缺頁異常。
10、缺頁異常進行一系列判斷,確定無非法操作后,內核發起請求調頁過程。
11、調頁過程先在交換緩存空間(swap cache)中尋找需要訪問的內存頁,如果沒有則調用nopage函數把所缺的頁從磁盤裝入到主存中。
12、之后進程即可對這片主存進行讀或者寫的操作,如果寫操作改變了其內容,一定時間后系統會自動回寫臟頁面到對應磁盤地址,也即完成了寫入到文件的過程。
注:修改過的臟頁面並不會立即更新回文件中,而是有一段時間的延遲,可以調用msync()來強制同步, 這樣所寫的內容就能立即保存到文件里了。
mmap和常規文件操作的區別:
1、進程發起讀文件請求。
2、內核通過查找進程文件符表,定位到內核已打開文件集上的文件信息,從而找到此文件的inode。
3、inode在address_space上查找要請求的文件頁是否已經緩存在頁緩存中。如果存在,則直接返回這片文件頁的內容。
4、如果不存在,則通過inode定位到文件磁盤地址,將數據從磁盤復制到頁緩存。之后再次發起讀頁面過程,進而將頁緩存中的數據發給用戶進程。
總結來說,常規文件操作為了提高讀寫效率和保護磁盤,使用了頁緩存機制。這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由於頁緩存處在內核空間,不能被用戶進程直接尋址,所以還需要將頁緩存中數據頁再次拷貝到內存對應的用戶空間中。這樣,通過了兩次數據拷貝過程,才能完成進程對文件內容的獲取任務。寫操作也是一樣,待寫入的buffer在內核空間不能直接訪問,必須要先拷貝至內核空間對應的主存,再寫回磁盤中(延遲寫回),也是需要兩次數據拷貝。
而使用mmap操作文件中,創建新的虛擬內存區域和建立文件磁盤地址和虛擬內存區域映射這兩步,沒有任何文件拷貝操作。而之后訪問數據時發現內存中並無數據而發起的缺頁異常過程,可以通過已經建立好的映射關系,只使用一次數據拷貝,就從磁盤中將數據傳入內存的用戶空間中,供進程使用。
總而言之,常規文件操作需要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。而mmap操控文件,只需要從磁盤到用戶主存的一次數據拷貝過程。說白了,mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不同數據不通的繁瑣過程。因此mmap效率更高。
mmap優點總結:
由上文討論可知,mmap優點共有一下幾點:
1、對文件的讀取操作跨過了頁緩存,減少了數據的拷貝次數,用內存讀寫取代I/O讀寫,提高了文件讀取效率。
2、實現了用戶空間和內核空間的高效交互方式。兩空間的各自修改操作可以直接反映在映射的區域內,從而被對方空間及時捕捉。
3、提供進程間共享內存及相互通信的方式。不管是父子進程還是無親緣關系的進程,都可以將自身用戶空間映射到同一個文件或匿名映射到同一片區域。從而通過各自對映射區域的改動,達到進程間通信和進程間共享的目的。
同時,如果進程A和進程B都映射了區域C,當A第一次讀取C時通過缺頁從磁盤復制文件頁到內存中;但當B再讀C的相同頁面時,雖然也會產生缺頁異常,但是不再需要從磁盤中復制文件過來,而可直接使用已經保存在內存中的文件數據。
4、可用於實現高效的大規模數據傳輸。內存空間不足,是制約大數據操作的一個方面,解決方案往往是借助硬盤空間協助操作,補充內存的不足。但是進一步會造成大量的文件I/O操作,極大影響效率。這個問題可以通過mmap映射很好的解決。換句話說,但凡是需要用磁盤空間代替內存的時候,mmap都可以發揮其功效。