章節ch2 Page Table
隨班附讀 M121355 廖哲賢
在作業系統中執行的程式(Process)及資料都會存在memory中,各有各自的地址,而由作業系統中的page table 通過page hardware 連接對應物理地址及虛擬地址。
本章節介紹的概念有
以x86為例 ,不管是user還是kernel指令都是虛擬地址。電腦的RAM,或者物理記憶體,則是用物理地址來作標記的。x86 的Page hardware通過對應機制將虛擬地址和物理地址關聯起來。
閱讀文件後理解的架構及運作方式如下說明
page table entries (PTEs) ,包含了20-bit的物理頁碼physical page number (PPN)和12-bit 的flag(對應的虛擬地址的使用權限),paging hardware 使用前20-bit來找到該虛擬地址在頁表中的索引,然後把前20-bit替換為對應PTE的PPN。
對應地址分成兩步驟
每個Process的user memory 從virtual address的0開始倒定義的KERNBASE,最高允許的記憶體到2GB,在xv6的memlayout.h程式宣告了memory layout的相關參數。程式碼如下
// Memory layout
#define EXTMEM 0x100000 // Start of extended memory
#define PHYSTOP 0xE000000 // Top physical memory
#define DEVSPACE 0xFE000000 // Other devices are at high addresses
// Key addresses for address space layout (see kmap in vm.c for layout)
#define KERNBASE 0x80000000 // First kernel virtual address
#define KERNLINK (KERNBASE+EXTMEM) // Address where kernel is linked
#define V2P(a) (((uint) (a)) - KERNBASE)
#define P2V(a) (((void *) (a)) + KERNBASE)
#define V2P_WO(x) ((x) - KERNBASE) // same as V2P, but without casts
#define P2V_WO(x) ((x) + KERNBASE) // same as P2V, but without casts
當Process向xv6要求更多的Memory時,xv6首先要找到空的物理頁,然後把這些頁對應的PTE加入該進程的頁表中,並讓PTE指向對應的物理頁。XV6設定了PTE中的PTE_U,PTE_W,PTE_P(定義為flag)。大多數process不會用完整個memory; XV6會把沒有被使用的PTE的PTE_P標誌位設為0。不同process的頁表將其user memory對應到不同的物理內存中,因此每個process就擁有了私有的用戶內存。
程式檔案:vm.c
程式流程
資料結構
static struct kmap {
void *virt; //virtual address 開始位置
uint phys_start; //physical address 開始位置
uint phys_end; //physical address 結束位置
int perm; //存放page table的flag
} kmap[] = {
{ (void*)KERNBASE, 0, EXTMEM, PTE_W}, // I/O space
{ (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kern text+rodata
{ (void*)data, V2P(data), PHYSTOP, PTE_W}, // kern data+memory
{ (void*)DEVSPACE, DEVSPACE, 0, PTE_W}, // more devices
};
function名稱:kvmalloc
代碼解釋:
呼叫setupkvm()初始化Page table,大多數的作業都在這個function發生。
switchkvm()來代換page table。
kvmalloc(void)
{
kpgdir = setupkvm();
switchkvm();
}
function名稱:setupkvm()
代碼解釋:
建造kernel所需要的page table。一開始呼叫kalloc()來存放Page Directory(變數pgdir),接下呼叫 mappages() 設定各層kmap值,若 mappages() return -1 就不再配置page table。
pde_t*
setupkvm(void)
{
pde_t *pgdir;
struct kmap *k;
if((pgdir = (pde_t*)kalloc()) == 0)
return 0;
memset(pgdir, 0, PGSIZE);
if (P2V(PHYSTOP) > (void*)DEVSPACE)
panic("PHYSTOP too high");
for(k = kmap; k < &kmap[NELEM(kmap)]; k++)
if(mappages(pgdir, k->virt, k->phys_end - k->phys_start,
(uint)k->phys_start, k->perm) < 0) {
freevm(pgdir);
return 0;
}
return pgdir;
}
function名稱:mappages(pde_t pgdir, void va, uint size, uint pa, int perm)
代碼解釋:
function作用是在page table建立一段virtual addresses到一段 physical addresses的對應,這一段的range是使用變數a及變數last來設定開始即結束區間,使用for迴圈呼叫walkpgdir跑一次迴圈a就加上PGSIZE,當a==last時跳出迴圈,用此來找到該地址對應的PTE地址。然後初始化該PTE以保存對應PPN
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
char *a, *last;
pte_t *pte;
a = (char*)PGROUNDDOWN((uint)va);
last = (char*)PGROUNDDOWN(((uint)va) + size - 1);
for(;;){
if((pte = walkpgdir(pgdir, a, 1)) == 0)
return -1;
if(*pte & PTE_P)
panic("remap");
*pte = pa | perm | PTE_P;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
function名稱:walkpgdir(pde_t pgdir, const void va, int alloc)
代碼解釋:
function是實作virtual addresses尋找PTE,如此章節所講述,使用virtual addresses前10-bit來尋找page directory entry,如果存在,在使用後10-bit來找尋PTE,反之,若不存在及alloc參數未被設置,說明要找的page table尚未分配
static pte_t *
walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
pde_t *pde;
pte_t *pgtab;
pde = &pgdir[PDX(va)];
if(*pde & PTE_P){
pgtab = (pte_t*)P2V(PTE_ADDR(*pde));
} else {
if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
return 0;
// Make sure all those PTE_P bits are zero.
memset(pgtab, 0, PGSIZE);
// The permissions here are overly generous, but they can
// be further restricted by the permissions in the page table
// entries, if necessary.
*pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U;
}
return &pgtab[PTX(va)];
}
在process運行時,kernel需要為page table、process’s user memory、kernel stacks, a pipe buffers分配空閒的Physical memory。
xv6使用從內核結尾到PHYSTOP之間的Physical memory為運行時分配提供memory資源。每次分配,它會將整塊4096-byte的page配出去。
程式檔案:kalloc.c
流程圖
資料結構
struct run {
struct run *next;
};
//kmem是用來存放free list的,並且加入lock來管理
struct {
struct spinlock lock; //放入lock 的struct
int use_lock; //選擇是否要用lock,要的話填1
struct run *freelist; //放入free list
} kmem;
function名稱:kinit1, kinit2
代碼解釋:
定義兩個方法是因為kinit1在前4MB不能使用lock 及 memory。而kinit2允許使用lock,並使得更多的內存可用於分配。可以從程式碼看到kmem.use_lock設0,kinit2使用Lock,因此kmem.use_lock改設為1,兩個代碼都有呼叫function freerange。
void
kinit1(void *vstart, void *vend)
{
initlock(&kmem.lock, "kmem");
kmem.use_lock = 0;
freerange(vstart, vend);
}
void
kinit2(void *vstart, void *vend)
{
freerange(vstart, vend);
kmem.use_lock = 1;
}
function名稱:freerange
代碼解釋:
function 宣告 p 變數利用PGROUNDUP函式來確保分配器只會釋放對齊的Physical Adress,使用for迴圈來將一個個page 當做參數傳入kfree,由kfree分配器來管理。
void
freerange(void *vstart, void *vend)
{
char *p;
p = (char*)PGROUNDUP((uint)vstart);
for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
kfree(p);
}
function名稱:kfree
代碼解釋:
function kinit會使用P2V(PHYSTOP)來將PHYSTOP(一個物理地址)轉成為虛擬地址。首先將memory的每個byte設為1。讓已被釋放memory讀到的不是原數據,接下來kfree把v轉換為一個指向struct run的指針,在r->next中保存原有 free list ,然後將其設置為r。kalloc移除並返回 free list的第一個元素 。
void
kfree(char *v)
{
struct run *r;
if((uint)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
panic("kfree");
// Fill with junk to catch dangling refs.
memset(v, 1, PGSIZE);
if(kmem.use_lock)
acquire(&kmem.lock);
r = (struct run*)v;
r->next = kmem.freelist;
kmem.freelist = r;
if(kmem.use_lock)
release(&kmem.lock);
}