mmap 内存映射详解
目錄
mmap基礎(chǔ)概念
mmap內(nèi)存映射原理
mmap示例代碼
mmap和常規(guī)文件操作的區(qū)別
mmap使用的細(xì)節(jié)
前言
原文對 mmap 內(nèi)存映射已經(jīng)表述的很清楚了,我只是在原文的基礎(chǔ)上,附上了 mmap 代碼實(shí)例。
?
mmap基礎(chǔ)概念
mmap是一種內(nèi)存映射的方法,這一功能可以用在文件的處理上,即將一個文件或者其它對象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對映關(guān)系。在編程時可以使某個磁盤文件的內(nèi)容看起來像是內(nèi)存中的一個數(shù)組。如果文件由記錄組成,而這些記錄又能夠用結(jié)構(gòu)體來描述的話,可以通過訪問結(jié)構(gòu)數(shù)組來更新文件的內(nèi)容。
實(shí)現(xiàn)這樣的映射關(guān)系后,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會自動回寫臟頁面到對應(yīng)的文件磁盤上,即完成了對文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)。內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間,從而可以實(shí)現(xiàn)不同進(jìn)程間的文件共享。如圖所示:
進(jìn)程的虛擬地址空間,由多個虛擬內(nèi)存區(qū)域構(gòu)成。虛擬內(nèi)存區(qū)域是進(jìn)程的虛擬地址空間中的一個同質(zhì)區(qū)間,即具有同樣特性的連續(xù)地址范圍。上圖中所示的text數(shù)據(jù)段(代碼段)、初始數(shù)據(jù)段、BSS數(shù)據(jù)段、堆、棧和內(nèi)存映射,都是一個獨(dú)立的虛擬內(nèi)存區(qū)域。而為內(nèi)存映射服務(wù)的地址空間處在堆棧之間的空余部分。
內(nèi)核為系統(tǒng)中的每個進(jìn)程維護(hù)一個單獨(dú)的任務(wù)結(jié)構(gòu)(task_struct)。任務(wù)結(jié)構(gòu)中的元素包含或者指向內(nèi)核運(yùn)行該進(jìn)程所需的所有信息(PID、指向用戶棧的指針、可執(zhí)行目標(biāo)文件的名字、程序計(jì)數(shù)器等)。Linux內(nèi)核使用vm_area_struct結(jié)構(gòu)來表示一個獨(dú)立的虛擬內(nèi)存區(qū)域,由于每個不同質(zhì)的虛擬內(nèi)存區(qū)域功能和內(nèi)部機(jī)制都不同,因此一個進(jìn)程使用多個vm_area_struct結(jié)構(gòu)來分別表示不同類型的虛擬內(nèi)存區(qū)域。各個vm_area_struct結(jié)構(gòu)使用鏈表或者樹形結(jié)構(gòu)鏈接,方便進(jìn)程快速訪問,如下圖所示:
vm_area_struct結(jié)構(gòu)中包含區(qū)域起始和終止地址以及其他相關(guān)信息,同時也包含一個vm_ops指針,其內(nèi)部可引出所有針對這個區(qū)域可以使用的系統(tǒng)調(diào)用函數(shù)。這樣,進(jìn)程對某一虛擬內(nèi)存區(qū)域的任何操作需要用要的信息,都可以從vm_area_struct中獲得。mmap函數(shù)就是要創(chuàng)建一個新的vm_area_struct結(jié)構(gòu),并將其與文件的物理磁盤地址相連。
mm_struct:描述了虛擬內(nèi)存的當(dāng)前狀態(tài)。pgd指向一級頁表的基址(當(dāng)內(nèi)核運(yùn)行這個進(jìn)程時, pgd會被存放在CR3控制寄存器,也就是頁表基址寄存器中),mmap指向一個vm_area_structs 的鏈表,其中每個vm_area_structs都描述了當(dāng)前虛擬地址空間的一個區(qū)域。 vm_starts 指向這個區(qū)域的起始處。 vm_end 指向這個區(qū)域的結(jié)束處。 vm_prot 描述這個區(qū)域內(nèi)包含的所有頁的讀寫許可權(quán)限。 vm_flags 描述這個區(qū)域內(nèi)的頁面是與其他進(jìn)程共享的,還是這個進(jìn)程私有的以及一些其他信息。 vm_next 指向鏈表的下一個區(qū)域結(jié)構(gòu)。?
mmap內(nèi)存映射原理
mmap內(nèi)存映射的實(shí)現(xiàn)過程,總的來說可以分為三個階段:
(一)進(jìn)程啟動映射過程,并在虛擬地址空間中為映射創(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ū)分配一個vm_area_struct結(jié)構(gòu),接著對這個結(jié)構(gòu)的各個域進(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)體(struct file),每個文件結(jié)構(gòu)體維護(hù)著和這個已打開文件相關(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ù)據(jù)關(guān)聯(lián)到主存中。
?
(三)進(jìn)程發(fā)起對這片映射空間的訪問,引發(fā)缺頁異常,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝
注:前兩個階段僅在于創(chuàng)建虛擬區(qū)間并完成地址映射,但是并沒有將任何文件數(shù)據(jù)的拷貝至主存。真正的文件讀取是當(dāng)進(jìn)程發(fā)起讀或?qū)懖僮鲿r。
9. 進(jìn)程的讀或?qū)懖僮髟L問虛擬地址空間這一段映射地址,通過查詢頁表,發(fā)現(xiàn)這一段地址并不在物理頁面上。因?yàn)槟壳爸唤⒘说刂酚成?#xff0c;真正的硬盤數(shù)據(jù)還沒有拷貝到內(nèi)存中,因此引發(fā)缺頁異常。
10. 缺頁異常進(jìn)行一系列判斷,確定無非法操作后,內(nèi)核發(fā)起請求調(diào)頁過程。
11. 調(diào)頁過程先在交換緩存空間(swap?cache)中尋找需要訪問的內(nèi)存頁,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中。
12. 之后進(jìn)程即可對這片主存進(jìn)行讀或者寫的操作,如果寫操作改變了其內(nèi)容,一定時間后系統(tǒng)會自動回寫臟頁面到對應(yīng)磁盤地址,也即完成了寫入到文件的過程。
注意:修改過的臟頁面并不會立即更新回文件中,而是有一段時間的延遲,可以調(diào)用msync()來強(qiáng)制同步, 這樣所寫的內(nèi)容就能立即保存到文件里了。
?
?
mmap 示例代碼
mmap (內(nèi)存映射)函數(shù)的作用是建立一段可以被兩個或更多個程序讀寫的內(nèi)存。一個程序?qū)λ龀龅男薷目梢员黄渌绦蚩匆姟_@要通過使用帶有特殊權(quán)限集的虛擬內(nèi)存段來實(shí)現(xiàn)。對這類虛擬內(nèi)存段的讀寫會使操作系統(tǒng)去讀寫磁盤文件中與之對應(yīng)的部分。 mmap 函數(shù)創(chuàng)建一個指向一段內(nèi)存區(qū)域的指針,該內(nèi)存區(qū)域與可以通過一個打開的文件描述符訪問的文件的內(nèi)容相關(guān)聯(lián)。mmap 函數(shù)原型如下:
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);可以通過傳遞?off 參數(shù)來改變經(jīng)共享內(nèi)存段訪問的文件中數(shù)據(jù)的起始偏移值。打開的文件描述符由 fildes 參數(shù)給出。可以訪問的數(shù)據(jù)量(即內(nèi)存段的長度)由 len 參數(shù)設(shè)置。
可以通過 addr 參數(shù)來請求使用某個特定的內(nèi)存地址。如果它的取值是零,結(jié)果指針就將自動分配。這是推薦的做法,否則會降低程序的可移植性,因?yàn)椴煌到y(tǒng)上的可用地址范圍是不一樣的。
prot 參數(shù)用于設(shè)置內(nèi)存段的訪問權(quán)限。它是下列常數(shù)值的按位或的結(jié)果:
PROT_READ 內(nèi)存段可讀。 PROT_WRITE 內(nèi)存段可寫。 PROT_EXEC 內(nèi)存段可執(zhí)行。 PROT_NONE 內(nèi)存段不能被訪問。flags 參數(shù)控制程序?qū)υ搩?nèi)存段的改變所造成的影響:
?
msync 函數(shù)的作用是:把在該內(nèi)存段的某個部分或整段中的修改寫回到被映射的文件中(或者從被映射文件里讀出)。
#include <sys/mman.h> int msync(void *addr, size_t len, int flags);內(nèi)存段需要修改的部分由作為參數(shù)傳遞過來的起始地址 addr 和長度 len 確定。flags 參數(shù)控制著執(zhí)行修改的具體方式,可以使用的選項(xiàng)如下:
MS_ASYNC ???????????????????采用異步寫方式 MS_SYNC ????????????????????采用同步寫方式 MS_INVALIDATE ??????????????從文件中讀回?cái)?shù)據(jù)?
munmap 函數(shù)的作用是釋放內(nèi)存段:
#include <sys/mman.h> int munmap(void *addr, size_t length);?
示例代碼:
(1) 定義一個 RECORD 數(shù)據(jù)結(jié)構(gòu),然后創(chuàng)建出 NRECORDS 個記錄,每個記錄中保存著它們各自的編號。然后把這些記錄都追加到文件 records.dat 里去。
(2) 接著,把第 43 條記錄中的整數(shù)值由 43 修改為 143,并把它寫入第 43 條記錄中的字符串。
(3) 把這些記錄映射到內(nèi)存中,然后訪問第 43 條記錄,把它的整數(shù)值修改為 243 (同時更新該記錄中的字符串),使用的還是內(nèi)存映射的方法。
可以將上述 (2) (3) 分別編寫程序驗(yàn)證結(jié)果。?
#include <unistd.h> #include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <stdlib.h>typedef struct{int integer;char string[24]; }RECORD;#define NRECORDS (100)int main() {RECORD record, *mapped;int i, f;FILE *fp;fp = fopen("records.dat", "w+");for( i = 0; i < NRECORDS; i++){record.integer = i;sprintf(record.string, "[RECORD-%d]", i);fwrite(&record, sizeof(record), 1, fp);}fclose(fp);fp = fopen("records.dat", "r+");fseek(fp, 43 * sizeof(record), SEEK_SET);fread(&record, sizeof(record), 1, fp);record.integer = 143;sprintf(record.string, "[RECORD-%d]", record.integer);fseek(fp, 43 * sizeof(record), SEEK_SET);fwrite(&record, sizeof(record), 1, fp);fclose(fp);f = open("records.dat", O_RDWR);mapped = (RECORD*)mmap(0, NRECORDS * sizeof(record), PROT_READ | PROT_WRITE, MAP_SHARED, f, 0);printf("f:[%d]\n", f);//open是系統(tǒng)調(diào)用,返回文件描述符。fopen是庫函數(shù),返回指針。 mapped[43].integer = 243;sprintf(mapped[43].string, "[RECORD-%d]", mapped[43].integer);msync((void *) mapped, NRECORDS * sizeof(record), MS_ASYNC);munmap((void *)mapped, NRECORDS * sizeof(record));close(f);return 0; }?
mmap 和常規(guī)文件操作的區(qū)別
使用系統(tǒng)調(diào)用,函數(shù)的調(diào)用過程:
1. 進(jìn)程發(fā)起讀文件請求。
2. 內(nèi)核通過查找進(jìn)程文件描述符表,定位到內(nèi)核已打開文件集上的文件信息,從而找到此文件的inode。
3. inode在address_space上查找要請求的文件頁是否已經(jīng)緩存在頁緩存中。如果存在,則直接返回這片文件頁的內(nèi)容。
4. 如果不存在,則通過inode定位到文件磁盤地址,將數(shù)據(jù)從磁盤復(fù)制到頁緩存。之后再次發(fā)起讀頁面過程,進(jìn)而將頁緩存中的數(shù)據(jù)發(fā)給用戶進(jìn)程。
總結(jié)來說,常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤,使用了頁緩存機(jī)制。這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由于頁緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內(nèi)存對應(yīng)的用戶空間中。這樣,通過了兩次數(shù)據(jù)拷貝過程,才能完成進(jìn)程對文件內(nèi)容的獲取任務(wù)。寫操作也是一樣,待寫入的buffer在內(nèi)核空間不能直接訪問,必須要先拷貝至內(nèi)核空間對應(yīng)的主存,再寫回磁盤中(延遲寫回),也是需要兩次數(shù)據(jù)拷貝。
而使用mmap操作文件中,創(chuàng)建新的虛擬內(nèi)存區(qū)域和建立文件磁盤地址和虛擬內(nèi)存區(qū)域映射這兩步,沒有任何文件拷貝操作。而之后訪問數(shù)據(jù)時發(fā)現(xiàn)內(nèi)存中并無數(shù)據(jù)而發(fā)起的缺頁異常過程,可以通過已經(jīng)建立好的映射關(guān)系,只使用一次數(shù)據(jù)拷貝,就從磁盤中將數(shù)據(jù)傳入內(nèi)存的用戶空間中,供進(jìn)程使用。
總而言之,常規(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)共有一下幾點(diǎn):
1.?對文件的讀取操作跨過了頁緩存,減少了數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代I/O讀寫,提高了文件讀取效率。
2. 實(shí)現(xiàn)了用戶空間和內(nèi)核空間的高效交互方式。兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi),從而被對方空間及時捕捉。
3. 提供進(jìn)程間共享內(nèi)存及相互通信的方式。不管是父子進(jìn)程還是無親緣關(guān)系的進(jìn)程,都可以將自身用戶空間映射到同一個文件或匿名映射到同一片區(qū)域。從而通過各自對映射區(qū)域的改動,達(dá)到進(jìn)程間通信和進(jìn)程間共享的目的。
同時,如果進(jìn)程A和進(jìn)程B都映射了區(qū)域C,當(dāng)A第一次讀取C時通過缺頁從磁盤復(fù)制文件頁到內(nèi)存中;但當(dāng)B再讀C的相同頁面時,雖然也會產(chǎn)生缺頁異常,但是不再需要從磁盤中復(fù)制文件過來,而可直接使用已經(jīng)保存在內(nèi)存中的文件數(shù)據(jù)。
4. 可用于實(shí)現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個方面,解決方案往往是借助硬盤空間協(xié)助操作,補(bǔ)充內(nèi)存的不足。但是進(jìn)一步會造成大量的文件I/O操作,極大影響效率。這個問題可以通過mmap映射很好的解決。換句話說,但凡是需要用磁盤空間代替內(nèi)存的時候,mmap都可以發(fā)揮其功效。
?
mmap 使用的細(xì)節(jié)
1.?使用mmap需要注意的一個關(guān)鍵點(diǎn)是,mmap映射區(qū)域大小必須是物理頁大小(page_size)的整倍數(shù)(32位系統(tǒng)中通常是4k字節(jié))。原因是,內(nèi)存的最小粒度是頁,而進(jìn)程虛擬地址空間和內(nèi)存的映射也是以頁為單位。為了匹配內(nèi)存的操作,mmap從磁盤到虛擬地址空間的映射也必須是頁。
2.?內(nèi)核可以跟蹤被內(nèi)存映射的底層對象(文件)的大小,進(jìn)程可以合法的訪問在當(dāng)前文件大小以內(nèi)又在內(nèi)存映射區(qū)以內(nèi)的那些字節(jié)。也就是說,如果文件的大小一直在擴(kuò)張,只要在映射區(qū)域范圍內(nèi)的數(shù)據(jù),進(jìn)程都可以合法得到,這和映射建立時文件的大小無關(guān)。
3.?映射建立之后,即使文件關(guān)閉,映射依然存在。因?yàn)橛成涞氖谴疟P的地址,不是文件本身,和文件句柄無關(guān)。同時可用于進(jìn)程間通信的有效地址空間不完全受限于被映射文件的大小,因?yàn)槭前错撚成洹?/span>
?
原文:https://www.cnblogs.com/huxiao-tee/p/4660352.html
總結(jié)
以上是生活随笔為你收集整理的mmap 内存映射详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决Linux因非正常关机或死机重启后进
- 下一篇: 内存对齐详解