RISC-V Assembly
挺简单的,提了一些小问题
分享一个里面的比较有意思的代码吧
在小端存储的RISC-V上打印下面的内容
c
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, (char *) &i);
57616的16进制为e110,这个不受其他因素影响;i在小端存储内存中存为72 6c 64 00,对应字符为r l d
最后打印的结果就是HE110 World
Backtrace
在kernel/printf.c中实现函数backtrace(),对调用它的函数打印当前调用链上所有栈的ra返回值
这个task关键在于理解xv6中栈的存储方式,这里附上我的另一篇笔记MIT 6.1810: Lec 5: calling conventions and stack frames RISC-V中有关stack的图片

可以看到stack从栈顶开始,向下分别是:
ra (return address)
fp (frame pointer 指向前一个栈的栈顶)
saved registers
...
在RV64模式下,ra, fp(整数位宽 / pointer)均占8个字节
那么我们在持有每个栈的栈顶指针时,对应-8bytes / -16bytes 即可寻址到每个栈的ra和指向上一个栈栈顶的fp
c
void
backtrace()
{
uint64 *fp = (uint64*)r_fp();
uint64 page = PGROUNDDOWN((uint64)fp);
while(PGROUNDDOWN((uint64)fp) == page){
printf("%p\n", (void*)*(fp-1));
fp = (uint64*)*(fp-2);
}
}
对uint64指针-1 即为-8bytes
这里对每个fp调用PGROUNDDOWN确保当前栈与上一个栈在同一页内
Alarm
实现两个系统调用sigalarm(int n, void (*handler)()) sigreturn()
sigalarm使得当前进程每隔n ticks调用一次handler函数
- 操作系统每次执行
kernel/trap.c/usertrap()会通过which_dev == 2检查是否为时钟中断,如果值为真,这就是一次tick - handler函数传递的地址在对应用户文件汇编代码alarmtest.asm中可以看到,如periodic第一个指令的位置为0
- 若当前handler正在执行,不允许再次调用handler
sigreturn使当前进程恢复handler调用前的状态,继续向后执行代码
struct proc
我们需要在struct proc中记录一些参数
参数意义见注释
c
// File: kernel/proc.h
struct sigalarm {
int retflag; // 是否接受到sigreturn系统调用
int reentrant; // 是否接受调用handler(0则表示handler正在执行)
int interval; // 调用间隔(ticks)
uint64 handler; // handler函数指针
int cnt; // 当前距离上次handler调用的ticks间隔
uint64 ra; // 在调用handler时保存的各寄存器值,用于sigreturn
uint64 sp;
uint64 t0;
// ...
uint64 satp;
};
在struct proc中添加该结构体
c
// File: kernel/proc.h
struct proc {
// ...
struct sigalarm *sigalarm; // store alarm interval, handler function pointer, cnt
};
我们在每个进程初始化时分配这部分的内存
c
// File: kernel/proc.c
static struct proc*
allocproc(void)
{
// ...
// initialize p->sigalarm
if((p->sigalarm = (struct sigalarm *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
}
为sys_sigalarm和sys_sigreturn提供了在proc.c中各自的对应函数接口
c
// File: kernel/sysproc.c
// If an application calls sigalarm(n, fn),
// then after every n "ticks" of CPU time that the program consumes,
// the kernel should cause application function fn to be called.
uint64
sys_sigalarm(void)
{
int interval;
uint64 handler;
argint(0, &interval);
argaddr(1, &handler);
return ksigalarm(interval, handler);
}
// return from sigalarm
uint64
sys_sigreturn(void)
{
return ksigreturn();
}
sigalarm
准备工作做完了,现在我们考虑sigalarm的实现
每次调用alarm时,我们要将interval和handler的值保存在进程结构体中
当调用值为(0, 0)时,我们将间隔interval设为-1,无论多少个时间间隔都不调用handler
c
// File: proc.c
// ksigalarm(n, fn),
// after every n "ticks" of CPU time that the program consumes,
// the kernel should cause application function fn to be called.
// return 0 if success
int
ksigalarm(int interval, uint64 handler)
{
struct proc *p = myproc();
if(interval == 0 && handler == 0){
p->sigalarm->interval = -1;
}
p->sigalarm->interval = interval;
p->sigalarm->handler = handler;
p->sigalarm->cnt = 0;
p->sigalarm->retflag = 0;
p->sigalarm->reentrant = 1;
return 0;
}
将这些信息保存在进程后,每次进程在usertrap触发时钟中断时,判断cnt == interval,若值为真,则要跳转到handler函数
这里我们要设置trap有关的寄存器sepc ,这个寄存器用于保存trap发生时的pc
我们将sepc的值改为handler,userret汇编代码就会将pc设为handler,后续代码就会在handler上执行
c
// File: kernel/trap.c
uint64
usertrap(void)
{
if(which_dev == 2 && p->sigalarm->interval != -1){
if(p->sigalarm->reentrant == 1 && p->sigalarm->cnt == p->sigalarm->interval){
// set sepc to handler function
// every interval ticks
w_sepc(p->sigalarm->handler);
p->sigalarm->cnt = 0;
p->sigalarm->reentrant = 0;
}
++p->sigalarm->cnt;
}
}
sigreturn
这样我们的程序就可以正确地通过sigalarm跳转到handler上了,但我们得想办法让它能通过sigreturn返回到调用handler的地方
要实现这个目的,我们需要在时钟中断发生且跳转到handler时,将寄存器的值保存到进程结构体中
(这里我用了很多寄存器显得很臃肿,网上有solution选择直接将整个trapframe保存起来,代码会简洁的多,但也会保存很多不必要的寄存器)
c
// File: kernel/trap.c
uint64
usertrap(void)
{
// ...
if(which_dev == 2 && p->sigalarm->interval != -1){
if(p->sigalarm->reentrant == 1 && p->sigalarm->cnt == p->sigalarm->interval){
// restore regs for sigreturn
p->sigalarm->ra = p->trapframe->ra;
p->sigalarm->sp = p->trapframe->sp;
p->sigalarm->t0 = p->trapframe->t0;
p->sigalarm->t1 = p->trapframe->t1;
p->sigalarm->t2 = p->trapframe->t2;
p->sigalarm->s0 = p->trapframe->s0;
p->sigalarm->s1 = p->trapframe->s1;
p->sigalarm->a0 = p->trapframe->a0;
p->sigalarm->a1 = p->trapframe->a1;
p->sigalarm->a2 = p->trapframe->a2;
p->sigalarm->a3 = p->trapframe->a3;
p->sigalarm->a4 = p->trapframe->a4;
p->sigalarm->a5 = p->trapframe->a5;
p->sigalarm->a6 = p->trapframe->a6;
p->sigalarm->a7 = p->trapframe->a7;
p->sigalarm->s2 = p->trapframe->s2;
p->sigalarm->s3 = p->trapframe->s3;
p->sigalarm->s4 = p->trapframe->s4;
p->sigalarm->s5 = p->trapframe->s5;
p->sigalarm->s6 = p->trapframe->s6;
p->sigalarm->s7 = p->trapframe->s7;
p->sigalarm->s8 = p->trapframe->s8;
p->sigalarm->s9 = p->trapframe->s9;
p->sigalarm->s10 = p->trapframe->s10;
p->sigalarm->s11 = p->trapframe->s11;
p->sigalarm->t3 = p->trapframe->t3;
p->sigalarm->t4 = p->trapframe->t4;
p->sigalarm->t5 = p->trapframe->t5;
p->sigalarm->t6 = p->trapframe->t6;
p->sigalarm->sepc = r_sepc();
p->sigalarm->stvec = r_stvec();
p->sigalarm->sstatus = r_sstatus();
p->sigalarm->satp = r_satp();
// set sepc to handler function
// every interval ticks
w_sepc(p->sigalarm->handler);
p->sigalarm->cnt = 0;
p->sigalarm->reentrant = 0;
}
++p->sigalarm->cnt;
}
// ...
}
在系统调用sigreturn发生时,将进程的retflag设为1
c
// File:proc.c
int
ksigreturn()
{
struct proc *p = myproc();
p->sigalarm->retflag = 1;
return 0;
}
retflag会触发usertrap中if语句内的代码,将保存起来的寄存器值复制到trapframe中,而后续userret复制到真正的寄存器中的正是trapframe中的值
c
// File:trap.c
uint64
usertrap(void)
{
// ...
// sigreturn
if(p->sigalarm->retflag == 1){
p->trapframe->ra = p->sigalarm->ra;
p->trapframe->sp = p->sigalarm->sp;
p->trapframe->t0 = p->sigalarm->t0;
p->trapframe->t1 = p->sigalarm->t1;
p->trapframe->t2 = p->sigalarm->t2;
p->trapframe->s0 = p->sigalarm->s0;
p->trapframe->s1 = p->sigalarm->s1;
p->trapframe->a0 = p->sigalarm->a0;
p->trapframe->a1 = p->sigalarm->a1;
p->trapframe->a2 = p->sigalarm->a2;
p->trapframe->a3 = p->sigalarm->a3;
p->trapframe->a4 = p->sigalarm->a4;
p->trapframe->a5 = p->sigalarm->a5;
p->trapframe->a6 = p->sigalarm->a6;
p->trapframe->a7 = p->sigalarm->a7;
p->trapframe->s2 = p->sigalarm->s2;
p->trapframe->s3 = p->sigalarm->s3;
p->trapframe->s4 = p->sigalarm->s4;
p->trapframe->s5 = p->sigalarm->s5;
p->trapframe->s6 = p->sigalarm->s6;
p->trapframe->s7 = p->sigalarm->s7;
p->trapframe->s8 = p->sigalarm->s8;
p->trapframe->s9 = p->sigalarm->s9;
p->trapframe->s10 = p->sigalarm->s10;
p->trapframe->s11 = p->sigalarm->s11;
p->trapframe->t3 = p->sigalarm->t3;
p->trapframe->t4 = p->sigalarm->t4;
p->trapframe->t5 = p->sigalarm->t5;
p->trapframe->t6 = p->sigalarm->t6;
w_sepc(p->sigalarm->sepc);
w_stvec(p->sigalarm->stvec);
w_sstatus(p->sigalarm->sstatus);
w_satp(p->sigalarm->satp);
p->sigalarm->retflag = 0;
p->sigalarm->reentrant = 1;
}
// ...
}
注意设置reentrant保证handler在被调用时没有新的handler调用
最后附一张通过截图,usertests测出来pagetable好像出了点问题,我都怀疑上个lab没做出来可能都是xv6代码本身有问题
这个Alarm也不是很难嘛(叉腰
