# Lab5 Lazy 紀錄
## 在處理 page fault 時,我們需要注意的幾件事情
* `stval`: 造成 page fault 的 virtual memory
* `sepc`: 造成 page fault 的 program counter
* instruction: 當下是正在執行什麼 instruction (?)
### registers
1. `stval`: va cause page fault
2. `scause`: the type of fault
* Read
* Write
* Instruction
3. `sepc`: the va of instruction
## Eliminate allocation from `sbrk()`
這個 assignment 就只要根據題目的指示,把 `growproc()` 給註解掉,並且記得把 `myproc()->sz` 增加 `n` bytes
```Clike=
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
// if(growproc(n) < 0)
// return -1;
myproc()->sz += n;
return addr;
}
```
改好之後執行 `echo hi` 會爆出以下的錯誤
```=
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ echo hi
usertrap(): unexpected scause 0x000000000000000f pid=3
sepc=0x00000000000012ac stval=0x0000000000004008
panic: uvmunmap: not mapped
```
##### 錯誤訊息解釋
* `usertrap()`:
* 這些錯誤訊息是從 `kernel/trap.c: usertrap()` 印出來的
* `unexpected scause 0x000000000000000f`:
* `usertrpa()` 處理這個 trap 時,`scause` = 0x0f 此情況並沒有對應的程式碼
* `pid = 3` 是 `echo hi` 的 pid
* 註: `sh` 的 pid 是 2
* `sepc=0x00000000000012ac`:
* 對應到 `sh.asm` 中的 `12ac: ld ra,56(sp)`
* 為什麼 `sepc` 停留在 `sh` 中而沒有進入到 `echo` ?
* `stval=0x0000000000004008`:
* 造成 page trap 的 virtual memory (`sh` or `echo`?)
### 刪掉 `growproc()` 之後,會造成什麼樣的影響
* 也就是說 `sys_sbrk()` 沒有執行 `growproc()`
* 也就是沒有執行 `uvmalloc()`
* 也就是沒有執行 `kalloc()`
* 沒有執行 `kalloc()` 就是怎麼樣了(?)
* 最終會導致 user program (sh) 使用了他沒有分配到的記憶體位置(va)
* 我的猜想:
* 在 sh 執行 `sw s6,8(a0)` 時,正要 access 某個 va (`8 + a0`) 的當下
* 硬體發現這個 PTE 的 `valid` flag 為 0 (false)
* 於是發出了 0x0f 這個 trap (page fault)
* 所以以物理上的狀況來說 page fault 就是
> 當我要 access 某一個 va 時,我發現這個 va 在 page table 上給我的回覆為:
> 此 va 並不是使用中的狀態
## Lazy allocation
當 page fault 發生時,allocate 一個新的 page,並且回到 user space 繼續執行,修改任何的 kernel code,讓 `echo hi` 可以執行
### hints
* page fault 時 `scause` 為 13 or 15
* 從 `stval` 可以得知造成 page fault 的 va 是多少
* 請偷學 `uvmalloc()`,並呼叫 `kalloc()` 以及 `mappages()`
* 先從偷學這裡開始
* 當這個 va 沒有被分配時,我就手動幫他分配
* 偷學 `uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)`
* `pagetable`: `myproc()->pagetable`
* `oldsz`: `myproc()->sz`
* `newsz`: `r_stval()`
* `kernel/trap.c`: `usertrap()`
```clike=
void
usertrap(void)
{
// ...
} else if((which_dev = devintr()) != 0){
// ok
} else if(r_scause() == 13 || r_scause() == 15){
uint64 va = r_stval();
lazy_alloc(va);
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
// ...
}
```
* 當 `lazy_alloc()` 還沒有完成實作時,會發生什麼事情?
* `kernel/trap.c`: `lazy_alloc()`
```clike=
int
lazy_alloc(uint64 va)
{
struct proc *p = myproc();
pagetable_t pagetable = p->pagetable;
uint64 new_page_va = PGROUNDDOWN(va);
char *newmem = kalloc();
if (newmem == 0)
return -1;
memset(newmem, 0, PGSIZE);
if (mappages(pagetable, new_page_va, PGSIZE, (uint64) newmem,
PTE_W|PTE_X|PTE_R|PTE_U) != 0) {
kfree(newmem);
return -1;
}
return 0;
}
```
* `kernel/vm.c`:
```Clike=
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
uint64 a;
pte_t *pte;
if((va % PGSIZE) != 0)
panic("uvmunmap: not aligned");
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
// panic("uvmunmap: walk");
continue;
if((*pte & PTE_V) == 0)
// panic("uvmunmap: not mapped");
continue;
if(PTE_FLAGS(*pte) == PTE_V)
panic("uvmunmap: not a leaf");
if(do_free){
uint64 pa = PTE2PA(*pte);
kfree((void*)pa);
}
*pte = 0;
}
}
```
* 之所以要變成 `continue` 是為什麼
* `procgrow()` 與 lazy alloc 的差別
* `growproc()` 會一次 alloc 連續的數個 page
* lazy alloc 只會 allocate **一個** page
* 所以說那些中間的不連續的沒有被 alloc 的 va 就會產生 `PTE_V` = 0 的問題
現在有了最基本的 lazy alloc, 接著來繼續完成
## Lazytests and Usertests
### hints
- [x] Handle negative sbrk() arguments.
- [x] Kill a process if it page-faults on a virtual memory address higher than any allocated with sbrk().
- [x] Handle the parent-to-child memory copy in fork() correctly.
- [ ] Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write, but the memory for that address has not yet been allocated.
- [ ] Handle out-of-memory correctly: if kalloc() fails in the page fault handler, kill the current process.
- [ ] Handle faults on the invalid page below the user stack.
### Handle negative sbrk() arguments.
對於正數,只需要把 `sz` 增加 n 就好
當參數為負數時,則要實際的釋放空間
釋放空間的方法: 就用原本的 `growproc()` 就好
```Clike=
uint64
sys_sbrk(void)
{
int addr;
int n;
struct proc *p = myproc();
addr = myproc()->sz;
if (argint(0, &n) < 0)
return -1;
if (n < 0) {
if (p->sz + n < 0)
return -1;
if (growproc(n) < -1)
return -1;
} else {
myproc()->sz += n;
}
return addr;
}
```
### Kill a process if it page-faults on a virtual memory address higher than any allocated with sbrk().
澄清 lazy alloc 的意涵:
如果用 sbrk 增加記憶體空間,就增加 `sz`
真的到了 page fault 的時候再來處理 allocate 的問題就好
可是有時候遇到 page fault 並不代表這個 va 是允許存在的
以下幾種方情況並不是正常的 va
1. `va` > `MAXVA`
1. 如果一個 page 的 `PTE_V` == 1 那麼他就一定不是 lazy alloc 因為它已經被分配了
1. `va` >= `p->sz` 因為並沒有藉由 `sbrk()` 去新增位置
解決方法:
```clike=
int
is_lazy_addr(int va)
{
struct proc *p = myproc();
if (va > MAXVA)
return 0;
pte_t *pte = walk(p->pagetable, va, 0);
if (pte && (*pte & PTE_V))
return 0;
if (PGROUNDDOWN(va) > PGROUNDDOWN(p->sz))
return 0;
return 1;
}
```
> Handle the parent-to-child memory copy in fork() correctly.
主要在講 `kernel/proc.c: fork()` 中的呼叫的,`kernel/vm.c: uvmcopy`
原本的 `uvmcopy` 是 copy 一段連續的 memory, 可是現在使用 lazy alloc 了話,就不一定是連續的
解決方法:
`continue` 掉兩個 `panic`
```clike=
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
// ...
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
// panic("uvmcopy: pte should exist");
continue;
if((*pte & PTE_V) == 0)
// panic("uvmcopy: page not present");
continue;
// ...
}
return 0;
}
```
### 問題一: `uvmunmap()` `walk()`
```shell=
$ lazytests
lazytests starting
running test lazy alloc
panic: uvmunmap: walk
```
`walk()` 出錯了
這跟前一個問題很像,都是因為 lazy alloc 只有 alloc 一個 page
而不是像原本的 `growproc` 是數個 page 的原因
解決方法:
```Clike=
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
// ...
if((pte = walk(pagetable, a, 0)) == 0)
// panic("uvmunmap: walk");
continue;
// ...
}
```
> Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write, but the memory for that address has not yet been allocated.
`copyin()` 與 `copyout()` 的問題
- [ ] trace code
## reference
* [6.S081 2020](https://pdos.csail.mit.edu/6.S081/2020/schedule.html)
* [Lab lazy](https://pdos.csail.mit.edu/6.S081/2020/labs/lazy.html)
* [xv6 book](https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf)
* [video](https://youtu.be/KSYO-gTZo0A)
* [Lazy Page Allocation 实验记录](https://ttzytt.com/2022/07/xv6_lab5_record/)