這一個實驗主要是對RISC-V的彙編、棧幀結構以及陷阱進行簡單的瞭解,難度並不大。git
代碼放在github上。github
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,遞歸打印函數調用棧。使用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)); } }
這一個要求添加系統調用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; }