XV6學習(6)Lab: traps

這一個實驗主要是對RISC-V的彙編、棧幀結構以及陷阱進行簡單的瞭解,難度並不大。git

代碼放在github上。github

RISC-V assembly (easy)

Q1: Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?app

RISC-V的函數調用過程參數優先使用寄存器傳遞,即a0~a7共8個寄存器。返回值能夠放在a0和a1寄存器。printf的參數13保存在a2寄存器。函數

Q2: Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.學習

從代碼能夠看出,這兩個都被內聯優化處理了。main中的f調用直接使用告終果12,而f中的函數g調用直接內聯在f中了。優化

Q3: At what address is the function printf located?ui

在0x630的位置this

Q4: What value is in the register ra just after the jalr to printf in main?code

值應該爲0x38,即函數的返回地址。遞歸

跳轉並連接指令(jal)具備雙重功能。若將下一條指令PC + 4的地址保存到目標寄存器中,一般是返回地址寄存器ra,即可以用它來實現過程調用。若是使用零寄存器(x0)替換ra做爲目標寄存器,則能夠實現無條件跳轉,由於x0不能更改。像分支同樣,jal將其20位分支地址乘以2,進行符號擴展後再添加到PC上,便獲得了跳轉地址。

跳轉和連接指令的寄存器版本(jalr)一樣是多用途的。它能夠調用地址是動態計算出來的函數,或者也能夠實現調用返回(只需ra做爲源寄存器,零寄存器(x0)做爲目的寄存器)。Switch和case語句的地址跳轉,也能夠使用jalr指令,目的寄存器設爲x0。

Q5: Run the following code.

unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

What is the output? Here's an ASCII table that maps bytes to characters.

The output depends on that fact that the RISC-V is little-endian. If the RISC-V were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?

結果爲:He110 World; 不要修改成0x726c6400; 57616不須要進行改變,編譯器會進行轉換。

Q6: In the following code, what is going to be printed after 'y='? (note: the answer is not a specific value.) Why does this happen? printf("x=%d y=%d", 3);

應該打印出寄存器a2的值,由於printf會從a2寄存器中讀取第三個參數做爲y的值。

Backtrace (moderate)

實現backtrace,遞歸打印函數調用棧。使用r_fp獲取當前棧幀地址,因爲棧是由高地址向低地址增加的,所以使用PGROUNDUP得到棧底地址,以後循環打印棧幀的函數的返回地址。

void
backtrace(void)
{
  printf("backtrace:\n");
  uint64 fp = r_fp();
  uint64 base = PGROUNDUP(fp);
  while(fp < base) {
    printf("%p\n", *((uint64*)(fp - 8)));
    fp = *((uint64*)(fp - 16));
  }
}

Alarm (hard)

這一個要求添加系統調用sigalarm來實現當用戶程序運行了n個ticks後,觸發一次回調函數。由以前的學習能夠知道,時鐘中斷的處理是在usertrap函數中的if(which_dev == 2)裏面的。

爲了實現這個功能,首先在proc結構體中添加相應字段:

struct proc {
  ...
  // these are used for sys_alarm
  int duration;                // ticks after last alarm
  int alarm;                   // alarm every n ticks
  uint64 handler;              // handler for alarm
  struct trapframe *alarm_trapframe; // register saved for alarm
};

以後實現sys_alarm函數,將相關信息填入proc中:

uint64
sys_sigalarm(void)
{
  int ticks;
  uint64 handler;
  if(argint(0, &ticks) < 0)
    return -1;
  if(argaddr(1, &handler) < 0)
    return -1;
  
  struct proc* p = myproc();
  p->alarm = ticks;
  p->handler = handler;
  p->duration = 0;
  p->alarm_trapframe = 0;
  return 0;
}

而最關鍵的部分是在usertrap中,當發生時鐘中斷時,將p->duration增長,若是p->duration == p->alarm,那麼就要觸發一次回調函數,而觸發的方法就是將p->trapframe->epc設置爲回調函數地址,當陷阱處理程序結束後就會跳轉到回調函數。

而爲了保證回調函數不會破壞原程序的寄存器,須要對trapframe進行保存;我這裏選擇的方法是經過kalloc申請一個新的trapframe結構體,而後將trapframe複製一份。

爲了保證回調函數執行期間不會重複調用,就能夠判斷p->alarm_trapframe是否爲0,不爲0說明上一次的回調函數尚未調用sigreturn,即函數未結束。

if(which_dev == 2){
    if(p->alarm != 0){
      p->duration++;
      if(p->duration == p->alarm){
        p->duration = 0;
        if(p->alarm_trapframe == 0){
          p->alarm_trapframe = kalloc();
          memmove(p->alarm_trapframe, p->trapframe, 512);
          p->trapframe->epc = p->handler;
        }else{
          yield();
        }
      }else{
        yield();
      }
    }else{
      yield();
    }
  }

最後就是sigreturn函數,這個函數要作的工做就是將以前保存的alarm_trapframe還原到trapframe中,並將alarm_trapframe釋放掉。別忘了在freeproc函數中也要對p->alarm_trapframe進行判斷,防止程序異常結束時該頁面沒有被釋放。

uint64
sys_sigreturn(void)
{
  struct proc* p = myproc();
  if(p->alarm_trapframe != 0){
    memmove(p->trapframe, p->alarm_trapframe, 512);
    kfree(p->alarm_trapframe);
    p->alarm_trapframe = 0;
  }
  return 0;
}
相關文章
相關標籤/搜索