Linux內(nèi)核反向映射RMAP:加速數(shù)據(jù)訪問(wèn)的關(guān)鍵技術(shù)
而今天要深入剖析的Linux內(nèi)核反向映射 RMAP(Reverse Mapping),正是一把能大幅加速數(shù)據(jù)訪問(wèn)的利刃,它在內(nèi)存管理的舞臺(tái)上扮演著舉足輕重的角色。從基礎(chǔ)概念到復(fù)雜的實(shí)現(xiàn)機(jī)制,RMAP 背后藏著諸多奧秘,接下來(lái),就讓我們一同踏上探索之旅,揭開(kāi)它的神秘面紗 。
一、Linux 內(nèi)存管理的 “前世今生”
在計(jì)算機(jī)系統(tǒng)中,內(nèi)存管理一直是操作系統(tǒng)的核心功能之一,對(duì)于 Linux 系統(tǒng)而言,其內(nèi)存管理機(jī)制隨著時(shí)間的推移不斷演進(jìn),從早期的簡(jiǎn)單形式逐漸發(fā)展為如今復(fù)雜而高效的體系。
早期的計(jì)算機(jī)系統(tǒng)中,內(nèi)存管理非常直接和簡(jiǎn)單。程序直接運(yùn)行在物理內(nèi)存上,采用連續(xù)分配的方式,將物理內(nèi)存劃分為不同的區(qū)域,每個(gè)區(qū)域分配給一個(gè)程序使用。這種方式雖然簡(jiǎn)單易懂,但存在諸多嚴(yán)重問(wèn)題,比如進(jìn)程地址空間無(wú)法隔離,一個(gè)進(jìn)程可以隨意訪問(wèn)其他進(jìn)程的內(nèi)存空間,這嚴(yán)重威脅系統(tǒng)安全;內(nèi)存使用效率低下,由于需要連續(xù)的內(nèi)存空間,容易產(chǎn)生內(nèi)存碎片,導(dǎo)致內(nèi)存浪費(fèi);而且程序運(yùn)行地址不確定,每次運(yùn)行都要在內(nèi)存中尋找足夠大的空閑區(qū)域,增加了程序重定位的復(fù)雜性。
為了解決這些問(wèn)題,虛擬內(nèi)存的概念應(yīng)運(yùn)而生。虛擬內(nèi)存作為程序和物理內(nèi)存之間的中間層,為每個(gè)進(jìn)程提供獨(dú)立的地址空間,實(shí)現(xiàn)進(jìn)程地址空間的隔離,增強(qiáng)系統(tǒng)安全性。在 Linux 系統(tǒng)中,虛擬內(nèi)存通過(guò)分頁(yè)和分段技術(shù)實(shí)現(xiàn)虛擬地址到物理地址的映射。分段技術(shù)將程序地址空間劃分為不同邏輯段,如代碼段、數(shù)據(jù)段、棧段等,每個(gè)段在物理內(nèi)存中可以不連續(xù)存儲(chǔ),解決程序運(yùn)行地址不確定問(wèn)題,但仍存在外部碎片問(wèn)題。分頁(yè)技術(shù)則把虛擬內(nèi)存和物理內(nèi)存劃分為固定大小的頁(yè),以頁(yè)為單位進(jìn)行映射和管理,大大提高內(nèi)存利用率,減少內(nèi)存碎片。
隨著 Linux 系統(tǒng)應(yīng)用場(chǎng)景不斷擴(kuò)展和硬件技術(shù)發(fā)展,內(nèi)存管理面臨新挑戰(zhàn)。特別是在多進(jìn)程、多線程環(huán)境下,如何快速準(zhǔn)確地確定物理頁(yè)面與虛擬頁(yè)面之間的映射關(guān)系,成為提高內(nèi)存管理效率的關(guān)鍵。在早期 Linux 內(nèi)核版本中(如 2.4 內(nèi)核),當(dāng)需要確定某物理頁(yè)面是否被某個(gè)進(jìn)程映射時(shí),必須遍歷每個(gè)進(jìn)程的頁(yè)表,這一過(guò)程工作量巨大,效率極低。比如在一個(gè)擁有大量進(jìn)程的服務(wù)器系統(tǒng)中,若要查找某個(gè)物理頁(yè)面的映射關(guān)系,遍歷所有進(jìn)程頁(yè)表可能需要耗費(fèi)大量 CPU 時(shí)間,嚴(yán)重影響系統(tǒng)性能。
為應(yīng)對(duì)這一挑戰(zhàn),在 Linux 2.5 內(nèi)核開(kāi)發(fā)期間,反向映射(Reverse Mapping,RMAP)的概念被提出并逐步完善。RMAP 的出現(xiàn),徹底改變 Linux 內(nèi)存管理中查找頁(yè)面映射關(guān)系的方式,極大提高內(nèi)存管理效率,為 Linux 系統(tǒng)在各種復(fù)雜環(huán)境下穩(wěn)定高效運(yùn)行奠定堅(jiān)實(shí)基礎(chǔ)。
二、RMAP是什么?
RMAP,即反向映射(Reverse Mapping),是 Linux 內(nèi)核中用于解決從物理頁(yè)面快速查找其對(duì)應(yīng)的虛擬地址映射關(guān)系的關(guān)鍵機(jī)制 ,與傳統(tǒng)的從虛擬地址到物理地址的正向映射方向相反,RMAP 建立了從物理頁(yè)面到虛擬地址空間的反向映射關(guān)系。在實(shí)際運(yùn)行中,當(dāng)系統(tǒng)需要對(duì)某個(gè)物理頁(yè)面進(jìn)行操作時(shí),如回收、遷移或共享,RMAP 能幫助內(nèi)核迅速定位到所有映射到該物理頁(yè)面的虛擬地址,極大提高操作效率。
在頁(yè)面回收?qǐng)鼍爸?,?dāng)系統(tǒng)內(nèi)存不足時(shí),需要將一些長(zhǎng)時(shí)間未使用的頁(yè)面從物理內(nèi)存中回收,釋放出空間供其他更急需內(nèi)存的進(jìn)程使用。在沒(méi)有 RMAP 機(jī)制之前,要回收一個(gè)物理頁(yè)面,內(nèi)核必須遍歷系統(tǒng)中每個(gè)進(jìn)程的頁(yè)表,檢查該物理頁(yè)面是否被某個(gè)進(jìn)程映射,這一過(guò)程效率極低,因?yàn)楝F(xiàn)代計(jì)算機(jī)系統(tǒng)中往往運(yùn)行著大量進(jìn)程,每個(gè)進(jìn)程又有龐大的頁(yè)表,遍歷所有進(jìn)程頁(yè)表的時(shí)間開(kāi)銷(xiāo)巨大。而有了 RMAP 后,內(nèi)核可以直接通過(guò)物理頁(yè)面的相關(guān)數(shù)據(jù)結(jié)構(gòu),快速找到所有映射該頁(yè)面的虛擬地址,然后斷開(kāi)這些映射關(guān)系,將頁(yè)面回收,大大提高內(nèi)存回收效率。
在頁(yè)面遷移場(chǎng)景中,為了實(shí)現(xiàn)內(nèi)存的高效利用和負(fù)載均衡,有時(shí)需要將一個(gè)物理頁(yè)面從一個(gè)內(nèi)存區(qū)域遷移到另一個(gè)內(nèi)存區(qū)域,例如在 NUMA(Non - Uniform Memory Access)架構(gòu)的系統(tǒng)中,為了讓進(jìn)程更高效地訪問(wèn)內(nèi)存,可能需要將進(jìn)程使用的頁(yè)面遷移到距離其 CPU 更近的內(nèi)存節(jié)點(diǎn)上。在遷移之前,同樣需要找到所有映射到該頁(yè)面的虛擬地址,以便在遷移完成后更新映射關(guān)系。如果沒(méi)有 RMAP,查找這些映射關(guān)系的過(guò)程會(huì)非常耗時(shí),影響系統(tǒng)性能;而RMAP 的存在使得這一查找過(guò)程變得快速而準(zhǔn)確,確保頁(yè)面遷移能夠順利進(jìn)行。
RMAP 機(jī)制對(duì)于 Linux 系統(tǒng)的內(nèi)存管理效率提升至關(guān)重要,它是解決復(fù)雜內(nèi)存管理問(wèn)題的核心方案,為系統(tǒng)在各種內(nèi)存操作場(chǎng)景下的高效運(yùn)行提供了有力支持。
2.1正向映射
當(dāng)進(jìn)程分配內(nèi)存并發(fā)生寫(xiě)操作時(shí),會(huì)分配虛擬地址并產(chǎn)生缺頁(yè),進(jìn)而分配物理內(nèi)存并建立虛擬地址到物理地址的映射關(guān)系, 這個(gè)叫正向映射。
圖片
2.2反向映射
反過(guò)來(lái), 通過(guò)物理頁(yè)面找到映射它的所有虛擬頁(yè)面叫反向映射(reverse-mapping, RMAP)。
圖片
2.3RMAP的背景
用戶進(jìn)程在使用虛擬內(nèi)存的過(guò)程中,從虛擬內(nèi)存頁(yè)面映射到物理內(nèi)存頁(yè)面時(shí),PTE保留這個(gè)記錄,page數(shù)據(jù)結(jié)構(gòu)中的_mapcount記錄有多少個(gè)用戶PTE映射到物理頁(yè)面。用戶PTE是指用戶進(jìn)程地址空間和物理頁(yè)面建立映射的PTE,不包括內(nèi)核地址空間映射物理頁(yè)面時(shí)產(chǎn)生的PTE。有的頁(yè)面需要遷移,有的頁(yè)面長(zhǎng)時(shí)間不使用,需要交換到磁盤(pán)。在交換之前,必須找出哪些進(jìn)程使用這個(gè)頁(yè)面,然后解除這些映射的用戶PTE。一個(gè)物理頁(yè)面可以同時(shí)被多個(gè)進(jìn)程的虛擬內(nèi)存映射,但是一個(gè)虛擬頁(yè)面同時(shí)只能映射到一個(gè)物理頁(yè)面。
在Linux 2.4內(nèi)核中,為了確定某一個(gè)頁(yè)面是否被某個(gè)進(jìn)程映射,必須遍歷每個(gè)進(jìn)程的頁(yè)表,因此工作量相當(dāng)大,效率很低。在Linux2.5內(nèi)核開(kāi)發(fā)期間,提出了反向映射(Reverse Mapping,RMAP)的概念。
三、RMAP 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)剖析
RMAP的主要目的是從物理頁(yè)面的page數(shù)據(jù)結(jié)構(gòu)中找到有哪些映射的用戶PTE,這樣頁(yè)面回收模塊就可以很快速和高效地把這個(gè)物理頁(yè)面映射的所有用戶PTE都解除并回收這個(gè)頁(yè)面。在深入了解 RMAP 的工作原理之前,我們先來(lái)剖析一下支撐 RMAP 機(jī)制運(yùn)行的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),它們是 RMAP 實(shí)現(xiàn)高效反向映射的基石,理解這些數(shù)據(jù)結(jié)構(gòu)的構(gòu)成和相互關(guān)系,對(duì)于掌握 RMAP 的核心思想至關(guān)重要。
為了達(dá)到這個(gè)目的,內(nèi)核在頁(yè)面創(chuàng)建時(shí)需要建立RMAP的“鉤子”,即建立相關(guān)的數(shù)據(jù)結(jié)構(gòu),RMAP系統(tǒng)中有兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu):一個(gè)是anon_vma,簡(jiǎn)稱(chēng)AV;另一個(gè)是anon_vma_chain,簡(jiǎn)稱(chēng)AVC。
3.1struct anon_vma(AV)
struct anon_vma 是 RMAP 機(jī)制中用于管理匿名內(nèi)存映射區(qū)域的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),在連接物理頁(yè)面的 page 數(shù)據(jù)結(jié)構(gòu)和虛擬內(nèi)存區(qū)域的 vm_area_struct(VMA) 中扮演著核心角色 。當(dāng)進(jìn)程創(chuàng)建匿名內(nèi)存映射(比如通過(guò) malloc 分配內(nèi)存,在缺頁(yè)異常時(shí)創(chuàng)建的匿名頁(yè)面)時(shí),anon_vma 就開(kāi)始發(fā)揮作用。
從定義來(lái)看,struct anon_vma 包含多個(gè)重要字段:
struct anon_vma {
struct anon_vma *root; /* Root of this anon_vma tree */
struct rw_semaphore rwsem; /* W: modification, R: walking the list */
atomic_t refcount; /* 引用計(jì)數(shù),記錄當(dāng)前有多少個(gè)VMA引用了該anon_vma */
unsigned degree; /* Count of child anon_vmas and VMAs which points to this anon_vma */
struct anon_vma *parent; /* Parent of this anon_vma */
struct rb_root_cached rb_root; /* Interval tree of private "related" vmas */
};其中,refcount 字段用于引用計(jì)數(shù),它記錄著當(dāng)前有多少個(gè) VMA 引用了該 anon_vma。當(dāng)一個(gè)新的 VMA 與該 anon_vma 建立關(guān)聯(lián)時(shí),refcount 會(huì)增加;反之,當(dāng) VMA 與 anon_vma 解除關(guān)聯(lián)時(shí),refcount 減少。當(dāng) refcount 變?yōu)?0 時(shí),說(shuō)明沒(méi)有 VMA 再引用這個(gè) anon_vma,此時(shí)就可以對(duì) anon_vma 進(jìn)行回收。
rb_root 字段是一個(gè)紅黑樹(shù)的根節(jié)點(diǎn),通過(guò)這個(gè)紅黑樹(shù),anon_vma 可以高效地管理和查找與它相關(guān)的 VMA。在實(shí)際場(chǎng)景中,當(dāng)系統(tǒng)需要查找所有引用某個(gè)匿名頁(yè)面的 VMA 時(shí),就可以通過(guò) page 結(jié)構(gòu)中的 mapping 指針找到對(duì)應(yīng)的 anon_vma,然后遍歷 anon_vma 的紅黑樹(shù),快速獲取所有相關(guān)的 VMA,大大提高查找效率。
3.2struct anon_vma_chain(AVC)
struct anon_vma_chain 是連接 vm_area_struct(VMA) 和 anon_vma 的橋梁,在 RMAP 機(jī)制中起著不可或缺的樞紐作用。它主要用于維護(hù) VMA 和 anon_vma 之間的關(guān)聯(lián)關(guān)系,使得內(nèi)核能夠方便地從 VMA 找到對(duì)應(yīng)的 anon_vma,反之亦然。
struct anon_vma_chain {
struct vm_area_struct *vma; // 指向?qū)?yīng)的VMA
struct anon_vma *anon_vma; // 指向?qū)?yīng)的anon_vma
struct list_head same_vma; // 用于鏈接與同一VMA相關(guān)的所有anon_vma_chain節(jié)點(diǎn)
struct rb_node rb; // 用于將anon_vma_chain節(jié)點(diǎn)插入到anon_vma的紅黑樹(shù)中
unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
unsigned long cached_vma_start, cached_vma_last;
#endif
};在進(jìn)程創(chuàng)建子進(jìn)程時(shí),子進(jìn)程會(huì)復(fù)制父進(jìn)程的地址空間和頁(yè)表。此時(shí),anon_vma_chain 就會(huì)發(fā)揮作用,它會(huì)在子進(jìn)程的 VMA 和父進(jìn)程的 anon_vma 之間建立連接。具體來(lái)說(shuō),子進(jìn)程的每個(gè) VMA 都會(huì)創(chuàng)建一個(gè) anon_vma_chain 節(jié)點(diǎn),該節(jié)點(diǎn)的 vma 指針指向子進(jìn)程的 VMA,anon_vma 指針指向父進(jìn)程的 anon_vma,然后通過(guò) same_vma 鏈表和 rb 紅黑樹(shù)節(jié)點(diǎn),將這個(gè) anon_vma_chain 節(jié)點(diǎn)插入到相應(yīng)的鏈表和紅黑樹(shù)中,從而實(shí)現(xiàn)子進(jìn)程 VMA 與父進(jìn)程 anon_vma 的關(guān)聯(lián) 。這樣,當(dāng)系統(tǒng)需要對(duì)某個(gè)匿名頁(yè)面進(jìn)行操作時(shí),就可以通過(guò) anon_vma_chain 快速找到所有與該頁(yè)面相關(guān)的 VMA,無(wú)論是在父進(jìn)程還是子進(jìn)程中。
3.3struct vm_area_struct(VMA)
struct vm_area_struct 用于描述進(jìn)程地址空間中的一段虛擬內(nèi)存區(qū)域,是進(jìn)程虛擬內(nèi)存管理的重要數(shù)據(jù)結(jié)構(gòu),在 RMAP 機(jī)制中也有著關(guān)鍵作用,它記錄了虛擬內(nèi)存區(qū)域的起始地址、結(jié)束地址、訪問(wèn)權(quán)限、所屬的內(nèi)存描述符等重要信息,與 RMAP 相關(guān)的字段主要有 anon_vma_chain 和 anon_vma。
struct vm_area_struct {
unsigned long vm_start; // 虛擬內(nèi)存區(qū)域的起始地址
unsigned long vm_end; // 虛擬內(nèi)存區(qū)域的結(jié)束地址
struct mm_struct *vm_mm; // 指向所屬的內(nèi)存描述符
struct vm_area_struct *vm_next; // 指向下一個(gè)虛擬內(nèi)存區(qū)域
pgprot_t vm_page_prot; // 頁(yè)面保護(hù)標(biāo)志
unsigned long vm_flags; // 虛擬內(nèi)存區(qū)域的標(biāo)志
struct list_head anon_vma_chain; // 用于鏈接anon_vma_chain節(jié)點(diǎn),通過(guò)這個(gè)鏈表可以找到與該VMA相關(guān)的所有anon_vma_chain
struct anon_vma *anon_vma; // 指向該VMA對(duì)應(yīng)的anon_vma
// 其他字段...
};在進(jìn)程運(yùn)行過(guò)程中,當(dāng)發(fā)生缺頁(yè)異常需要?jiǎng)?chuàng)建新的匿名頁(yè)面時(shí),內(nèi)核會(huì)為該頁(yè)面所在的 VMA 分配一個(gè) anon_vma,并通過(guò) anon_vma_chain 將 VMA 和 anon_vma 連接起來(lái)。例如,當(dāng)一個(gè)進(jìn)程調(diào)用 malloc 分配內(nèi)存時(shí),會(huì)在進(jìn)程的地址空間中創(chuàng)建一個(gè)新的 VMA 來(lái)管理這塊內(nèi)存,同時(shí)為這個(gè) VMA 關(guān)聯(lián)一個(gè) anon_vma,并通過(guò) anon_vma_chain 建立兩者之間的聯(lián)系。這樣,在后續(xù)的內(nèi)存操作中,如頁(yè)面回收、遷移等,就可以通過(guò) VMA 的這些字段快速找到相關(guān)的 anon_vma 和其他關(guān)聯(lián)信息,從而高效地完成內(nèi)存管理任務(wù)。
四、RMAP的工作流程與原理
4.1匿名頁(yè)面的創(chuàng)建與 RMAP 初始化
在進(jìn)程運(yùn)行過(guò)程中,當(dāng)訪問(wèn)的虛擬地址尚未映射到物理頁(yè)面時(shí),會(huì)觸發(fā)缺頁(yè)異常。此時(shí),內(nèi)核會(huì)調(diào)用 do_anonymous_page 函數(shù)來(lái)處理匿名頁(yè)面的創(chuàng)建。以 do_anonymous_page 函數(shù)為例,當(dāng)進(jìn)程調(diào)用 malloc 分配內(nèi)存時(shí),如果相應(yīng)的虛擬地址尚未映射物理頁(yè)面,就會(huì)觸發(fā)缺頁(yè)異常,進(jìn)而調(diào)用 do_anonymous_page 函數(shù)。在這個(gè)函數(shù)中,會(huì)先后調(diào)用 anon_vma_prepare 和 page_add_new_anon_rmap 函數(shù)來(lái)完成 RMAP 相關(guān)的初始化工作。
static vm_fault_t do_anonymous_page(struct vm_fault *vmf) {
struct vm_area_struct *vma = vmf->vma;
struct page *page;
vm_fault_t ret = 0;
pte_t entry;
// 準(zhǔn)備anon_vma
if (unlikely(anon_vma_prepare(vma)))
goto oom;
// 分配物理頁(yè)面
page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
if (!page)
goto oom;
// 添加新的匿名反向映射
page_add_new_anon_rmap(page, vma, vmf->address, false);
// 其他處理...
return ret;
oom:
return VM_FAULT_OOM;
}anon_vma_prepare 函數(shù)主要負(fù)責(zé)為 VMA 分配和初始化 anon_vma 結(jié)構(gòu),并建立 anon_vma_chain 連接。在這個(gè)函數(shù)中,首先嘗試查找是否存在可合并的 anon_vma,如果不存在則分配一個(gè)新的 anon_vma。然后,通過(guò) anon_vma_chain_alloc 分配一個(gè) anon_vma_chain 結(jié)構(gòu),并將其與 VMA 和 anon_vma 進(jìn)行關(guān)聯(lián)。具體來(lái)說(shuō),它會(huì)將 anon_vma_chain 的 vma 指針指向 VMA,anon_vma 指針指向分配的 anon_vma,然后將 anon_vma_chain 添加到 VMA 的 anon_vma_chain 鏈表和 anon_vma 的紅黑樹(shù)中,這樣就建立了 VMA 和 anon_vma 之間的雙向關(guān)聯(lián)。
int __anon_vma_prepare(struct vm_area_struct *vma) {
struct anon_vma *anon_vma, *allocated;
struct anon_vma_chain *avc;
// 分配anon_vma_chain
avc = anon_vma_chain_alloc(GFP_KERNEL);
anon_vma = find_mergeable_anon_vma(vma);
allocated = NULL;
if (!anon_vma) {
// 分配新的anon_vma
anon_vma = anon_vma_alloc();
allocated = anon_vma;
}
anon_vma_lock_write(anon_vma);
spin_lock(&mm->page_table_lock);
if (likely(!vma->anon_vma)) {
vma->anon_vma = anon_vma;
// 鏈接anon_vma_chain
anon_vma_chain_link(vma, avc, anon_vma);
}
// 解鎖相關(guān)鎖
spin_unlock(&mm->page_table_lock);
anon_vma_unlock_write(anon_vma);
return 0;
}page_add_new_anon_rmap 函數(shù)則將新分配的物理頁(yè)面與 VMA 建立反向映射關(guān)系。它會(huì)設(shè)置頁(yè)面的 mapping 字段指向 anon_vma,并計(jì)算頁(yè)面在 VMA 中的索引值存儲(chǔ)在 index 字段中。具體實(shí)現(xiàn)中,先設(shè)置頁(yè)面的 SwapBacked 標(biāo)志,表示該頁(yè)面可交換到磁盤(pán)。然后根據(jù)頁(yè)面是否為復(fù)合頁(yè)(如大頁(yè)),設(shè)置 mapcount 字段。接著,通過(guò) __page_set_anon_rmap 函數(shù)將 anon_vma 加上 PAGE_MAPPING_ANON 后賦值給頁(yè)面的 mapping 字段,并計(jì)算頁(yè)面在 VMA 中的線性索引值賦值給 index 字段,從而完成物理頁(yè)面與 VMA 的反向映射關(guān)聯(lián) 。
void page_add_new_anon_rmap(struct page *page, struct vm_area_struct *vma, unsigned long address, bool compound) {
int nr = compound? hpage_nr_pages(page) : 1;
VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
__SetPageSwapBacked(page);
if (compound) {
VM_BUG_ON_PAGE(!PageTransHuge(page), page);
atomic_set(compound_mapcount_ptr(page), 0);
__inc_node_page_state(page, NR_ANON_THPS);
} else {
VM_BUG_ON_PAGE(PageTransCompound(page), page);
atomic_set(&page->mapcount, 0);
}
__mod_node_page_state(page_pgdat(page), NR_ANON_MAPPED, nr);
__page_set_anon_rmap(page, vma, address, 1);
}4.2子進(jìn)程創(chuàng)建時(shí)的 RMAP 處理
當(dāng)父進(jìn)程通過(guò) fork 創(chuàng)建子進(jìn)程時(shí),子進(jìn)程需要復(fù)制父進(jìn)程的地址空間及頁(yè)表。在這個(gè)過(guò)程中,dup_mmap 函數(shù)負(fù)責(zé)復(fù)制父進(jìn)程的進(jìn)程地址空間,遍歷父進(jìn)程的 VMA 鏈表,為每個(gè) VMA 創(chuàng)建一個(gè)新的 VMA 數(shù)據(jù)結(jié)構(gòu),并將父進(jìn)程 VMA 中的相關(guān)信息復(fù)制到子進(jìn)程的 VMA 中。
static __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm) {
struct vm_area_struct *mpnt, *tmp;
int retval;
for (mpnt = oldmm->map; mpnt; mpnt = mpnt->vm_next) {
// 創(chuàng)建臨時(shí)VMA數(shù)據(jù)結(jié)構(gòu)tmp,把父進(jìn)程VMA復(fù)制到子進(jìn)程剛創(chuàng)建的tmp中
tmp = vm_area_dup(mpnt);
if (!tmp) {
retval = -ENOMEM;
goto free_vmas;
}
tmp->vm_mm = mm;
// 為子進(jìn)程創(chuàng)建相應(yīng)anon_vma數(shù)據(jù)結(jié)構(gòu)并添加到紅黑樹(shù)
if (anon_vma_fork(tmp, mpnt)) {
__vma_link_rb(mm, tmp, rb_link, rb_parent);
}
// 復(fù)制父進(jìn)程的PTE到子進(jìn)程
if (!(tmp->vm_flags & VM_WIPEONFORK)) {
retval = copy_page_range(tmp, mpnt);
if (retval)
goto unlink_vma;
}
}
return 0;
unlink_vma:
__vma_unlink_rb(mm, tmp);
free_vmas:
// 釋放相關(guān)資源
return retval;
}anon_vma_fork 函數(shù)在子進(jìn)程創(chuàng)建過(guò)程中起著關(guān)鍵作用,負(fù)責(zé)為子進(jìn)程的 VMA 創(chuàng)建相應(yīng)的 anon_vma 數(shù)據(jù)結(jié)構(gòu),并建立子進(jìn)程 VMA 與父進(jìn)程 anon_vma 之間的關(guān)聯(lián)。如果父進(jìn)程的 VMA 沒(méi)有關(guān)聯(lián)的 anon_vma,則直接返回。否則,嘗試克隆父進(jìn)程的 anon_vma 到子進(jìn)程。如果克隆失敗,則分配一個(gè)新的 anon_vma 給子進(jìn)程,并將其與子進(jìn)程的 VMA 通過(guò) anon_vma_chain 連接起來(lái),同時(shí)設(shè)置相關(guān)的父子關(guān)系和引用計(jì)數(shù) 。
int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma) {
struct anon_vma_chain *avc;
struct anon_vma *anon_vma;
int error;
if (!pvma->anon_vma)
return 0;
error = anon_vma_clone(vma, pvma);
if (vma->anon_vma)
return 0;
anon_vma = anon_vma_alloc();
avc = anon_vma_chain_alloc(GFP_KERNEL);
anon_vma->root = pvma->anon_vma->root;
anon_vma->parent = pvma->anon_vma;
get_anon_vma(anon_vma->root);
vma->anon_vma = anon_vma;
// 鏈接anon_vma_chain
anon_vma_chain_link(vma, avc, anon_vma);
return 0;
}在實(shí)際場(chǎng)景中,假設(shè)父進(jìn)程有一個(gè) VMA 用于管理堆內(nèi)存,當(dāng)通過(guò) fork 創(chuàng)建子進(jìn)程時(shí),子進(jìn)程會(huì)復(fù)制父進(jìn)程的這個(gè) VMA 結(jié)構(gòu),并通過(guò) anon_vma_fork 函數(shù)建立與父進(jìn)程 anon_vma 的關(guān)聯(lián)。這樣,在后續(xù)內(nèi)存操作中,系統(tǒng)可以通過(guò) RMAP 機(jī)制統(tǒng)一管理父子進(jìn)程中與該 VMA 相關(guān)的匿名頁(yè)面,實(shí)現(xiàn)內(nèi)存的高效利用和共享。
4.3頁(yè)面回收與遷移時(shí)的 RMAP 應(yīng)用
當(dāng)系統(tǒng)內(nèi)存不足時(shí),kswapd 內(nèi)核線程會(huì)啟動(dòng)頁(yè)面回收機(jī)制,查找可以回收的頁(yè)面。對(duì)于匿名頁(yè)面,kswapd 會(huì)利用 RMAP 機(jī)制來(lái)找到所有映射該頁(yè)面的 PTE 并斷開(kāi)映射。具體來(lái)說(shuō),kswapd 會(huì)調(diào)用 try_to_unmap 函數(shù),該函數(shù)是反向映射的核心函數(shù)之一,它會(huì)遍歷頁(yè)面的反向映射關(guān)系,找到所有映射該頁(yè)面的 VMA,并調(diào)用 try_to_unmap_one 函數(shù)來(lái)斷開(kāi)每個(gè) VMA 中對(duì)該頁(yè)面的映射。
int try_to_unmap(struct page *page, enum ttu_flags flags) {
int ret;
struct rmap_walk_control rwc = {
.rmap_one = try_to_unmap_one,
.arg = (void *)flags,
.done = page_not_mapped,
.anon_lock = page_lock_anon_vma_read,
};
VM_BUG_ON_PAGE(!PageHuge(page) && PageTransHuge(page), page);
if ((flags & TTU_MIGRATION) &&!PageKsm(page) && PageAnon(page))
rwc.invalid_vma = invalid_migration_vma;
ret = rmap_walk(page, &rwc);
if (ret != SWAP_MLOCK &&!page_mapped(page))
ret = SWAP_SUCCESS;
return ret;
}在頁(yè)面遷移場(chǎng)景中,當(dāng)需要將一個(gè)物理頁(yè)面從一個(gè)內(nèi)存區(qū)域遷移到另一個(gè)內(nèi)存區(qū)域時(shí),同樣需要利用 RMAP 機(jī)制。例如在 NUMA 架構(gòu)系統(tǒng)中,為了優(yōu)化內(nèi)存訪問(wèn)性能,可能需要將某個(gè)進(jìn)程使用的頁(yè)面遷移到距離其 CPU 更近的內(nèi)存節(jié)點(diǎn)上。在遷移過(guò)程中,首先通過(guò) RMAP 找到所有映射該頁(yè)面的 PTE,然后將這些 PTE 標(biāo)記為無(wú)效,并在目標(biāo)內(nèi)存區(qū)域分配新的物理頁(yè)面。遷移完成后,再更新 PTE 的映射關(guān)系,指向新的物理頁(yè)面 。整個(gè)過(guò)程中,RMAP 機(jī)制確保了在頁(yè)面遷移前后,所有相關(guān)進(jìn)程的頁(yè)表能夠正確更新,保證進(jìn)程對(duì)內(nèi)存的正常訪問(wèn)。
五、反向映射RMAP應(yīng)用
內(nèi)核中通過(guò)struct page找到所有映射到這個(gè)頁(yè)面的VMA典型場(chǎng)景有:
- kswapd內(nèi)核線程回收頁(yè)面需要斷開(kāi)所有映射了該匿名頁(yè)面的用戶PTE頁(yè)表項(xiàng)。
- 頁(yè)面遷移時(shí),需要斷開(kāi)所有映射到匿名頁(yè)面的用戶PTE頁(yè)表項(xiàng)。
try_to_unmap()是反向映射的核心函數(shù),內(nèi)核中其他模塊會(huì)調(diào)用此函數(shù)來(lái)斷開(kāi)一個(gè)頁(yè)面的所有映射:
/**
* try_to_unmap - try to remove all page table mappings to a page
* @page: the page to get unmapped
* @flags: action and flags
*
* Tries to remove all the page table entries which are mapping this
* page, used in the pageout path. Caller must hold the page lock.
* Return values are:
*
* SWAP_SUCCESS - we succeeded in removing all mappings------------成功解除了所有映射的PTE。
* SWAP_AGAIN - we missed a mapping, try again later---------------可能錯(cuò)過(guò)了一個(gè)映射的PTE,需要重來(lái)一次。
* SWAP_FAIL - the page is unswappable-----------------------------失敗
* SWAP_MLOCK - page is mlocked.-----------------------------------頁(yè)面被鎖住了
*/
int try_to_unmap(struct page *page, enum ttu_flags flags)
{
int ret;
struct rmap_walk_control rwc = {
.rmap_one = try_to_unmap_one,--------------------------------具體斷開(kāi)某個(gè)VMA上映射的pte
.arg = (void *)flags,
.done = page_not_mapped,-------------------------------------判斷一個(gè)頁(yè)面是否斷開(kāi)成功的條件
.anon_lock = page_lock_anon_vma_read,------------------------鎖
};
VM_BUG_ON_PAGE(!PageHuge(page) && PageTransHuge(page), page);
/*
* During exec, a temporary VMA is setup and later moved.
* The VMA is moved under the anon_vma lock but not the
* page tables leading to a race where migration cannot
* find the migration ptes. Rather than increasing the
* locking requirements of exec(), migration skips
* temporary VMAs until after exec() completes.
*/
if ((flags & TTU_MIGRATION) && !PageKsm(page) && PageAnon(page))
rwc.invalid_vma = invalid_migration_vma;
ret = rmap_walk(page, &rwc);
if (ret != SWAP_MLOCK && !page_mapped(page))
ret = SWAP_SUCCESS;
return ret;
}內(nèi)核中有三種頁(yè)面需要unmap操作,即KSM頁(yè)面、匿名頁(yè)面、文件映射頁(yè)面:
int rmap_walk(struct page *page, struct rmap_walk_control *rwc)
{
if (unlikely(PageKsm(page)))
return rmap_walk_ksm(page, rwc);
else if (PageAnon(page))
return rmap_walk_anon(page, rwc);
else
return rmap_walk_file(page, rwc);
}面以匿名頁(yè)面的unmap為例:
static int rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc)
{
struct anon_vma *anon_vma;
pgoff_t pgoff;
struct anon_vma_chain *avc;
int ret = SWAP_AGAIN;
anon_vma = rmap_walk_anon_lock(page, rwc);-----------------------------------獲取頁(yè)面page->mapping指向的anon_vma數(shù)據(jù)結(jié)構(gòu),并申請(qǐng)一個(gè)讀者鎖。
if (!anon_vma)
return ret;
pgoff = page_to_pgoff(page);
anon_vma_interval_tree_foreach(avc, &anon_vma->rb_root, pgoff, pgoff) {------遍歷anon_vma->rb_root紅黑樹(shù)中的AVC,從AVC得到相應(yīng)的VMA。
struct vm_area_struct *vma = avc->vma;
unsigned long address = vma_address(page, vma);
if (rwc->invalid_vma && rwc->invalid_vma(vma, rwc->arg))
continue;
ret = rwc->rmap_one(page, vma, address, rwc->arg);-----------------------實(shí)際的斷開(kāi)用戶PTE頁(yè)表項(xiàng)操作。
if (ret != SWAP_AGAIN)
break;
if (rwc->done && rwc->done(page))
break;
}
anon_vma_unlock_read(anon_vma);
return ret;
}struct rmap_walk_control中的rmap_one實(shí)現(xiàn)是try_to_unmap_one,最終調(diào)用page_remove_rmap()和page_cache_release()來(lái)斷開(kāi)PTE映射關(guān)系。
六、補(bǔ)充:其他反向映射
6.1KSM反向映射
KSM(kernel shared memory)是一種內(nèi)存共享機(jī)制,啟用 ksm后,ksm守護(hù)進(jìn)程會(huì)定期掃描用戶內(nèi)存區(qū)域,并合并具有相同內(nèi)存的匿名物理頁(yè)面以減少頁(yè)面的冗余。
和“多個(gè)子進(jìn)程”不一樣的是,映射到同一個(gè)物理頁(yè)面, ksm 在不同進(jìn)程的虛擬地址空間的虛擬地址肯定是不一樣的,因此就不能使用 page->index 來(lái)表示虛擬頁(yè)號(hào)。
對(duì)于ksm 頁(yè)面,內(nèi)核維護(hù)了一個(gè)結(jié)構(gòu)體 rmap_item,用它來(lái)保存同一物理頁(yè)面在不同的虛擬地址空間信息;物理頁(yè)的 mapping 指向了一個(gè) struct stable_node 結(jié)構(gòu)體,通過(guò) stable_node->hlist 將 rmap_item 連接起來(lái)。
rmap_item中維護(hù)了page對(duì)應(yīng)的虛擬地址address及anon_vma結(jié)構(gòu)。(函數(shù)實(shí)現(xiàn):mm\rmap.c:page_referenced -> rmap_walk ->rmap_walk_ksm);
圖片
6.2文件頁(yè)的反向映射
文件頁(yè)的反向映射是指在操作系統(tǒng)中,將虛擬內(nèi)存地址與物理內(nèi)存地址之間進(jìn)行映射的過(guò)程。它允許操作系統(tǒng)跟蹤每個(gè)虛擬頁(yè)到物理頁(yè)的對(duì)應(yīng)關(guān)系。
通常,操作系統(tǒng)使用頁(yè)表來(lái)管理虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。每個(gè)進(jìn)程都有自己獨(dú)立的頁(yè)表,通過(guò)查閱頁(yè)表,操作系統(tǒng)可以將進(jìn)程的虛擬地址轉(zhuǎn)換為對(duì)應(yīng)的物理地址。
反向映射則是根據(jù)給定的物理內(nèi)存地址,找到對(duì)應(yīng)的虛擬內(nèi)存地址。這樣可以在需要時(shí)快速找到一個(gè)物理頁(yè)面所屬的進(jìn)程和虛擬地址。
實(shí)現(xiàn)反向映射通常需要一些數(shù)據(jù)結(jié)構(gòu)支持,比如逆向頁(yè)表或者其他類(lèi)似結(jié)構(gòu)。這樣,在需要時(shí)就可以從給定的物理地址追溯到相應(yīng)的虛擬地址。
圖片
物理頁(yè)的 mapping 會(huì)指向文件對(duì)應(yīng)的 address_space,address_space 的i_mmap 保存了所有的vma。物理頁(yè)的所有 vma,很容易找到,那不同vma的虛擬地址呢?
物理頁(yè)的index 表示物理頁(yè)在文件內(nèi)的offset(以page size為單位),vma的vm_pgoff 表示 vma 區(qū)域在文件中的偏移量。那么,在不同的vma的虛擬地址= page->index - vma->vm_pgoff + vma->vm_start 。
圖片
知道了 vma和 虛擬地址,就可以解除pte映射了,(函數(shù)實(shí)現(xiàn):mm\rmap.c:page_referenced -> rmap_walk ->rmap_walk_file)。
























