6.S081 Lab: traps
6.S081 Lab4: traps

1、Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?
哪个寄存器存储函数的参数?例如,在main对printf的调用中哪个寄存器存了13?
a0-a7;a2
2、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.)
这里对f()和g()的调用被优化了,在汇编代码中可以看到f(8)+1被直接优化成了12
3、At what address is the function printf located?
0x630
4、What value is in the register ra just after the jalr to printf in main?
0x38
5、Run the following code.
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
What is the output? 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; No, 57616 is 110 in hex regardless of endianness
6、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);
A random value,my output is 0; '3' is passed to a1, and what is printed after 'y=' depends on what in the a2 before the call
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: 7b050513 addi a0,a0,1968 # 7d8 <malloc+0xea>
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 27e080e7 jalr 638(ra) # 2b8 <exit>
在RISC-V汇编中:


Backtrace
这个lab实际就是要求写一个backtrace函数,能够打印出frame pointer所指的地址处return address
这个slide描述了堆栈的结构
根据提示,我们有了r_fp()可以取出s0中的frame pointer,那么剩下的工作就是遍历栈,打印出所有的return address,请注意,返回地址与堆栈帧指针的固定偏移量(-8),保存的帧指针与帧指针的固定偏移量(-16)
void backtrace(){
uint64 frame_point = r_fp();
while(frame_point != PGROUNDUP(frame_point)){
printf("%p\n", *(uint64*)(frame_point-8));
frame_point = *(uint64*)(frame_point-16);
}
}
在sys_sleep开头插入backtrace()的调用:
uint64
sys_sleep(void)
{
int n;
uint ticks0;
backtrace();
if(argint(0, &n) < 0)
return -1;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){
if(myproc()->killed){
release(&tickslock);
return -1;
}
sleep(&ticks, &tickslock);
}
release(&tickslock);
return 0;
}
在defs.h中添加backtrace()的声明:
// printf.c
void printf(char*, ...);
void panic(char*) __attribute__((noreturn));
void printfinit(void);
// new
void backtrace(void);
Alarm
在本练习中,需要向xv6添加一个特性,该特性在进程使用CPU时定期向其发出警报。这对于希望限制占用多少CPU时间的受计算限制的进程,或者对于希望进行计算但又希望执行某些周期性操作的进程,可能很有用。更一般地说,将要实现的是用户级中断/故障处理程序的原始形式;例如,可以使用类似的方法来处理应用程序中的页面错误。
第一步是先添加一个sigalarm(interval, handler)函数调用,如果有一个应用调用了sigalarm(n, fn),每隔 n 个 "ticks" 的CPU时间,内核就会调用一次fn(),并且 fn返回后,应用应该回到调用前的状态
以 test0 为例:
// tests whether the kernel calls
// the alarm handler even a single time.
void
test0()
{
int i;
printf("test0 start\n");
count = 0;
sigalarm(2, periodic);
for(i = 0; i < 1000*500000; i++){
if((i % 1000000) == 0)
write(2, ".", 1);
if(count > 0)
break;
}
sigalarm(0, 0);
if(count > 0){
printf("test0 passed\n");
} else {
printf("\ntest0 failed: the kernel never called the alarm handler\n");
}
}
sigalarm(2, periodic)先调用sigalarm(), 将时间间隔设为 2,每隔2 个 ticks调用程序 periodic,然后调用 sigalarm(0, 0), 内核 停止 生成 periodic alarm calls.
具体步骤:
先修改 Makefile 让 alarmtest.c 能被编译为一个xv6用户程序:

在user/user.h 中声明:
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
更新 user/usys.pl , kernel/syscall.h, and kernel/syscall.c 让 alarmtest 能够进行 sigalarm 和 sigreturn 系统调用.
sys_sigalarm 和 sys_sigreturn系统调用:
uint64 sys_sigalarm(void){
int n;
uint64 handler;
if(argint(0, &n) < 0)
return -1;
if(argaddr(1, &handler) < 0)
return -1;
return sigalarm(n, (void(*)())(handler));
}
uint64 sys_sigreturn(void){
return sigreturn();
}
在 struct proc中增加几个字段:
int alarm_interval; // 时间间隔
void(*alarm_handler)(); // 处理程序
int interval_num; // 已经过了多少个 ticks
int pre_end; // 上一个 sigalarm 是否已经结束
struct trapframe *alarm_trapframe; // 用来 save and restore registers
初始化 proc fields in allocproc() in proc.c
static struct proc*
allocproc(void)
{
// ...
// Allocate a trapframe page.
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
release(&p->lock);
return 0;
}
// new
if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){
release(&p->lock);
return 0;
}
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
// new
p->interval_num = 0;
p->alarm_interval = 0;
p->alarm_handler = 0;
p->pre_end = 1;
return p;
}
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
if(p->alarm_trapframe)
kfree((void*)p->alarm_trapframe);
p->alarm_trapframe = 0;
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
p->pagetable = 0;
p->sz = 0;
p->pid = 0;
p->parent = 0;
p->name[0] = 0;
p->chan = 0;
p->killed = 0;
p->xstate = 0;
// new
p->alarm_handler = 0;
p->alarm_interval = 0;
p->interval_num = 0;
p->pre_end = 1;
p->state = UNUSED;
}
在trap.c 中增加sigalarm() 和 sigreturn() 函数:
int sigalarm(int ticks, void (*handler)()){
struct proc *p = myproc();
p->alarm_interval = ticks;
p->interval_num = 0;
p->alarm_handler = handler;
return 0;
}
int sigreturn(void){
struct proc *p = myproc();
p->pre_end = 1; // 上一个sigalarm已经结束
*p->trapframe = *p->alarm_trapframe; // 恢复寄存器
return 0;
}
修改usertrap() 函数:
void
usertrap(void)
{
// ...
// give up the CPU if this is a timer interrupt.
if(which_dev == 2){
if(p->alarm_interval != 0){
if(p->pre_end != 0){ // 没有别的sigalarm 在运行
p->interval_num += 1;
if(p->interval_num >= p->alarm_interval){ // 到了规定的计数次数
p->interval_num = 0; // 计数清零
*p->alarm_trapframe = *p->trapframe; // 保存寄存器
p->pre_end = 0;
// CPU由内核态退出,返回到用户模式时,会将sepc寄存器的值加载到pc寄存器,进而跳转到sepc指向的代码处执行
p->trapframe->epc = (uint64)p->alarm_handler; // epc 用来保存user program counter
}
}
}
yield();
}
usertrapret();
}

浙公网安备 33010602011771号