# Lab4 Traps: 紀錄 [lab: Traps](https://pdos.csail.mit.edu/6.S081/2022/labs/traps.html) ## 一些跟這個 lab 有關的 register ![Table 4.2]() ![Table 3.7]() ![Figure 4.12]() ## 程式碼解析 #### `yeild()` `yeild()` 讓 `myproc()` 的 state 變成 `RUNNABLE` 並且呼叫 `sched()`,`sched()` 可以讓其他 function 也有機會可以被執行到 `yeild()` 想要做的事情是: "我用 CPU 用夠久了,可以換其他人使用了" 所謂的 "換其他人使用" 也就是呼叫 `sched()`,它會幫我們處理 ```Clike= // Give up the CPU for one scheduling round. void yield(void) { struct proc *p = myproc(); acquire(&p->lock); p->state = RUNNABLE; sched(); release(&p->lock); } ``` #### `sched()` 解析 `sched()` 只會被以下幾個情境被使用到: * `exit()` * `yeild()` * `sleep()` 從這裡我們可以發現,這都是在一些 "該輪到別人使用 CPU 了" 的時間點 所以可以推測 `sched()` 是用來決定下一個使用 CPU 的人是誰 不過讓我比較意外地點是,`scheduler()` 並沒有呼叫到 `sched()`, 那麼 `scheduler()` 到底扮演了什麼樣的角色呢 ```C // Switch to scheduler. Must hold only p->lock // and have changed proc->state. Saves and restores // intena because intena is a property of this // kernel thread, not this CPU. It should // be proc->intena and proc->noff, but that would // break in the few places where a lock is held but // there's no process. void sched(void) { int intena; struct proc *p = myproc(); if(!holding(&p->lock)) // 我應該要 hold 我自己的 lock panic("sched p->lock"); if(mycpu()->noff != 1) // TODO: why noff should be equals to 1 ? panic("sched locks"); if(p->state == RUNNING) // sched() 的 caller 會先把 myproc() 設為 RUNNABLE panic("sched running"); if(intr_get()) // TODO: learn sstatus first panic("sched interruptible"); intena = mycpu()->intena; // why? swtch(&p->context, &mycpu()->context); mycpu()->intena = intena; } ``` 幾個困惑的點 * `swtch()` 實際上是如何運作的 * `mycpu()->context` 裡面到底裝著些什麼東西 * `mycpu()->context` 什麼時候會被初始化 #### `devintr()` 用來確認 interrupt 的種類 回傳值: * 2 -> timer interrupt * 1 -> 其他 device * 0 -> 認不出來 ```Clike= // check if it's an external interrupt or software interrupt, // and handle it. // returns 2 if timer interrupt, // 1 if other device, // 0 if not recognized. int devintr() { uint64 scause = r_scause(); if((scause & 0x8000000000000000L) && (scause & 0xff) == 9){ // this is a supervisor external interrupt, via PLIC. // irq indicates which device interrupted. int irq = plic_claim(); if(irq == UART0_IRQ){ uartintr(); } else if(irq == VIRTIO0_IRQ){ virtio_disk_intr(); } else if(irq){ printf("unexpected interrupt irq=%d\n", irq); } // the PLIC allows each device to raise at most one // interrupt at a time; tell the PLIC the device is // now allowed to interrupt again. if(irq) plic_complete(irq); return 1; } else if(scause == 0x8000000000000001L){ // software interrupt from a machine-mode timer interrupt, // forwarded by timervec in kernelvec.S. if(cpuid() == 0){ clockintr(); } // acknowledge the software interrupt by clearing // the SSIP bit in sip. w_sip(r_sip() & ~2); return 2; } else { return 0; } } ``` * `usertrap()` 跟 `kerneltrap()` 的差別在哪裡? ### Platform-Level Interrupt Controller(PLIC) ## Lab ### Backtrace **print the saved return address in each stack frame.** 題目需求: 1. 在 `kernel/printf.c` 中實作出 `backtrace()` 2. 在 `sys_sleep` 呼叫 `backtrace()` 1. 把 `backtrace()` 的原型加到 `kernel/defs.h` ```Clike= void backtrace(void); ``` ##### xv6 中的 stack: ``` . . +-> . | +-----------------+ | | | return address | | | | previous fp ------+ | | saved registers | | | local variables | | | ... | <-+ | +-----------------+ | | | return address | | +------ previous fp | | | saved registers | | | local variables | | +-> | ... | | | +-----------------+ | | | return address | | | | previous fp ------+ | | saved registers | | | local variables | | | ... | <-+ | +-----------------+ | | | return address | | +------ previous fp | | | saved registers | | | local variables | | $fp --> | ... | | +-----------------+ | | return address | | | previous fp ------+ | saved registers | $sp --> | local variables | +-----------------+ ``` 1. 把以下內容加到 `kernel/riscv.h` ```Clike= static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x) ); return x; } ``` `r_fp()` 可以讓我們拿到 fram pointer `fp` 的值 拿到 `fp` 之後,先 print 出來 `fp` 裡面到底放著什麼東西 這裡要注意一個很重要的特性(some hints 中有說明): kernel stack 只會被塞在一個 page (4096 bytes) 中 我們可以用 ```Clike= void backtrace(void) { uint64 *cur_frame = (uint64 *)r_fp(); printf("%p\n", *(cur_frame - 1)); } ``` ```Clike= void backtrace(void) { void *cur_frame; void *bot; cur_frame = (void *)r_fp(); bot = (void *) PGROUNDUP((uint64)cur_frame); while (cur_frame < bot) { printf("%p\n", *((void **)cur_frame - 1)); cur_frame = *((void **)cur_frame - 2); } } ``` ### Alarm * 這裡的 tick 並不是拿來推動 cpu 的 tick,而應該是拿來當時鐘的 tick ```Clike= // Per-process state struct proc { // ... // these are provided to handle SYS_sigalarm int ticks; // The number of ticks between alarm calls int ticks_since_alarm; // Ticks since alarm void (*handler)(); // Called when alarm }; ``` 在 `trap.c`: `usertrap()` 中實作 ```Clike= void usertrap(void) { // ... // give up the CPU if this is a timer interrupt. if(which_dev == 2) { if (p->ticks_since_alarm++ > p->ticks) { // p->handler(); // TODO: why this error p->trapframe->epc = (uint64) p->handler; } else { yield(); } } usertrapret(); } ``` * 策略: * 手上有的:每一個 tick `if(which_dev == 2)` 都會成立一次 * 題目要的:每一個 tick `if(which_dev == 2)` 都會成立一次 * tick 的處理優先順序高於 `yield()` * 如果 handler 執行了,還要執行 `yield()` 嗎? * 我個人認為,不用,因為執行 handler 就已經跟 `yield()` 有很類似的效果了 * 雖然每一個 process 都有 `ticks`, `ticks_since_alarm`, `handler` 但是這並不代表 每一個 process 都需要進行 alarm 的檢查 * 判斷的標準在於 `tick == 0` 則 alarm disable * 對應到 `sigalarm(0, 0)` 會把 alarm disable #### 為什麼不可以 ```C void usertrap(void) { // ... // give up the CPU if this is a timer interrupt. if(which_dev == 2) { if (p->ticks_since_alarm++ > p->ticks) { // p->handler(); // TODO: why this error p->trapframe->epc = (uint64) p->handler; } else { yield(); } } usertrapret(); } ``` * `p->handler` 使用了是 (pa/va)? * `p->handler()` 實際上會做什麼事情 * `p->trapframe->epc = (uint64) p->handler` 實際上會做什麼事情? * 想要回答上面的問題了話,必須要搞清楚以下的幾個問題 * 在 usertrap() 中,正在使用的 page table 是哪一個 * 使用 kernel page table ,因為在 uservec 之後應該都是 kernel mode * handler 所紀錄的 address 是 va/pa ? 用的是哪一個 page table * 從 `user/alarmtest.c` 當中可以看到 * 在 usertrap 中的這個當下,各個 register 的狀態如何 * 在 `uservec` * `p->handler()` 實際上會做什麼事情 * 使用 gdb trace * `p->trapframe->epc = (uint64) p->handler` 實際上會做什麼事情? * 使用 gdb trace ### `p->trapframe->epc = (uint64) p->handler` 實際上會做什麼事情? * 當 trap 的流程**結束之後** 會繼續根據 `epc` 的內容往下執行 * 太漂亮了!! ### `p->handler()` 實際上會做什麼事情 這個會把 program counter 跑到 handler 的地方, the key point is: is handler a va or pa * should be a va that using page table of user program * in trap.c we are using kernel page table * this is why we can not `p->handler()` ### test1/test2()/test3(): resume interrupted code 現在我缺少了什麼事情? 現在缺少了 `sigreturn()` `handler()` 的最後面需要去呼叫 `sigreturn()` 像這樣: ```Clike= void periodic() { count = count + 1; printf("alarm!\n"); sigreturn(); // like this } ``` 問題: 當 handler 結束之後回到原本的 user program 會出錯 因為這不是正常的使用 call stack,而是在 usertrap() 中**直接**修改 所謂的一個 process 其實也就是由 1. registers 的內容 1. 一個 page table 1. 放在 memory 的 instructions 所組成的 也就是說在 `handler()` 之後我們需要還原以下一些東西: 1. registers 的內容 1. 把 `sapt` 指到屬於那個 user program 的地方 我們該怎麼知道那些 registers 的值被放到哪裡? * trap 發生時,在 `uservec` 中會把這些 register 的值放到 `struct proc` 的 trapfram 中 * 這個 user program 的位置存放在 ### 在正常情況下是如何從 kernel mode 回到 user program 應該是 userret 就會處理好 而現在的問題在於原本的 program counter(`sepc`) 在 usertrapret 被洗掉了 他被洗掉了,我們就完全找不回來的 原本的步驟為: * `uservec` * `userret` 題目的 hint: * 在 sigreturn() 中需要把 user program 的 registers 給復元回去 * trapfram 中有做紀錄 * 問題在於原本的 sepc 被洗掉了 * 就像 lab pagetable 那樣新增一個 struct 去紀錄 alarm 需要紀錄的東西 * 目前應該只需要紀錄一個 program counter 的值就好 * 也可以把**所有的** registers 都重新備一份 * 真的有這個必要嗎? ### test3 failed: register a0 changed 這裡來探討 `a0` 的旅程 0. 在 user program 中 `a0` 有一個值,可能是有用的,也可能是沒有在用的 1. 在 uservec 中 * 把 `a0` 的值存到 `sscratch` 中 * `a0` 用來存放 `TRAPFRAME` * 把 `a0` 的值從 `sscratch` 中拿回來 * 也應該把 `a0` 的值放到 `p->trampoline` 中 2. 接下來在 `sigreturn()` 應該會把 a0 放回去才對 問題在於 `dummy_handler()`: ```C // // dummy alarm handler; after running immediately uninstall // itself and finish signal handling void dummy_handler() { sigalarm(0, 0); sigreturn(); } ``` `sigalarm(0, 0)` 會讓 alarm 關掉,關掉之後,就不會再一次的進入到 `dummy_handler()` 中了 那麼跟 a0 有什麼關係 請注意,test3() 的 `a0` 被改掉了 * 應該是 `dummy_handler()` 有問題 * 也就是說 `sigreturn()` 沒有把 `a0` 搞定 * 也就是說 `sys_sigreturn()` 中 `p->alarmtrame` 中的 `a0` 是錯誤的 * 也就是說 `p->trapframe` 中的 `a0` 是錯誤的 * 我完全不這麼認為 * 在某個地方 a0 變成了 0 ## TODO [] 再看一次影片 [] trapmpoline.S [] using gdb ## 參考資料 [lab: Traps](https://pdos.csail.mit.edu/6.S081/2022/labs/traps.html) [xv6 book](https://pdos.csail.mit.edu/6.S081/2022/xv6/book-riscv-rev3.pdf) [reference page](https://pdos.csail.mit.edu/6.S081/2022/reference.html) [RISC-V privileged instructions](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf) [10.2 自制操作系统: risc-v Supervisor寄存器 sscratch/sepc/scause/stval/senvcfg](https://blog.csdn.net/dai_xiangjun/article/details/124083732) [MIT 6.s081 Xv6 Lab4 Traps 实验记录](https://ttzytt.com/2022/07/xv6_lab4_record/)