1、mmap基礎(chǔ)概念
mmap 是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其他對象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一映射關(guān)系。
實(shí)現(xiàn)這樣的映射關(guān)系后,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會自動(dòng)回寫臟頁面到對應(yīng)的文件磁盤上,即完成了對文件的操作而不必調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)。相反,內(nèi)核空間的這段區(qū)域的修改也直接反應(yīng)用戶空間,從而可以實(shí)現(xiàn)不同進(jìn)程的文件共享。如下圖所示:
由上圖可以看出,進(jìn)程的虛擬地址空間,由多個(gè)虛擬內(nèi)存區(qū)域構(gòu)成。虛擬內(nèi)存區(qū)域是進(jìn)程的虛擬地址空間中的一個(gè)同質(zhì)區(qū)間,即具有同樣特性的連續(xù)地址范圍。上圖中所示的text數(shù)據(jù)段、初始數(shù)據(jù)段、Bss數(shù)據(jù)段、堆、棧、內(nèi)存映射,都是一個(gè)獨(dú)立的虛擬內(nèi)存區(qū)域。而為內(nèi)存映射服務(wù)的地址空間處在堆棧之間的空余部分。
linux 內(nèi)核使用的vm_area_struct 結(jié)構(gòu)來表示一個(gè)獨(dú)立的虛擬內(nèi)存區(qū)域,由于每個(gè)不同質(zhì)的虛擬內(nèi)存區(qū)域功能和內(nèi)部機(jī)制不同;因此同一個(gè)進(jìn)程使用多個(gè)vm_area_struct 結(jié)構(gòu)來分別表示不同類型的虛擬內(nèi)存區(qū)域。各個(gè)vm_area_struct 結(jié)構(gòu)使用鏈表或者樹形結(jié)構(gòu)鏈接,方便進(jìn)程快速訪問。如下圖所示:
vm_area_struct 結(jié)構(gòu)中包含區(qū)域起始和終止地址以及其他相關(guān)信息,同時(shí)也包含一個(gè)vm_ops 指針,其內(nèi)部可引出所有針對這個(gè)區(qū)域可以使用的系統(tǒng)調(diào)用函數(shù)。這樣,進(jìn)程對某一虛擬內(nèi)存區(qū)域的任何操作都需要的信息,都可以從vm_area_struct 中獲得。mmap函數(shù)就是要?jiǎng)?chuàng)建一個(gè)新的vm_area_struct結(jié)構(gòu) ,并將其與文件的物理磁盤地址相連。具體步驟如下:
2、mmap 內(nèi)存映射原理
mmap 內(nèi)存映射實(shí)現(xiàn)過程,總的來說可以分為三個(gè)階段:
(一)進(jìn)程啟動(dòng)映射過程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
1、進(jìn)程在用戶空間調(diào)用函數(shù)mmap ,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
2、在當(dāng)前進(jìn)程虛擬地址空間中,尋找一段空閑的滿足要求的連續(xù)的虛擬地址
3、為此虛擬區(qū)分配一個(gè)vm_area_struct 結(jié)構(gòu),接著對這個(gè)結(jié)構(gòu)各個(gè)區(qū)域進(jìn)行初始化
4、將新建的虛擬區(qū)結(jié)構(gòu)(vm_area_struct)插入進(jìn)程的虛擬地址區(qū)域鏈表或樹中
(二)調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap (不同于用戶空間函數(shù)),實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系
5、為映射分配新的虛擬地址區(qū)域后,通過待映射的文件指針,在文件描述符表中找到對應(yīng)的文件描述符,通過文件描述符,鏈接到內(nèi)核“已打開文集”中該文件結(jié)構(gòu)體,每個(gè)文件結(jié)構(gòu)體維護(hù)者和這個(gè)已經(jīng)打開文件相關(guān)各項(xiàng)信息。
6、通過該文件的文件結(jié)構(gòu)體,鏈接到file_operations模塊,調(diào)用內(nèi)核函數(shù)mmap,其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用戶空間庫函數(shù)。
7、內(nèi)核mmap函數(shù)通過虛擬文件系統(tǒng)inode模塊定位到文件磁盤物理地址。
8、通過remap_pfn_range函數(shù)建立頁表,即實(shí)現(xiàn)了文件地址和虛擬地址區(qū)域的映射關(guān)系。此時(shí),這片虛擬地址并沒有任何數(shù)據(jù)關(guān)聯(lián)到主存中。
(三)進(jìn)程發(fā)起對這片映射空間的訪問,引發(fā)缺頁異常,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝。
前兩個(gè)階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射,但是并沒有將任何文件數(shù)據(jù)拷貝至主存。真正的文件讀取是當(dāng)進(jìn)程發(fā)起讀或者寫操作時(shí)。
9、進(jìn)程的讀寫操作訪問虛擬地址空間這一段映射地址后,通過查詢頁表,先這一段地址并不在物理頁面。因?yàn)槟壳爸唤⒘擞成洌嬲挠脖P數(shù)據(jù)還沒有拷貝到內(nèi)存中,因此引發(fā)缺頁異常。
10、缺頁異常進(jìn)行一系列判斷,確定無法操作后,內(nèi)核發(fā)起請求掉頁過程。
11、調(diào)頁過程先在交換緩存空間中尋找需要訪問的內(nèi)存頁,,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中。
12、之后進(jìn)程即可對這片主存進(jìn)行讀或者寫的操作了,如果寫操作改變了內(nèi)容,一定時(shí)間后系統(tǒng)自動(dòng)回寫臟頁面到對應(yīng)的磁盤地址,也即完成了寫入到文件的過程。
注:修改過的臟頁面并不會立即更新回文件,而是有一段時(shí)間延遲,可以調(diào)用msync() 來強(qiáng)制同步,這樣所寫的內(nèi)容就能立即保存到文件里了。
3、mmap和常規(guī)文件操作的區(qū)別
首先我們來回顧一下常規(guī)文件操作,函數(shù)的調(diào)用過程:
1、進(jìn)程發(fā)起讀文件請求
2、內(nèi)核通過查找進(jìn)程文件符表。
3、inode在address_space上查找要請求的文件頁是否已經(jīng)緩存在頁緩存中。如果存在,則直接返回這片文件頁的內(nèi)容。
總的來說,常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤,使用了頁緩存機(jī)制,這樣造成了讀文件時(shí)需要先將文件頁從磁盤拷貝到緩存中,由于頁緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內(nèi)存對用的用戶空間中。這樣通過兩次拷貝過程,才能完成進(jìn)程對文件內(nèi)容的獲取。寫操作也一樣,待寫入的Buffer在內(nèi)核空間不能直接訪問,必須先拷貝到內(nèi)核空間對應(yīng)的主存,再回寫磁盤中,也是需要兩次數(shù)據(jù)拷貝。而使用mmap 操作文件中,創(chuàng)建新的虛擬內(nèi)存區(qū)域、建立文件磁盤地址和內(nèi)存區(qū)域映射這兩步,沒有任何文件拷貝操作。而之后訪問數(shù)據(jù)時(shí),發(fā)現(xiàn)內(nèi)存中并無數(shù)據(jù)而發(fā)起的缺失頁異常過程,可以通過建立好的映射關(guān)系,只使用一次數(shù)據(jù)拷貝,就從磁盤中將數(shù)據(jù)傳入內(nèi)存的用戶空間中,供過程使用。
總而言之,常規(guī)的文件操作需要從磁盤到頁緩存再到用戶主存的兩次數(shù)據(jù)拷貝,而mmap操作文件,只需要從磁盤到用戶主存的一次數(shù)據(jù)拷貝過程。說白了,mmap的關(guān)鍵點(diǎn)是實(shí)現(xiàn)了用戶空間和內(nèi)核的數(shù)據(jù)直接交互省去了空間不同數(shù)據(jù)不通的繁瑣過程。因此 mmap效率更高。
mmap優(yōu)點(diǎn)總結(jié)
由上文討論可知,mmap 優(yōu)點(diǎn)共有以下幾點(diǎn):
對文件的讀取操作跨過了頁緩存,減少了數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代了I/O讀寫,提高了讀取的效率。
實(shí)現(xiàn)了用戶空間和內(nèi)核空間的高校交互方式,兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi),從而被對方空間及時(shí)捕捉。
提供進(jìn)程間共享內(nèi)存及互相通信的方式。不管是父子進(jìn)程還是無親緣關(guān)系進(jìn)程,都可以將自身空間用戶映射到同一個(gè)文件或者匿名映射到同一片區(qū)域。從而通過各自映射區(qū)域的改動(dòng),打到進(jìn)程間通信和進(jìn)程間共享的目的。
同時(shí),如果進(jìn)程A和進(jìn)程 B 都映射了區(qū)域C,當(dāng)A第一次讀取C時(shí)候,通過缺頁從磁盤復(fù)制文件頁到內(nèi)存中,但當(dāng)B再讀C的相同頁面時(shí),雖然也會產(chǎn)生缺頁異常,但是不會從磁盤中復(fù)制文件過來,而是直接使用已經(jīng)保存再內(nèi)存中的文件數(shù)據(jù)。
可用于實(shí)現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個(gè)方面,解決方案往往是借助于硬盤空間的協(xié)助,補(bǔ)充內(nèi)存的不足。但是進(jìn)一步造成大量的文件I/O操作,極大影響效率。這個(gè)問題可以通過mmap映射很好的解決。換句話說,但凡需要磁盤空間代替內(nèi)存的時(shí)候,mmap都可以發(fā)揮功效。
mmap使用細(xì)節(jié)
使用mmap需要注意一點(diǎn),mmap映射區(qū)域大小必須是物理頁大小(page_size)的整數(shù)倍,原因是:內(nèi)存的最小粒度是頁,而進(jìn)程虛擬地址空間和內(nèi)存的映射單位也是以頁為單位,為了匹配內(nèi)存操作,mmap從磁盤到虛擬地址空間的映射也必須是頁。
內(nèi)核可以跟蹤被內(nèi)存映射的底層對象,大小。就是說,如果文件的大小一直再擴(kuò)張,只要再映射區(qū)域范圍內(nèi)的數(shù)據(jù),進(jìn)程都可以依法得到,這和映射建立時(shí)文件的大小無關(guān)。
映射建立后,即使文件關(guān)閉,映射依然存在。因?yàn)橛成涞氖谴疟P的地址,不是文件本身,和文件句柄無關(guān),同時(shí)可用于進(jìn)程間通信的有效地址空間,不完全受限于被映射文件的大小,因?yàn)槭前错撚成洹?/p>