下面是基于 ARM Linux 核心 3.14 版本源碼的虛拟記憶體管理的技術文章,包括部分重要相關代碼的講解。
1. 概述
Linux 核心中的虛拟記憶體管理是 Linux 核心的核心功能之一,負責管理系統中所有程序的虛拟記憶體空間,使每個程序都能夠通路它們自己的位址空間,并保證不會幹擾到其他程序的位址空間。ARM Linux 核心在虛拟記憶體管理方面也有其獨特的實作。
在 ARM Linux 核心中,虛拟記憶體管理主要包括以下幾個方面:
- 虛拟位址空間管理
- 頁表和頁表項
- 記憶體映射和反映射
- 頁面配置設定和釋放
- 緩存和頁面回收
接下來我們将詳細講解這些方面的實作原理,并且貼出部分重要相關代碼來進行講解。
2. 虛拟位址空間管理
在 Linux 核心中,每個程序都有自己的虛拟位址空間,它包含了程序所能通路的全部虛拟位址。ARM Linux 核心中的虛拟位址空間管理主要涉及到以下幾個資料結構:
- mm_struct:每個程序都有一個與之對應的 mm_struct 資料結構,它包含了該程序的位址空間相關的資訊,例如程序的代碼、資料、棧等資訊,以及指向虛拟位址空間中 VMAs(Virtual Memory Areas,虛拟記憶體區域)的指針。
- vm_area_struct:代表了程序中一段連續的虛拟位址空間,它包含了該區域的起始位址、結束位址、通路權限、對應的實體頁等資訊。在 ARM Linux 核心中,一個程序的虛拟位址空間可以由多個不連續的 VMA 組成。
- page_table:頁表,用于将虛拟位址映射到實體位址上。
- pte_t:頁表項,表示一個虛拟位址和對應的實體位址之間的映射關系。
ARM Linux 核心中的虛拟位址空間管理主要由核心的 mm 子系統負責,該子系統包含了許多函數和資料結構,用于實作位址空間管理。例如,mm_struct 資料結構中包含了一個指向 vm_area_struct 資料結構的指針,表示程序的虛拟位址空間中的 VMA。
ARM Linux 核心中的位址空間管理實作主要包括以下幾個步驟:
- 在建立新程序時,核心會建立一個新的 mm_struct 資料結構,并将程序的虛拟位址空間初始化為一個空的 VMA。
- 當程序通路虛拟位址空間中的某個位址時,核心會檢查該位址是否在某個 VMA 的範圍内。如果是,則可以通路對應的實體位址;如果不是,則會産生一個缺頁異常(page fault),由核心負責将缺失的頁面加載到記憶體中。
- 當程序需要更改其虛拟位址空間時,例如調用 mmap() 或 munmap() 函數時,核心會更新對應的 VMA 或者建立新的 VMA,然後重建立立頁表映射關系。
- 當程序終止時,核心會釋放該程序的虛拟位址空間,包括所有 VMA 和頁表。
下面我們來看一下 ARM Linux 核心中的一些關鍵代碼。
在 mm_struct 結構體中,有一個指向虛拟位址空間中 VMA 的指針 vmacache。在核心中,對于經常通路的 VMA,vmacache 可以用于緩存它們的資訊,進而提高通路速度。
struct mm_struct {
...
struct vm_area_struct *mmap; /* list of VMAs */
struct vm_area_struct *mmap_cache; /* last find_vma result */
...
};
在 ARM Linux 核心中,VMA 被組織成一個連結清單,其中每個 VMA 代表程序中一段連續的虛拟位址空間。vm_start 和 vm_end 分别代表了該 VMA 區域的起始位址和結束位址。
struct vm_area_struct {
struct mm_struct *vm_mm; /* The address space we belong to. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address */
...
struct vm_area_struct *vm_next; /* linked list of VM areas per task */
};
當程序通路某個虛拟位址時,核心會先檢查該位址是否在某個 VMA 區域内。如果是,則可以通路對應的實體位址。如果不是,則會産生一個缺頁異常。
static inline int is_vm_area(struct vm_area_struct *vma, unsigned long addr)
{
return (addr >= vma->vm_start) && (addr < vma->vm_end);
}
在 ARM Linux 核心中,頁表的結構如下所示。其中 pgd_t 表示頁全局目錄(Page Global Directory),pgd_t 數組中的每個元素指向一個二級頁表(Page Middle Directory),而 pte_t 表示頁表項(Page Table Entry),pte_t 數組中的每個元素指向一個實體頁面。
typedef struct {
pgd_t pgd[PGD_SIZE];
} pgd_t;
typedef struct {
pmd_t pmd[PMD_SIZE];
} pmd_t;
typedef struct {
pte_t pte[PAGE_SIZE/sizeof(pte_t)];
} pte_t;
在 ARM Linux 核心中,通過修改頁表項中的标志位來控制虛拟位址和實體位址之間的映射關系。例如,可以将某個頁面标記為隻讀、可寫或可執行,也可以标記為不可通路,進而實作對程序記憶體的保護。
下面是 ARM Linux 核心中用于修改頁表項的一些關鍵代碼。
在核心中,每個程序都有自己的頁全局目錄(pgd)。在程序切換時,核心會切換頁全局目錄,以確定程序之間的位址空間隔離。
#define set_pgd(pgdptr, pgdval) set_pud(pgdptr, pgdval)
在 ARM Linux 核心中,通過修改頁表項中的标志位來實作對虛拟位址空間的保護。例如,可以将某個頁面标記為隻讀、可寫或可執行,也可以标記為不可通路,進而實作對程序記憶體的保護。下面是 ARM Linux 核心中用于修改頁表項的一些關鍵代碼。
static inline pte_t *pte_offset_kernel(pmd_t *pmd, unsigned long address)
{
return (pte_t *)pmd_page_vaddr(*pmd) + pte_index(address);
}
static inline void set_pte_atomic(pte_t *ptep, pte_t pte)
{
unsigned long addr = (unsigned long)ptep;
__asm__ __volatile__("str %1, [%0]\n" : : "r" (addr), "r" (pte) : "memory");
}
static inline void pte_clear(struct mm_struct *mm, unsigned long address, pte_t *ptep)
{
set_pte_atomic(ptep, __pte(0));
flush_tlb_kernel_page(address);
}
ARM Linux 核心中還有很多與虛拟記憶體管理相關的代碼,例如對缺頁異常的處理、記憶體回收機制、寫時複制等等。由于篇幅限制,這裡隻是對其中的一些關鍵代碼進行了簡單的介紹。
總的來說,虛拟記憶體管理是作業系統中一個非常重要的部分。在 ARM Linux 核心中,虛拟記憶體管理的實作主要基于位址空間管理、頁表映射和缺頁異常處理等關鍵技術,這些技術互相配合,共同保證了程序的位址空間隔離和記憶體保護等需求。