XV6學習(3)Page tables

頁表是操做系統中很是重要的一部分,用於將虛擬地址轉化爲物理地址。虛擬內存是操做系統實現進程隔離的關鍵技術。
在 XV6 中經過 RISC-V 的頁表機構完成了虛擬地址向物理地址的轉換。數組

分頁硬件機構

XV6 運行於 Sv39 RISC-V 上,64 位地址中的低 39 位被使用。RISC-V 的頁表邏輯上是 page table entries (PTEs) 的數組,長度爲 2^27。PTE 包含 44 位物理地址號(PPN)。頁的大小爲 4KB,所以,分頁硬件使用 39 位中的高 27 位查找 PTE,以後轉化爲 56 位的物理地址。
地址轉換app

而實際上,RISC-V 使用的三級頁表,1 級頁表爲 1 頁(4KB),包含 512 個 PTE。27 位頁號中的高 9 位爲一級頁表,中間 9 位爲 2 級頁表,末 9 位爲三級頁表。函數

在 PTE 中,低 8 位爲標誌位,其中 PTE_V 表明地址是否有效,當訪問無效頁面時會觸發page fault;PTE_U表明地址可否在用戶模式被訪問,若是未設置則頁面只能在 supervisor mode 中訪問。
ui

爲了使分頁機構可以正常運行,操做系統必須設置satp寄存器爲1級頁表的物理地址。操作系統

內核地址空間

XV6 每一個進程擁有一個獨立頁表,同時內核也擁有一個頁表用於描述內核地址空間。內核會將自身地址空間直接映射到物理地址上,來方便訪問物理內存和硬件資源。內核地址空間定義在memlayout.h中,以下圖所示:

在QEMU中,0~0x80000000用於映射設備接口,而0x80000000(KERNBASE) ~ 0x86400000(PHYSTOP)爲RAM。指針

有一小部份內核地址空間不是直接映射的,Trampoline 頁面在地址空間最高的位置,隨後是每一個進程對應的內核棧,每一個棧之間都有一個 Guard page ,該頁的 PTE_V 設置爲 0,用於避免緩衝區溢出。code

若是內核棧使用直接映射的方法,那麼 Guard page 相對應的物理內存中將會產生不少空洞,致使內存管理變得困難。對象

Code: 建立地址空間

與地址空間有關的代碼主要在vm.c文件中。pagetable_t表明一級頁表,實際數據類型是一個指針,指向頁表的物理地址。blog

walk函數是最核心的函數,該函數經過頁表pagetable將虛擬地址va轉換爲PTE,若是alloc1就會分配一個新頁面。接口

kvminit分析

kvminit函數用於初始化內核頁表,該函數在內核啓動開啓分頁機制前被調用,所以是直接對物理地址進行操做的。函數首先經過kalloc申請了一個頁面用於保存一級頁表。kalloc函數就簡單地從kmem.freelist中取出一個空閒頁面。

kmem結構體的初始化在kinit中進行,該函數在kvminit以前被調用。該函數首先初始化鎖,以後使用freerange函數將內核以後的所有空閒 RAM 以 4KB 爲一頁加入該鏈表。

void kinit()
{
  initlock(&kmem.lock, "kmem");
  freerange(end, (void*)PHYSTOP);
}

回到kvminit函數,在申請到頁表後,經過調用kvmmap函數,將物理地址中的UART0 CLINT等映射到內核頁表中,完成了內核頁表的初始化。

kvminit函數完成後,main函數緊接着就會調用kvminithart函數。在該函數中,使用MAKE_SATP產生SATP的值,將該值寫入satp寄存器中,以後使用sfence_vma刷新 TLB,完成了虛擬地址轉換的開啓,以後代碼中的地址就所有會經過地址轉換機構進行轉換。

#define SATP_SV39 (8L << 60)
#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12))

而在MAKE_SATP中使用SATP_SV39設置 MODE 域爲 8,即開啓 SV39 地址轉換,以下圖所示。
satp

