6.S081-2021-Lab3 Pgtbl學習筆記
Speed up system calls
根據hints檢視kernel/proc.c
中的函式proc_pagetable
// kernel/proc.c // Create a user page table for a given process, // with no user memory, but with trampoline pages. pagetable_t proc_pagetable(struct proc *p) { // map the trampoline code (for system call return) // at the highest user virtual address. // only the supervisor uses it, on the way // to/from user space, so not PTE_U. if(mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0){ uvmfree(pagetable, 0); return 0; } // map the trapframe just below TRAMPOLINE, for trampoline.S. if(mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe), PTE_R | PTE_W) < 0){ uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return 0; } .... }
結合程式碼以及手冊,USYSCALL
頁面的位置在heap
之前,trapfram
之後
only read
對應賦予PTE_R | PTE_U
// kernel/proc.c ... // map the trapframe just below TRAMPOLINE, for trampoline.S. if(mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe), PTE_R | PTE_W) < 0){ uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return 0; } // map the usyscall just below TRAPFRAME if (mappages(pagetable, USYSCALL, PGSIZE, (uint64) (p->usyscall), PTE_R | PTE_U) < 0) { uvmunmap(pagetable, USYSCALL, 1, 0); uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return 0; } return pagetable;
分配和初始化頁面
- Don't forget to allocate and initialize the page in
allocproc()
.
在allocate
函式中參考empty user page的方式依葫蘆畫瓢為usyscall
分配空間
同時要記得將pid
返回到使用者態,這樣才能在使用者態直接使用pid
在kernel/proc.h
的struct proc
中新增 struct usyscall* usyscall
.... // Allocate a USYSCALL page. if((p->usyscall = (struct usyscall *)kalloc()) == 0) { freeproc(p); release(&p->lock); return 0; } p->usyscall->pid = p->pid; // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ freeproc(p); release(&p->lock); return 0; } ...
解除對映的部分有兩個函式要修改
- Make sure to free the page in
freeproc()
.
這裡一定要釋放,不然後面的test過不了
在函式proc_freepagetable
新增程式碼
// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
// free USYSCALL
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmfree(pagetable, sz);
}
在函式freeproc
新增程式碼
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
if (p->usyscall)
kfree((void *) p->usyscall);
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
...
}
Print a page table
在exec.c
檔案的return argc
語句前插入if(p->pid==1) vmprint(p->pagetable)
參考freewalk
void
freewalk(pagetable_t pagetable)
{
// 一張頁表由512個頁表項(PTE)組成
for(int i = 0; i < 512; i++){
// 取出當前頁表項
pte_t pte = pagetable[i];
// 當該條目有效,但卻無法讀、寫、執行的時候
// 說明這是一條指向子頁面的PTE,遞迴遍歷子頁面
if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
// this PTE points to a lower-level page table.
uint64 child = PTE2PA(pte);
freewalk((pagetable_t)child);
pagetable[i] = 0;
} else if(pte & PTE_V){
panic("freewalk: leaf");
}
}
kfree((void*)pagetable);
}
我們的vmprint
基本就是copy freewalk
只不過不需要free頁面
照著格式來
在kernel/vm.c
編寫函式vmprint
,別忘了在kernel/defs.h
中宣告
void
backtrace(pagetable_t pagetable, int level)
{
for(int i = 0; i < 512; i++){
pte_t pte = pagetable[i];
if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) {
uint64 child = PTE2PA(pte);
for (int j=0; j <= level; j++) {
printf("..");
if ((j+1) <= level) {
printf(" ");
}
}
printf("%d: pte %p pa %p\n", i, pte, child);
backtrace((pagetable_t)child, level+1);
}
else if (pte & PTE_V) {
uint64 child = PTE2PA(pte);
printf(".. .. ..%d: pte %p pa %p\n", i, pte, child);
}
}
}
void vmprint(pagetable_t pagetable)
{
printf("page table %p\n", pagetable);
backtrace(pagetable, 0);
}
Detecting which pages have been accessed
這個實驗需要我們判斷頁面是否被訪問過,為此需要新增一個標誌位PTE_A
來標識頁面是否被訪問過
- You'll need to define
PTE_A
, the access bit, inkernel/riscv.h
. Consult the RISC-V manual to determine its value.
根據提示查閱手冊
在kernel/riscv.h
中新增
#define PTE_A (1L << 6) // access bit
- You'll need to parse arguments using
argaddr()
andargint()
- First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit)
根據這兩個提示我們可以猜測出三個引數的型別應該分別為uint64,int,uint64,分別是第一個需要檢查的使用者頁的虛擬地址,要檢查的頁表數,以及輸出結果的使用者態地址(因為核心態跟使用者態的空間是不互通的,所以要藉助 copyout 將 bitmask 傳給使用者態程式)
根據引數可以知道我們是要檢視n個頁的,問題是隻給了第一個使用者頁的虛擬地址,之後的n個頁怎麼檢視。其實只需要每次迴圈加上頁表大小PGSIZE
就能到下一個頁了
同時我們來看一下walk
,它的作用是根據虛擬地址返回頁表中對應的PTE
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");
for(int level = 2; level > 0; level--) {
// 主要注意這裡,請結合下圖理解
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}
#define PGSHIFT 12 // 對應圖中的offset
#define PXSHIFT(level) (PGSHIFT+(9*(level))) //L2 L1 L0 都是9位,+9*level是為了定位到對應的level上
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) //獲得L2或L1,L0的bits
明白了walk
的功能就可以完成這一部分的任務了
- 通過虛擬地址VA確定
PTE
(walk
來完成) - 檢查
PTE
的標誌位PTE_A
是否為1,1表示被訪問過- 記得將
PTE_A
清0,以防影響再次呼叫sys_pgaccess
時影響判斷
- 記得將
- VA+=PGSIZE得到下一個頁面的虛擬地址
#ifdef LAB_PGTBL
uint64
sys_pgaccess(void)
{
uint64 va;
int page_num;
uint64 user_bitmask_addr;
if(argaddr(0, &va) < 0)
return -1;
if(argint(1, &page_num) < 0)
return -1;
if(argaddr(2, &user_bitmask_addr) < 0)
return -1;
// 32是看test得出來的
// It's okay to set an upper limit on the number of pages that can be scanned.
if(page_num < 0 || page_num > 32)
return -1;
uint32 bitmask = 0;
pte_t *pte;
struct proc *p = myproc();
for(int i = 0; i < page_num; i++){
if(va >= MAXVA)
return -1;
// alloc不為0,walk就會為找不到對應pte地址的va申請一個頁
// 顯然不存在,就表示沒有被訪問過
// 我們無需建立頁,這不是當前函式的職責
pte = walk(p->pagetable, va, 0);
if(pte == 0)
return -1;
if(*pte & PTE_A){
bitmask |= (1 << i);
// Be sure to clear PTE_A after checking if it is set.
*pte &= (~PTE_A);
}
va += PGSIZE;
}
if(copyout(p->pagetable, user_bitmask_addr, (char*)&bitmask, sizeof(bitmask)) < 0)
return -1;
return 0;
}
#endif