RISC-V
user/call.asm
// 有关g, f, main部分的代码
int g(int x) {
0: 1141 addi sp,sp,-16
2: e422 sd s0,8(sp)
4: 0800 addi s0,sp,16
return x+3;
}
6: 250d addiw a0,a0,3
8: 6422 ld s0,8(sp)
a: 0141 addi sp,sp,16
c: 8082 ret
000000000000000e <f>:
int f(int x) {
e: 1141 addi sp,sp,-16
10: e422 sd s0,8(sp)
12: 0800 addi s0,sp,16
return g(x);
}
14: 250d addiw a0,a0,3
16: 6422 ld s0,8(sp)
18: 0141 addi sp,sp,16
1a: 8082 ret
000000000000001c <main>:
void main(void) {
1c: 1141 addi sp,sp,-16
1e: e406 sd ra,8(sp)
20: e022 sd s0,0(sp)
22: 0800 addi s0,sp,16
printf("%d %d\n", f(8)+1, 13);
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7c850513 addi a0,a0,1992 # 7f0 <malloc+0xe8>
30: 00000097 auipc ra,0x0
34: 61a080e7 jalr 1562(ra) # 64a <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 298080e7 jalr 664(ra) # 2d2 <exit>
回答问题
- Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
- 函数参数储存在
a0、a1、a2
等寄存器中。 - 13 被保存在
a2
寄存器,从代码li a2,13
可以看出。
- 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.)
- 在
user/call.c
的main
函数中对函数f
进行了调用:printf("%d %d\n", f(8)+1, 13);
,而在汇编代码中26: 45b1 li a1,12
,直接将其替换成了12,说明了:编译器进行了内联优化,g
被内联到f
中,f
被内联到main
中。所以对于main
函数的汇编代码来说,其并没有调用函数f
和g
,而是在运行之前由编译器对其进行了计算。
- At what address is the function printf located?
000000000000064a <printf>:
void
printf(const char *fmt, ...)
{...}
- 地址是:
0x64a
- What value is in the register ra just after the jalr to printf in main?
ra
寄存器用来保存函数执行以后的下一条指令的地址,因此ra
寄存器应当存放return address
的地址,即0x38
。
- 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?
Here’s a description of little- and big-endian and a more whimsical description.
- 输出是
He110 World
。 - %x 打印的是16进制值,57616 转换为 16 进制为
0xe110
- 小端从内存低位开始读,对应的 ASCII 字符为
rld
- 大端从内存高位开始读
- 无论是小端还是大端,区别在于储存方式,而实际值的大小是相同的。
- 所以大端时,57616的值不改变,而 i 的值应修改为
0x00646c72
- 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);
- 在
y=
后会跟一个随机值 - 因为在
printf
函数中,格式字符串中指定了两个%d
占位符,但只提供了一个参数。因此,printf
函数将尝试读取第二个参数时,会读取栈中下一个位置的值作为参数,而在这个寄存器中,值是未定义的,因此打印结果是一个随机值。
Backtrace
代码与实现思路
kernel/riscv.h
// kernel/riscv.h
// hint1:添加r_fp函数
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
kernel/defs.h
// kernel/defs.h
// hint2:添加backtrace函数声明
...
// printf.c
void printf(char*, ...);
void panic(char*) __attribute__((noreturn));
void printfinit(void);
void backtrace(void); // add
...
kernel/printf.c
// kernel/printf.c
// hint5:在panic调用
void
panic(char *s)
{
pr.locking = 0;
printf("panic: ");
printf(s);
printf("\n");
backtrace(); // add
panicked = 1; // freeze uart output from other CPUs
for(;;)
;
}
// backtrace函数实现
void
backtrace(void)
{
uint64 fp_address = r_fp();
// PGROUNDDOWN(fp_address)为栈的底部
while(fp_address != PGROUNDDOWN(fp_address)) {
// 返回地址位于fp-8处,而前一个栈帧的帧指针位于fp-16
printf("%p\n", *(uint64*)(fp_address-8));
fp_address = *(uint64*)(fp_address - 16);
}
}
kernel/sysproc.c
// kernel/sysproc.c
uint64
sys_sleep(void)
{
int n;
uint ticks0;
backtrace(); // add
argint(0, &n);
if(n < 0)
n = 0;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){
if(killed(myproc())){
release(&tickslock);
return -1;
}
sleep(&ticks, &tickslock);
}
release(&tickslock);
return 0;
}
实现结果
Alarm
代码实现
在user/usys.pl
,kernel/syscall.h
,kernel/syscall.c
,user/user.h
中添加sigalarm
和sigreturn
的系统调用与声明。(具体代码略)
kernel/proc.h
// kernel/proc.h
struct proc {
struct spinlock lock;
// add start
uint64 handler_va; // 信号处理函数的虚拟地址
int alarm_interval; // 时间间隔
int ticks; // tick计数
struct trapframe saved_trapframe; // 保存中断时的 trapframe
int have_return; // 是否从alarm handler返回的flag
// add end
...
}
kernel/sysproc.c
// kernel/sysproc.c
uint64
sys_sigalarm(void)
{
int ticks;
uint64 handler_va;
argint(0, &ticks);
argaddr(1, &handler_va);
struct proc* proc = myproc();
proc->alarm_interval = ticks;
proc->handler_va = handler_va;
proc->have_return = 1;
return 0;
}
uint64
sys_sigreturn(void)
{
struct proc* proc = myproc();
*proc->trapframe = proc->saved_trapframe;
proc->have_return = 1;
return proc->trapframe->a0;
}
kernel/trap.c
// kernel/trap.c
void
usertrap(void)
{
...
// give up the CPU if this is a timer interrupt.
if(which_dev == 2) {
struct proc *proc = myproc();
if (proc->alarm_interval && proc->have_return) {
if (++proc->ticks == 2) {
proc->saved_trapframe = *p->trapframe;
proc->trapframe->epc = proc->handler_va;
proc->ticks = 0;
proc->have_return = 0;
}
}
yield();
}
usertrapret();
}
实现思路
kernel/sysproc.c
中:
sys_sigalarm
函数:从用户态获取参数ticks
和handler_va
,然后将这些值设置到当前进程的proc
结构体中的相应字段中sys_sigreturn
函数:将保存的trapframe
恢复到当前进程的trapframe
中,然后将have_return
标记设置为1,返回proc->trapframe->a0
,恢复所有寄存器的值
kernel/trap.c
中:
如果发生中断,则获取当前进程的 proc
结构体,并检查间隔非0且有返回值,tick计数加1,当满足2次时,将保存的 trapframe
赋值给当前进程的 trapframe
,将 epc
设置为信号处理函数的虚拟地址,使 trap
返回时将 pc
值修改为该 alarm handler
函数的地址,并将 ticks
和 have_return
重置为0。
实验结果
alarmtest
usertests -q