kvminithart函數執行完成後,就會調用procinit函數,初始化全部進程結構體,對每一個進程結構體申請兩個頁面做爲內核棧,以後將該頁面映射到內核地址空間的高位上。最後再次調用kvminithart函數,刷新 TLB,使硬件知道新 PTEs 的加入,防止使用舊的 TLB 項。

sfence.vma

sfence.vma rs1, rs2指令是一條特權指令,用於通知處理器頁表的修改。rs1指示了頁表哪一個虛址對應的轉換被修改了;rs2給出了被修改頁表的進程的地址空間標識符(ASID)。若是二者都是x0,便會刷新整個 TLB。

sfence.vma 僅影響執行當前指令的 hart 的地址轉換硬件。當 hart 更改了另外一個 hart 正在使用的頁表時,前一個 hart 必須用處理器間中斷來通知後一個 hart,他應該執行 sfence.vma 指令。這個過程一般被稱爲 TLB 擊落。

在 XV6 中,兩個地方使用了sfence.vma指令,一個是上文提到的kvminithart函數,另外一個就是trampoline.S中,當陷入內核以及返回用戶態時會調用。

進程地址空間

每一個進程擁有獨立的地址空間,當進程切換時同時會對頁表進行切換。XV6 進程地址空間從 0 開始到 MAXVA,即 256GB。

當進程申請內存時,內核就會先調用kalloc函數申請物理頁面,以後構造PTE加入進程對應的頁表項中。
進程地址空間
在進程地址空間的最高位置爲 trampoline,全部進程的該頁面映射到同一個物理頁面上。一樣地,在用戶棧的下方也設置了一個 guard page 來防止緩衝區溢出。

Code: sbrk

系統調用char* sbrk(int)用於增長或減小物理內存,當參數爲正數時增長,負數時減小。sbrk實際經過growproc進行,growproc調用uvmallocuvmdealloc完成工做。

進程地址空間是從 0 開始連續向上增加的,所以經過proc.sz獲取已分配字節數,就能夠計算獲得當前已分配空間的頂部地址,以後就能夠獲得對應的頁面地址。

uvmalloc函數先計算須要申請的頁面數,以後在進程地址空間頂部再申請所需的連續的頁面。函數經過kalloc申請物理頁面,以後使用mappages函數映射到進程頁表中。

uvmdealloc函數先計算須要減小的頁面數,以後經過uvmunmap刪除頁面。在uvmunmap函數內部經過walk獲取對應 PTE,將PTE_V設置爲0,最後經過kfree函數將該物理頁面添加到空閒鏈表中。

Code: exec

系統調用exec用於建立進程地址空間。函數首先使用namei獲取可執行文件,讀取 ELF 頭,檢查 ELF 中的 magic。以後使用proc_pagetable建立進程頁表。

proc_pagetable函數中,先使用uvmcreate函數申請一個頁面,以後將 trampoline 和 trapframe 映射到高位地址空間中。

exec以後使用uvmalloc申請內存空間,再使用loadseg函數將程序加載到對應頁面中。在 Program Header 中描述了各段的 filesz,memsz等信息,當 filesz 小於 memsz 時,中間的空隙用 0 填充(如C語言中的全局變量)。

程序加載完成後,再申請兩塊頁面,第一塊爲 guard page ,使用uvmclear函數將該頁面PTE_U設置爲0,即不容許 user mode 訪問。第二頁設置爲進程的棧。而後將argcargv和返回地址壓棧,完成棧的準備工做。

最後,exec函數更新進程結構體,將舊頁表釋放。

在 Program Header 的vaddr中,程序能夠指定被加載到的虛擬地址,而這多是危險的,所以在exec中會檢查if(ph.vaddr + ph.memsz < ph.vaddr),避免發生加法溢出。

實際操做系統

在 XV6 中,內核直接加載到 0x80000000 的位置上,而在實際操做系統中,通常會使用 kaslr 技術,即內核地址隨機化,使攻擊者不能直接經過反彙編獲取內核變量和函數的地址。

在 RISC-V 中支持 super pages,即大小爲4MB的頁面,用於下降大內存機器上的頁表開銷。

XV6 也缺乏相似於 malloc 的機制來減小使用sbrk大量分配小對象的開銷。

相關文章
相關標籤/搜索