内存映射mmap
先看下白話MMAP:https://www.cnblogs.com/liqiangchn/p/13587437.html
本文鏈接:找不到了
內(nèi)存映射是linux中的一個重要機制,它和虛擬內(nèi)存管理和文件IO都有直接的關(guān)系,本篇將詳細(xì)介紹linux中內(nèi)存映射的原理。
mmap基本概念
? ? ? ? 在介紹內(nèi)存映射之前,首先知道現(xiàn)代計算機系統(tǒng)普遍采用虛擬內(nèi)存的方式管理物理內(nèi)存。在32位機器上每個進(jìn)程都有自己的4G虛擬內(nèi)存空間,其中0-3G屬于用戶空間,是該進(jìn)程獨有的;3-4G之間的是內(nèi)核空間,是計算機中的所有進(jìn)程的內(nèi)核空間和內(nèi)核進(jìn)程所共享的地址空間。必須明確的一點是:用戶空間和內(nèi)核空間屬于虛擬地址空間,是虛擬內(nèi)存中的概念。進(jìn)程地址空間的劃分如圖1所示。
圖 1 進(jìn)程地址空間
? ? ? ? mmap將就是圖1的進(jìn)程空間中用戶空間的內(nèi)存映射區(qū)域和磁盤上的某個文件或者其他對象形成一一對應(yīng)關(guān)系,形成這樣的關(guān)系之后,進(jìn)程就可以采用對虛擬地址(指針)讀寫的方式實現(xiàn)對內(nèi)存映射區(qū)對應(yīng)的物理內(nèi)存的讀寫,而這種讀寫會被系統(tǒng)自動通過后臺線程(flusher)刷到后備存儲空間、或者通過msync調(diào)用刷到對應(yīng)的后備存儲空間,從而不用調(diào)用read(),write()等系統(tǒng)調(diào)用實現(xiàn)對文件的讀寫。如果不同的進(jìn)程映射同一個文件的同樣區(qū)間或者映射同一個匿名對象到各自的虛擬內(nèi)存空間,那么可以實現(xiàn)進(jìn)程之間的通信,這是一種十分高效的IPC方式。
? ? ? ? 從圖1中可以看出進(jìn)程虛擬地址空間中,用戶空間被劃分成很多的段,包含代碼段、數(shù)據(jù)段、未初始化數(shù)據(jù)段、堆、內(nèi)存映射區(qū)還有棧等區(qū)間,每一個區(qū)間都對應(yīng)一個或者多個管理結(jié)構(gòu)體,在內(nèi)核中用vm_area_struct表示。那么進(jìn)程的用戶空間可以表示為vm_area_struct結(jié)構(gòu)體組成的鏈表,如圖2所示。
圖 2 進(jìn)程地址空間中不同區(qū)段在內(nèi)核中的表示
mmap的基本原理
? ? ? ? mmap內(nèi)存映射的實現(xiàn)過程,可以分為三個階段:
一、進(jìn)程在用戶空間調(diào)用mmap啟動映射過程,內(nèi)核在該進(jìn)程的虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域結(jié)構(gòu)體---vm_area_struct
? ? ? ? 1.進(jìn)程在用戶空間調(diào)用mmap,函數(shù)原型為
? ? ? ? void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
? ? ? ? 關(guān)于該系統(tǒng)調(diào)用的中多解釋可以參考《unix環(huán)境高級編程》中14.8節(jié);
? ? ? ? 2.如果start為NULL(一般的使用情況,增強可移植性),則由內(nèi)核在當(dāng)前進(jìn)程的虛擬地址空間中,尋找一段能夠滿足映射長度需求的連續(xù)虛擬地址區(qū)間;
? ? ? ? 3.為尋找到的連續(xù)虛擬地址區(qū)間分配一個vm_area_struct結(jié)構(gòu)體,接著對這個結(jié)構(gòu)體中的各個成員變量進(jìn)行初始化;
? ? ? ? 4.將新建的虛擬區(qū)間結(jié)構(gòu)體插入到當(dāng)前進(jìn)程的vm_area_struct結(jié)構(gòu)體鏈表和相應(yīng)的紅黑樹結(jié)構(gòu)中;
二、調(diào)用內(nèi)核空間的系統(tǒng)函數(shù)mmap(與為用戶空間提供的系統(tǒng)調(diào)用不是一回事),實現(xiàn)文件磁盤地址和進(jìn)程虛擬地址的一一映射關(guān)系
????????1.為映射分配了新的虛擬地址區(qū)域之后,通過待映射的文件描述符在“進(jìn)程文件描述符表”的struct file* fd_array中找到對應(yīng)的指向文件的struct file*,每個進(jìn)程都為該進(jìn)程打開的所有文件保存著一個struct file*數(shù)組,其中的每個元素都是指向“系統(tǒng)文件描述符”的中的struct file的。系統(tǒng)為每個被打開的文件都維護(hù)一個struct file,struct file中存在指向已打開文件的struct path成員,struct path成員中存在指向系統(tǒng)打開的特定文件的struct dentry*,struct dentry存在指向該能夠唯一標(biāo)識該文件的struct inode結(jié)構(gòu)體(這種關(guān)系可以參考博客),簡化圖如圖3。
圖 3 進(jìn)程地址空間、頁緩存和文件系統(tǒng)關(guān)系圖
?
? ? ? ? 2.通過該文件最終找到的struct inode結(jié)構(gòu)體中的struct file_operations* i_fop模塊,調(diào)用內(nèi)核函數(shù)的mmap(也就是驅(qū)動程序,該模塊也是虛擬文件系統(tǒng)和實際文件系統(tǒng)連接的橋梁),其原型為int mmap(struct file* filp, struct vm_area_struct* vma),通過該內(nèi)核函數(shù)就知道這是將虛擬地址區(qū)間和實際的后備文件系統(tǒng)相關(guān)聯(lián)。
? ? ? ? 3.通過1中所述,由struct file能夠找到系統(tǒng)打開的文件所對應(yīng)的inode結(jié)構(gòu)體,并通過inode結(jié)構(gòu)體中的設(shè)備號和塊號最終定位到文件在磁盤的物理地址。
? ? ? ? 4.通過remap_pfn_rang函數(shù)建立頁表,實現(xiàn)文件在磁盤中的地址和虛擬地址區(qū)域的映射關(guān)系。此時這片虛擬地址并沒有任何數(shù)據(jù)關(guān)聯(lián)到主存中,只是建立了虛擬地址和磁盤地址之間的映射關(guān)系。
三、進(jìn)程訪問分配的虛擬的地址區(qū)間中的某個地址,引發(fā)缺頁異常,實現(xiàn)文件內(nèi)容到物理內(nèi)存的拷貝
? ? ? ? 1.進(jìn)程的讀或者寫操作訪問虛擬地址區(qū)間中的一個或者一段映射地址,通過查詢頁表,發(fā)現(xiàn)這一段地址并不在主存上存在對應(yīng)的物理頁面。因為目前只是建立了地址映射,真正的硬盤數(shù)據(jù)還沒有拷貝到物理內(nèi)存中,由此引發(fā)缺頁異常。
? ? ? ? 2.缺頁異常通過一系列判斷,確定操作合法后,通過DMA的方式讀取數(shù)據(jù)。
? ? ? ? 3.調(diào)頁過程先在交換緩存空間(swap cache)中尋找需要訪問的內(nèi)存頁,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到物理內(nèi)存中。這個過程還涉及分配物理頁框、確定物理頁框的地址、將讀取的數(shù)據(jù)寫入物理頁框,最終更新頁表中訪問的虛擬地址對應(yīng)的物理頁框的地址。
? ? ? ? 4.之后進(jìn)程從缺頁異常中恢復(fù)即可讀取該內(nèi)容文件對應(yīng)的實際內(nèi)容或者對文件進(jìn)行寫操作。
? ? ? ??mmap調(diào)用需要說明的一點是物理頁框最終是由內(nèi)核以頁緩存的方式進(jìn)行管理的,參見《linux內(nèi)核設(shè)計與實現(xiàn)》中頁緩存一章。因此,如果是對文件進(jìn)行寫操作,那么臟頁面不會立即更新到文件中,而是暫時寫到頁緩存中,由后臺的flusher線程將對文件的更新寫到磁盤中或者是由用戶調(diào)用msync將對應(yīng)的數(shù)據(jù)刷新到磁盤中。
mmap映射的四種類型
? ? ? ? mmap分為有后備文件的映射和匿名映射,而這兩種映射又都有私有映射和共享映射之分,所以mmap一共存在四種類型的映射。
? ? ? ? 1.有后備文件的共享映射。多個進(jìn)程的vm_area_struct指向同一個物理內(nèi)存區(qū)域,一個進(jìn)程對文件內(nèi)容的修改對其他進(jìn)程可見,對文件內(nèi)容的修改最終會被寫到后備文件中。
? ? ? ? 2.有后備文件的私有映射。多個進(jìn)程的vm_area_struct指向同一個物理內(nèi)存區(qū)域,采用寫時拷貝的方式,當(dāng)一個進(jìn)程對文件內(nèi)容做修改,不會被其他的進(jìn)程所看到,另外對文件內(nèi)容的修改也不會被寫到后備文件。當(dāng)內(nèi)存不夠需要執(zhí)行頁回收操作的時候,私有映射的頁被交換到交換區(qū)或者直接寫到磁盤。一般用在加載共享代碼庫。
? ? ? ? 3.匿名文件的共享映射。內(nèi)核創(chuàng)建一個都是0的物理內(nèi)存區(qū)域,然后多個進(jìn)程的vm_area_struct指向這個共享的物理內(nèi)存區(qū)域,對該區(qū)域內(nèi)容的修改對所有的進(jìn)程都是可見的,匿名文件在頁會頁回收的時候被交換到交換區(qū)。
? ? ? ? 4.匿名文件的私有映射。內(nèi)核創(chuàng)建一個初始都是0的物理內(nèi)存區(qū)域,對該區(qū)域的內(nèi)容的修改只對進(jìn)程創(chuàng)建者可見,匿名文件在頁回收的時候被交換到交換區(qū)。malloc()的底層調(diào)用是用了匿名文件的私有映射來分配大塊的內(nèi)存。
mmap的用途
? ? ? ? 內(nèi)存映射的用途很多,如
? ? ? ? 1.后備文件的共享映射可以用作內(nèi)存映射IO來對大文件進(jìn)行操作,比普通IO減少一次內(nèi)存的拷貝工作。需要注意的是內(nèi)存映射IO涉及到內(nèi)核的很多操作,比如vm_area_struct的創(chuàng)建、頁表的修改等等,比普通的IO操作更加復(fù)雜。小文件的讀寫使用普通IO更合適。
? ? ? ? 2.后備文件的私有映射可以用作共享庫二進(jìn)制文件代碼段,數(shù)據(jù)段的加載。
? ? ? ? 3.匿名文件的共享映射可以用作fork時,讓父子進(jìn)程共享匿名映射分配的內(nèi)存。
? ? ? ? 4.匿名文件的私有映射可以用作進(jìn)程的私有內(nèi)存分配。
?
總結(jié)
- 上一篇: B树,B-树和B+树、B*树的区别
- 下一篇: kafka如何彻底删除topic及数据