# 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/)