Linux內(nèi)核反向映射RMAP:加速數(shù)據(jù)訪問的關(guān)鍵技術(shù)
而今天要深入剖析的Linux內(nèi)核反向映射 RMAP(Reverse Mapping),正是一把能大幅加速數(shù)據(jù)訪問的利刃,它在內(nèi)存管理的舞臺上扮演著舉足輕重的角色。從基礎(chǔ)概念到復(fù)雜的實(shí)現(xiàn)機(jī)制,RMAP 背后藏著諸多奧秘,接下來,就讓我們一同踏上探索之旅,揭開它的神秘面紗 。
一、Linux 內(nèi)存管理的 “前世今生”
在計(jì)算機(jī)系統(tǒng)中,內(nèi)存管理一直是操作系統(tǒng)的核心功能之一,對于 Linux 系統(tǒng)而言,其內(nèi)存管理機(jī)制隨著時(shí)間的推移不斷演進(jìn),從早期的簡單形式逐漸發(fā)展為如今復(fù)雜而高效的體系。
早期的計(jì)算機(jī)系統(tǒng)中,內(nèi)存管理非常直接和簡單。程序直接運(yùn)行在物理內(nèi)存上,采用連續(xù)分配的方式,將物理內(nèi)存劃分為不同的區(qū)域,每個(gè)區(qū)域分配給一個(gè)程序使用。這種方式雖然簡單易懂,但存在諸多嚴(yán)重問題,比如進(jìn)程地址空間無法隔離,一個(gè)進(jì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ù)雜性。
為了解決這些問題,虛擬內(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)存通過分頁和分段技術(shù)實(shí)現(xiàn)虛擬地址到物理地址的映射。分段技術(shù)將程序地址空間劃分為不同邏輯段,如代碼段、數(shù)據(jù)段、棧段等,每個(gè)段在物理內(nèi)存中可以不連續(xù)存儲,解決程序運(yùn)行地址不確定問題,但仍存在外部碎片問題。分頁技術(shù)則把虛擬內(nèi)存和物理內(nèi)存劃分為固定大小的頁,以頁為單位進(jìn)行映射和管理,大大提高內(nèi)存利用率,減少內(nèi)存碎片。
隨著 Linux 系統(tǒng)應(yīng)用場景不斷擴(kuò)展和硬件技術(shù)發(fā)展,內(nèi)存管理面臨新挑戰(zhàn)。特別是在多進(jìn)程、多線程環(huán)境下,如何快速準(zhǔn)確地確定物理頁面與虛擬頁面之間的映射關(guān)系,成為提高內(nèi)存管理效率的關(guān)鍵。在早期 Linux 內(nèi)核版本中(如 2.4 內(nèi)核),當(dāng)需要確定某物理頁面是否被某個(gè)進(jìn)程映射時(shí),必須遍歷每個(gè)進(jìn)程的頁表,這一過程工作量巨大,效率極低。比如在一個(gè)擁有大量進(jìn)程的服務(wù)器系統(tǒng)中,若要查找某個(gè)物理頁面的映射關(guān)系,遍歷所有進(jìn)程頁表可能需要耗費(fèi)大量 CPU 時(shí)間,嚴(yán)重影響系統(tǒng)性能。
為應(yīng)對這一挑戰(zhàn),在 Linux 2.5 內(nèi)核開發(fā)期間,反向映射(Reverse Mapping,RMAP)的概念被提出并逐步完善。RMAP 的出現(xiàn),徹底改變 Linux 內(nèi)存管理中查找頁面映射關(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īng)的虛擬地址映射關(guān)系的關(guān)鍵機(jī)制 ,與傳統(tǒng)的從虛擬地址到物理地址的正向映射方向相反,RMAP 建立了從物理頁面到虛擬地址空間的反向映射關(guān)系。在實(shí)際運(yùn)行中,當(dāng)系統(tǒng)需要對某個(gè)物理頁面進(jìn)行操作時(shí),如回收、遷移或共享,RMAP 能幫助內(nèi)核迅速定位到所有映射到該物理頁面的虛擬地址,極大提高操作效率。
在頁面回收場景中,當(dāng)系統(tǒng)內(nèi)存不足時(shí),需要將一些長時(shí)間未使用的頁面從物理內(nèi)存中回收,釋放出空間供其他更急需內(nèi)存的進(jìn)程使用。在沒有 RMAP 機(jī)制之前,要回收一個(gè)物理頁面,內(nèi)核必須遍歷系統(tǒng)中每個(gè)進(jìn)程的頁表,檢查該物理頁面是否被某個(gè)進(jìn)程映射,這一過程效率極低,因?yàn)楝F(xiàn)代計(jì)算機(jī)系統(tǒng)中往往運(yùn)行著大量進(jìn)程,每個(gè)進(jìn)程又有龐大的頁表,遍歷所有進(jìn)程頁表的時(shí)間開銷巨大。而有了 RMAP 后,內(nèi)核可以直接通過物理頁面的相關(guān)數(shù)據(jù)結(jié)構(gòu),快速找到所有映射該頁面的虛擬地址,然后斷開這些映射關(guān)系,將頁面回收,大大提高內(nèi)存回收效率。
在頁面遷移場景中,為了實(shí)現(xiàn)內(nèi)存的高效利用和負(fù)載均衡,有時(shí)需要將一個(gè)物理頁面從一個(gè)內(nèi)存區(qū)域遷移到另一個(gè)內(nèi)存區(qū)域,例如在 NUMA(Non - Uniform Memory Access)架構(gòu)的系統(tǒng)中,為了讓進(jìn)程更高效地訪問內(nèi)存,可能需要將進(jìn)程使用的頁面遷移到距離其 CPU 更近的內(nèi)存節(jié)點(diǎn)上。在遷移之前,同樣需要找到所有映射到該頁面的虛擬地址,以便在遷移完成后更新映射關(guān)系。如果沒有 RMAP,查找這些映射關(guān)系的過程會非常耗時(shí),影響系統(tǒng)性能;而RMAP 的存在使得這一查找過程變得快速而準(zhǔn)確,確保頁面遷移能夠順利進(jìn)行。
RMAP 機(jī)制對于 Linux 系統(tǒng)的內(nèi)存管理效率提升至關(guān)重要,它是解決復(fù)雜內(nèi)存管理問題的核心方案,為系統(tǒng)在各種內(nèi)存操作場景下的高效運(yùn)行提供了有力支持。
2.1正向映射
當(dāng)進(jìn)程分配內(nèi)存并發(fā)生寫操作時(shí),會分配虛擬地址并產(chǎn)生缺頁,進(jìn)而分配物理內(nèi)存并建立虛擬地址到物理地址的映射關(guān)系, 這個(gè)叫正向映射。
圖片
2.2反向映射
反過來, 通過物理頁面找到映射它的所有虛擬頁面叫反向映射(reverse-mapping, RMAP)。
圖片
2.3RMAP的背景
用戶進(jìn)程在使用虛擬內(nèi)存的過程中,從虛擬內(nèi)存頁面映射到物理內(nèi)存頁面時(shí),PTE保留這個(gè)記錄,page數(shù)據(jù)結(jié)構(gòu)中的_mapcount記錄有多少個(gè)用戶PTE映射到物理頁面。用戶PTE是指用戶進(jìn)程地址空間和物理頁面建立映射的PTE,不包括內(nèi)核地址空間映射物理頁面時(shí)產(chǎn)生的PTE。有的頁面需要遷移,有的頁面長時(shí)間不使用,需要交換到磁盤。在交換之前,必須找出哪些進(jìn)程使用這個(gè)頁面,然后解除這些映射的用戶PTE。一個(gè)物理頁面可以同時(shí)被多個(gè)進(jìn)程的虛擬內(nèi)存映射,但是一個(gè)虛擬頁面同時(shí)只能映射到一個(gè)物理頁面。
在Linux 2.4內(nèi)核中,為了確定某一個(gè)頁面是否被某個(gè)進(jìn)程映射,必須遍歷每個(gè)進(jìn)程的頁表,因此工作量相當(dāng)大,效率很低。在Linux2.5內(nèi)核開發(fā)期間,提出了反向映射(Reverse Mapping,RMAP)的概念。
三、RMAP 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)剖析
RMAP的主要目的是從物理頁面的page數(shù)據(jù)結(jié)構(gòu)中找到有哪些映射的用戶PTE,這樣頁面回收模塊就可以很快速和高效地把這個(gè)物理頁面映射的所有用戶PTE都解除并回收這個(gè)頁面。在深入了解 RMAP 的工作原理之前,我們先來剖析一下支撐 RMAP 機(jī)制運(yùn)行的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),它們是 RMAP 實(shí)現(xiàn)高效反向映射的基石,理解這些數(shù)據(jù)結(jié)構(gòu)的構(gòu)成和相互關(guān)系,對于掌握 RMAP 的核心思想至關(guān)重要。
為了達(dá)到這個(gè)目的,內(nèi)核在頁面創(chuàng)建時(shí)需要建立RMAP的“鉤子”,即建立相關(guān)的數(shù)據(jù)結(jié)構(gòu),RMAP系統(tǒng)中有兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu):一個(gè)是anon_vma,簡稱AV;另一個(gè)是anon_vma_chain,簡稱AVC。
3.1struct anon_vma(AV)
struct anon_vma 是 RMAP 機(jī)制中用于管理匿名內(nèi)存映射區(qū)域的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),在連接物理頁面的 page 數(shù)據(jù)結(jié)構(gòu)和虛擬內(nèi)存區(qū)域的 vm_area_struct(VMA) 中扮演著核心角色 。當(dāng)進(jìn)程創(chuàng)建匿名內(nèi)存映射(比如通過 malloc 分配內(nèi)存,在缺頁異常時(shí)創(chuàng)建的匿名頁面)時(shí),anon_vma 就開始發(fā)揮作用。
從定義來看,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 會增加;反之,當(dāng) VMA 與 anon_vma 解除關(guān)聯(lián)時(shí),refcount 減少。當(dāng) refcount 變?yōu)?0 時(shí),說明沒有 VMA 再引用這個(gè) anon_vma,此時(shí)就可以對 anon_vma 進(jìn)行回收。
rb_root 字段是一個(gè)紅黑樹的根節(jié)點(diǎn),通過這個(gè)紅黑樹,anon_vma 可以高效地管理和查找與它相關(guān)的 VMA。在實(shí)際場景中,當(dāng)系統(tǒng)需要查找所有引用某個(gè)匿名頁面的 VMA 時(shí),就可以通過 page 結(jié)構(gòu)中的 mapping 指針找到對應(yīng)的 anon_vma,然后遍歷 anon_vma 的紅黑樹,快速獲取所有相關(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 找到對應(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的紅黑樹中
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)程會復(fù)制父進(jìn)程的地址空間和頁表。此時(shí),anon_vma_chain 就會發(fā)揮作用,它會在子進(jìn)程的 VMA 和父進(jìn)程的 anon_vma 之間建立連接。具體來說,子進(jìn)程的每個(gè) VMA 都會創(chuàng)建一個(gè) anon_vma_chain 節(jié)點(diǎn),該節(jié)點(diǎn)的 vma 指針指向子進(jìn)程的 VMA,anon_vma 指針指向父進(jìn)程的 anon_vma,然后通過 same_vma 鏈表和 rb 紅黑樹節(jié)點(diǎn),將這個(gè) anon_vma_chain 節(jié)點(diǎn)插入到相應(yīng)的鏈表和紅黑樹中,從而實(shí)現(xiàn)子進(jìn)程 VMA 與父進(jìn)程 anon_vma 的關(guān)聯(lián) 。這樣,當(dāng)系統(tǒng)需要對某個(gè)匿名頁面進(jìn)行操作時(shí),就可以通過 anon_vma_chain 快速找到所有與該頁面相關(guān)的 VMA,無論是在父進(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é)束地址、訪問權(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; // 頁面保護(hù)標(biāo)志
unsigned long vm_flags; // 虛擬內(nèi)存區(qū)域的標(biāo)志
struct list_head anon_vma_chain; // 用于鏈接anon_vma_chain節(jié)點(diǎn),通過這個(gè)鏈表可以找到與該VMA相關(guān)的所有anon_vma_chain
struct anon_vma *anon_vma; // 指向該VMA對應(yīng)的anon_vma
// 其他字段...
};
在進(jìn)程運(yùn)行過程中,當(dāng)發(fā)生缺頁異常需要?jiǎng)?chuàng)建新的匿名頁面時(shí),內(nèi)核會為該頁面所在的 VMA 分配一個(gè) anon_vma,并通過 anon_vma_chain 將 VMA 和 anon_vma 連接起來。例如,當(dāng)一個(gè)進(jìn)程調(diào)用 malloc 分配內(nèi)存時(shí),會在進(jìn)程的地址空間中創(chuàng)建一個(gè)新的 VMA 來管理這塊內(nèi)存,同時(shí)為這個(gè) VMA 關(guān)聯(lián)一個(gè) anon_vma,并通過 anon_vma_chain 建立兩者之間的聯(lián)系。這樣,在后續(xù)的內(nèi)存操作中,如頁面回收、遷移等,就可以通過 VMA 的這些字段快速找到相關(guān)的 anon_vma 和其他關(guān)聯(lián)信息,從而高效地完成內(nèi)存管理任務(wù)。
四、RMAP的工作流程與原理
4.1匿名頁面的創(chuàng)建與 RMAP 初始化
在進(jìn)程運(yùn)行過程中,當(dāng)訪問的虛擬地址尚未映射到物理頁面時(shí),會觸發(fā)缺頁異常。此時(shí),內(nèi)核會調(diào)用 do_anonymous_page 函數(shù)來處理匿名頁面的創(chuàng)建。以 do_anonymous_page 函數(shù)為例,當(dāng)進(jìn)程調(diào)用 malloc 分配內(nèi)存時(shí),如果相應(yīng)的虛擬地址尚未映射物理頁面,就會觸發(fā)缺頁異常,進(jìn)而調(diào)用 do_anonymous_page 函數(shù)。在這個(gè)函數(shù)中,會先后調(diào)用 anon_vma_prepare 和 page_add_new_anon_rmap 函數(shù)來完成 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;
// 分配物理頁面
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。然后,通過 anon_vma_chain_alloc 分配一個(gè) anon_vma_chain 結(jié)構(gòu),并將其與 VMA 和 anon_vma 進(jìn)行關(guān)聯(lián)。具體來說,它會將 anon_vma_chain 的 vma 指針指向 VMA,anon_vma 指針指向分配的 anon_vma,然后將 anon_vma_chain 添加到 VMA 的 anon_vma_chain 鏈表和 anon_vma 的紅黑樹中,這樣就建立了 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ù)則將新分配的物理頁面與 VMA 建立反向映射關(guān)系。它會設(shè)置頁面的 mapping 字段指向 anon_vma,并計(jì)算頁面在 VMA 中的索引值存儲在 index 字段中。具體實(shí)現(xiàn)中,先設(shè)置頁面的 SwapBacked 標(biāo)志,表示該頁面可交換到磁盤。然后根據(jù)頁面是否為復(fù)合頁(如大頁),設(shè)置 mapcount 字段。接著,通過 __page_set_anon_rmap 函數(shù)將 anon_vma 加上 PAGE_MAPPING_ANON 后賦值給頁面的 mapping 字段,并計(jì)算頁面在 VMA 中的線性索引值賦值給 index 字段,從而完成物理頁面與 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)程通過 fork 創(chuàng)建子進(jìn)程時(shí),子進(jìn)程需要復(fù)制父進(jìn)程的地址空間及頁表。在這個(gè)過程中,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)并添加到紅黑樹
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ā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 沒有關(guān)聯(lián)的 anon_vma,則直接返回。否則,嘗試克隆父進(jìn)程的 anon_vma 到子進(jìn)程。如果克隆失敗,則分配一個(gè)新的 anon_vma 給子進(jìn)程,并將其與子進(jìn)程的 VMA 通過 anon_vma_chain 連接起來,同時(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í)際場景中,假設(shè)父進(jìn)程有一個(gè) VMA 用于管理堆內(nèi)存,當(dāng)通過 fork 創(chuàng)建子進(jìn)程時(shí),子進(jìn)程會復(fù)制父進(jìn)程的這個(gè) VMA 結(jié)構(gòu),并通過 anon_vma_fork 函數(shù)建立與父進(jìn)程 anon_vma 的關(guān)聯(lián)。這樣,在后續(xù)內(nèi)存操作中,系統(tǒng)可以通過 RMAP 機(jī)制統(tǒng)一管理父子進(jìn)程中與該 VMA 相關(guān)的匿名頁面,實(shí)現(xiàn)內(nèi)存的高效利用和共享。
4.3頁面回收與遷移時(shí)的 RMAP 應(yīng)用
當(dāng)系統(tǒng)內(nèi)存不足時(shí),kswapd 內(nèi)核線程會啟動頁面回收機(jī)制,查找可以回收的頁面。對于匿名頁面,kswapd 會利用 RMAP 機(jī)制來找到所有映射該頁面的 PTE 并斷開映射。具體來說,kswapd 會調(diào)用 try_to_unmap 函數(shù),該函數(shù)是反向映射的核心函數(shù)之一,它會遍歷頁面的反向映射關(guān)系,找到所有映射該頁面的 VMA,并調(diào)用 try_to_unmap_one 函數(shù)來斷開每個(gè) VMA 中對該頁面的映射。
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;
}
在頁面遷移場景中,當(dāng)需要將一個(gè)物理頁面從一個(gè)內(nèi)存區(qū)域遷移到另一個(gè)內(nèi)存區(qū)域時(shí),同樣需要利用 RMAP 機(jī)制。例如在 NUMA 架構(gòu)系統(tǒng)中,為了優(yōu)化內(nèi)存訪問性能,可能需要將某個(gè)進(jìn)程使用的頁面遷移到距離其 CPU 更近的內(nèi)存節(jié)點(diǎn)上。在遷移過程中,首先通過 RMAP 找到所有映射該頁面的 PTE,然后將這些 PTE 標(biāo)記為無效,并在目標(biāo)內(nèi)存區(qū)域分配新的物理頁面。遷移完成后,再更新 PTE 的映射關(guān)系,指向新的物理頁面 。整個(gè)過程中,RMAP 機(jī)制確保了在頁面遷移前后,所有相關(guān)進(jìn)程的頁表能夠正確更新,保證進(jìn)程對內(nèi)存的正常訪問。
五、反向映射RMAP應(yīng)用
內(nèi)核中通過struct page找到所有映射到這個(gè)頁面的VMA典型場景有:
- kswapd內(nèi)核線程回收頁面需要斷開所有映射了該匿名頁面的用戶PTE頁表項(xiàng)。
- 頁面遷移時(shí),需要斷開所有映射到匿名頁面的用戶PTE頁表項(xiàng)。
try_to_unmap()是反向映射的核心函數(shù),內(nèi)核中其他模塊會調(diào)用此函數(shù)來斷開一個(gè)頁面的所有映射:
/**
* 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ò)過了一個(gè)映射的PTE,需要重來一次。
* SWAP_FAIL - the page is unswappable-----------------------------失敗
* SWAP_MLOCK - page is mlocked.-----------------------------------頁面被鎖住了
*/
int try_to_unmap(struct page *page, enum ttu_flags flags)
{
int ret;
struct rmap_walk_control rwc = {
.rmap_one = try_to_unmap_one,--------------------------------具體斷開某個(gè)VMA上映射的pte
.arg = (void *)flags,
.done = page_not_mapped,-------------------------------------判斷一個(gè)頁面是否斷開成功的條件
.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)核中有三種頁面需要unmap操作,即KSM頁面、匿名頁面、文件映射頁面:
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);
}
面以匿名頁面的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);-----------------------------------獲取頁面page->mapping指向的anon_vma數(shù)據(jù)結(jié)構(gòu),并申請一個(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紅黑樹中的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í)際的斷開用戶PTE頁表項(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()來斷開PTE映射關(guān)系。
六、補(bǔ)充:其他反向映射
6.1KSM反向映射
KSM(kernel shared memory)是一種內(nèi)存共享機(jī)制,啟用 ksm后,ksm守護(hù)進(jìn)程會定期掃描用戶內(nèi)存區(qū)域,并合并具有相同內(nèi)存的匿名物理頁面以減少頁面的冗余。
和“多個(gè)子進(jìn)程”不一樣的是,映射到同一個(gè)物理頁面, ksm 在不同進(jìn)程的虛擬地址空間的虛擬地址肯定是不一樣的,因此就不能使用 page->index 來表示虛擬頁號。
對于ksm 頁面,內(nèi)核維護(hù)了一個(gè)結(jié)構(gòu)體 rmap_item,用它來保存同一物理頁面在不同的虛擬地址空間信息;物理頁的 mapping 指向了一個(gè) struct stable_node 結(jié)構(gòu)體,通過 stable_node->hlist 將 rmap_item 連接起來。
rmap_item中維護(hù)了page對應(yīng)的虛擬地址address及anon_vma結(jié)構(gòu)。(函數(shù)實(shí)現(xiàn):mm\rmap.c:page_referenced -> rmap_walk ->rmap_walk_ksm);
圖片
6.2文件頁的反向映射
文件頁的反向映射是指在操作系統(tǒng)中,將虛擬內(nèi)存地址與物理內(nèi)存地址之間進(jìn)行映射的過程。它允許操作系統(tǒng)跟蹤每個(gè)虛擬頁到物理頁的對應(yīng)關(guān)系。
通常,操作系統(tǒng)使用頁表來管理虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。每個(gè)進(jìn)程都有自己獨(dú)立的頁表,通過查閱頁表,操作系統(tǒng)可以將進(jìn)程的虛擬地址轉(zhuǎn)換為對應(yīng)的物理地址。
反向映射則是根據(jù)給定的物理內(nèi)存地址,找到對應(yīng)的虛擬內(nèi)存地址。這樣可以在需要時(shí)快速找到一個(gè)物理頁面所屬的進(jìn)程和虛擬地址。
實(shí)現(xiàn)反向映射通常需要一些數(shù)據(jù)結(jié)構(gòu)支持,比如逆向頁表或者其他類似結(jié)構(gòu)。這樣,在需要時(shí)就可以從給定的物理地址追溯到相應(yīng)的虛擬地址。
圖片
物理頁的 mapping 會指向文件對應(yīng)的 address_space,address_space 的i_mmap 保存了所有的vma。物理頁的所有 vma,很容易找到,那不同vma的虛擬地址呢?
物理頁的index 表示物理頁在文件內(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)。