MIT 6.1810: Lab traps: traps

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也不是很难嘛(叉腰

相关推荐
小陈phd1 小时前
多模态大模型学习笔记(四十八)——从自然语言到 SQL:大模型时代结构化数据查询的技术革命与落地实践
笔记·sql·学习
元气少女小圆丶2 小时前
SenseGlove Nova 2+Unity开发笔记4
笔记·unity·游戏引擎
ZK_H3 小时前
MFC程序开发自学笔记其一——windows应用程序与c++基础
c++·笔记·mfc
GLDbalala3 小时前
GPU PRO 5 - 2.6 Wire Antialiasing 笔记
笔记
梦074 小时前
学习笔记-ClaudeCode快速安装配置上手
笔记·学习
江华森4 小时前
TDengine 时序数据库深度学习笔记
笔记·时序数据库·tdengine
路人蛃4 小时前
【深入理解计算机系统】第二章第一节(信息存储)笔记
服务器·网络·笔记·计算机网络·系统架构
imDwAaY5 小时前
机器学习入门:从感知机到逻辑回归,理解线性分类器与Softmax CS188 Note20 学习笔记
人工智能·笔记·python·学习·机器学习·逻辑回归
chushiyunen5 小时前
json-rpc笔记
笔记·rpc·json