Lab:traps
前置知识
- xv6 book 第四章
- kernel/trampolines.S 涉及从用户态到内核态以及返回的代码
- kernel/trap.c 处理中断的代码
RISC-V assembly
实验目标
该实验旨在让你熟悉一些汇编代码
Backtrace
实验目标
在kernel/printf.c 中实现一个函数 backtrace(), 并在 sys_sleep 中调用它。
backtrace() 会打印当前栈上所有函数调用返回地址
backtrace: 0x0000000080002cda 0x0000000080002bb6 0x0000000080002898
实验实现
先看提示
- 编译器会往每一个帧栈中放入一个指向调用者地址的frame pointer(指向某个函数使用的栈地址的顶端–从高往低)。你的 backtrace 应该利用这些 frame pointer 来遍历栈,打印每个帧栈的返回地址
- gcc编译器将当前的frame pointer 储存在 寄存器 s0,在kernel/riscv.h 加入以下函数来获取它
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
-
返回地址储存在距离帧栈顶部(-8)偏移处, 指向上一个帧栈的指针储存在帧栈顶部(-16)偏移处
-
xv6 为每个栈分配一页的大小,并且栈底地址是 PGSIZE 对齐的,你可以使用 PGROUNDDOWN(fp) 和 PGROUNDUP(fp) 来计算栈顶和栈底的地址。(可用于计算 backtrace 的终止条件)
综上所述,我们得出一个 backtrace 的大概设计方案
先使用 r_fp() 获得当前的帧栈顶部地址
递归地进行
- 打印返回地址(-8)
- 获取指向上一个帧栈顶部的指针(-16)
- 解析指针,判断是否到达栈底
代码实现
/* kernel/printf.c */
void
backtrace(void)
{
printf("backtrace:\n");
uint64 addr = r_fp();
while (PGROUNDUP(addr) - PGROUNDDOWN(addr) == PGSIZE)
{
uint64 ret_addr = *(uint64 *)(addr - 8);
printf("%p\n", ret_addr);
addr = *((uint64 *)(addr - 16));
}
}
/* sysproc.c */
uint64
sys_sleep(void)
{
backtrace();
......
}
Alarm
实验目标
实现系统调用*sigalarm(int n, (void )handler) 和 sigreturn()
当它被调用后, 系统将在 n 个时钟中断后执行 handler,执行完毕后调用 sigreturn 返回
实验实现
先看提示
- 你需要保持对时钟间隔 n 和回调函数指针 handler 的记录。(在 proc structure 中记录)。除此之外,我们还需要判断当前是否以达到时钟间隔,并且在调用回调函数前,需要保存当前进程的用户上下文(trapframe),以用于 sigreturn 返回。另外,在正在运行回调函数时,我们不希望再次中断运行回调函数,所以还需要加一个标志位标识是否正在运行回调函数。
struct proc {
......
int interval; // interval of alarm
int since_interval; // last call until now
void (*handler)(); // function pointer
int running_hand; // flag of running
struct trapframe trapframe_cp; //copy of trapframe_cp
};
-
在 allproc 中初始化你新增的字段
-
每个 tick,硬件都会发出一个时钟中断到内核, 在 usertrap() 中被处理
if(which_dev == 2) ...
综上所述,我们得出大概得设计方案
- 在每次时钟中断处,将时钟计数器+1. 若计数器达到时钟间隔,则
- 先保存进程的用户上下文 trapframe
- 回调函数运行标志位置1
- 通过设置程序计数器调用回调函数
- 回调函数调用完毕后,test程序会调用 sigreturn,sigreturn 需要做的事有
- 重置计数器
- 还原进程用户上下文trapframe
- 回调函数运行标志位置0
代码实现
/*
系统调用声明部分略过
*/
/* sysproc.c */
uint64
sys_sigreturn(void)
{
struct proc *p = myproc();
p->since_interval = 0;
*(p->trapframe) = p->trapframe_cp;
p->running_hand = 0;
return 0;
}
uint64
sys_sigalarm(void)
{
int interval;
uint64 pointer;
if(argint(0, &interval) < 0)
return -1;
if(interval == 0)
return 0;
if(argaddr(1, &pointer) < 0)
return -1;
myproc()->handler = (void *)pointer;
myproc()->since_interval = 0;
myproc()->running_hand = 0;
myproc()->interval = interval;
return 0;
}
/* proc.c */
static struct proc*
allocproc(void)
{
....
found:
p->pid = allocpid();
p->interval = 0;
p->since_interval = 0;
p->handler = 0;
....
}
/* trap.c */
void
usertrap(void)
{
.......
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
{
if(p->interval != 0)
{
if(!p->running_hand)
p->since_interval = p->since_interval + 1;
if(!p->running_hand && p->since_interval == p->interval)
{
printf("alarm!\n");
p->running_hand = 1;
p->trapframe_cp = *(p->trapframe);
p->trapframe->epc = (uint64)p->handler;
}
}
yield();
}
......
}