---
# System prepended metadata

title: 'Lab4 Traps: 紀錄'

---

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