当前位置: 代码迷 >> 综合 >> 【操作系统】MIT 6.s081 LAB4
  详细解决方案

【操作系统】MIT 6.s081 LAB4

热度:95   发布时间:2023-12-04 12:29:26.0

LAB 4: Traps

原文地址:YSBLOG
参考:[mit6.s081] 笔记 Lab4: Traps | 中断陷阱

实验目的:探索如何通过trap实现系统调用。

RISC-V assembly (easy)

阅读call.asm函数代码,回答一下问题:

1、哪些寄存器保存函数的参数?例如,在mainprintf的调用中,哪个寄存器保存13?

在risc-v中a0-a7寄存器保存函数参数,如果函数参数超过8个,则保存在内存中,函数调用的默认第一个参数为函数本身名字,存放在a0寄存器,所以13作为printf的第二个参数存放在a2寄存器。

2、main的汇编代码中对函数f的调用在哪里?对g的调用在哪里(提示:编译器可能会将函数内联)

在call.asm第45行可以看出(li a1, 12)main中直接把f(8)+1的值传递给了a1寄存器,所以在汇编中main函数进行了内联优化处理,在mian的汇编代码中并有没对f进行函数调用。

3、printf函数位于哪个地址?

在call.asm第50行可以看出,printf的地址在0x640

4、在mainprintfjalr之后的寄存器ra中有什么值?

ra寄存器用来保存函数执行以后的下一个执行指令的地址,printf的jalr之后,ra寄存器应当是返回main函数的地址,为0x638

5、运行以下代码。

unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

程序的输出是什么?这是将字节映射到字符的ASCII码表。

程序的输出为"Hello World"

输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i设置成什么?是否需要将57616更改为其他值?

如果RISC-V是大端存储,为了得到相同的输出,需要把i设置成0x726c6400,不需要把57616修改为其他值。

在下面的代码中,“y=”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?

printf("x=%d y=%d", 3);

y=%d中的%d会替换为当前a2寄存器中的值,因为当前a2寄存器中值不明确,所以答案不是一个特定的值

Backtrace(moderate)

该任务的目的是输出调用backtrace()函数的cpu的函数当前函数调用关系。

image-20220110210154778

根据课程PPT中的结构图可以知道,fp指向当前函数栈的开头,sp执行当前函数栈的结尾。

每一个函数的前8位表示当前函数结束后返回上一级的地址(ra),8-16位表示上一级函数栈的fp,由于整个栈是由高地址向低地址扩展,所以uint64 ra = *(uint64*)(fp - 8); fp = *(uint64*)(fp - 16);

所以我们想要实现输出当前cpu所有函数栈,我们只需要通过获取当前fp寄存器的值后不断回到上一级fp,直到fp与当前页面顶部地址相同,则说明回到了第一级的fp。

1、在defs.h中添加函数定义`

void            backtrace(void);

2、根据提示,在riscv.h中添加获取当前fp寄存器的方法

static inline uint64
r_fp()
{
    uint64 x;asm volatile("mv %0, s0" : "=r" (x) );return x;
}

3、在printf.c中实现backtrace()

void
backtrace(void) {
    printf("backtrace:\n");// 读取当前帧指针uint64 fp = r_fp();while (fp != PGROUNDUP(fp)) {
    // xv6中,用一个页来存储栈,如果fp已经达到了栈页的上届,说明已经达到栈底// 地址扩张是向低地址扩展,所以当fp到达最高地址时说明到达栈底uint64 ra = *(uint64*)(fp - 8); // return addressprintf("%p\n", ra);fp = *(uint64*)(fp - 16); // preivous fp}
}

4、在printf.cpainc中添加backtrack的调用,这样就能在发生异常时定位问题了

void
panic(char *s)
{
    backtrace();pr.locking = 0;printf("panic: ");printf(s);printf("\n");panicked = 1; // freeze uart output from other CPUsfor(;;);
}

实验结果如下:

image-20220111103801583

Alarm(Hard)

该任务的目的是通过定时器中断实现定期警报功能,经过一定的CPU时间后,执行指定报警函数。

该任务的test0是完成对于报警函数的调用,不考虑函数的返回,在test1,test2中需要考虑在执行完报警函数后,能够正确返回被中断之前的状态(让报警函数对被中断的函数透明),所以需要保存中断之前的所有寄存器。

这里将全部要求一起实现,具体过程如下:

1、在makefile中添加 $U/_alarmtest\

2、在usys.pl中添加entry("sigalarm");``entry("sigreturn");

3、在syscall.h中添加#define SYS_sigalarm 22 *#define* SYS_sigreturn 23

4、在syscall.c中添加extern uint64 sys_sigalarm(void); extern uint64 sys_sigreturn(void);

5、在syscall.c中添加[SYS_sigalarm] sys_sigalarm,[SYS_sigreturn] sys_sigreturn,

6、proc.h中添加

struct proc {
    ... ...     uint64 interval;              // alarm interval timevoid (*handler)();            // alarm handle functionuint64 ticks;                 // how many ticks have passed since the last callstruct trapframe *alarm_trapframe;  // A copy of trapframe right before running alarm_handlerint alarm_goingoff;          // Is an alarm currently going off and hasn't not yet returned? (prevent re-entrance of alarm_handler)
}

7、在proc.callocproc函数,初始化新增字段

static struct proc*
allocproc(void)
{
    ... ...// Allocate a trapframe page.if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);return 0;}if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);return 0;}
... ...p->ticks = 0;p->handler = 0;p->interval = 0;p->alarm_goingoff = 0;return p;
}

8、在proc.cfreeproc函数中释放新增字段

static void
freeproc(struct proc *p)
{
    if(p->trapframe)kfree((void*)p->trapframe);if (p->alarm_trapframe) {
    kfree((void*)p->alarm_trapframe);}
... ...p->ticks = 0;p->handler = 0;p->interval = 0;p->alarm_goingoff = 0;p->state = UNUSED;
}

9、实现sys_sigalarm()sig_sigreturn()函数,用于实现系统调用

uint64
sys_sigalarm(void) {
    // sigalarm 的第一个参数为ticks,第二个参数为void(*handler)()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();
}

10、在trap.c中实现int sigalarm(int ticks, void(*handler)()以及int sigreturn()

int sigalarm(int ticks, void(*handler)()) {
    // 初始化alarm时设置该进程的计数大小以及对于alarm函数struct proc *p = myproc();p->interval = ticks;p->handler = handler;p->ticks = 0;return 0; 
}
int sigreturn() {
    // alarm返回时将备份的trapframe寄存器恢复,确保回退时cpu状态和进入中断时一致,对被中断函数透明struct proc *p = myproc();*(p->trapframe) = *(p->alarm_trapframe);// 清除进入alarm标志位,确保能再次进入p->alarm_goingoff = 0;return 0;
}

11、在trap.cusertrap函数中添加定时器中断响应

void
usertrap(void)
{
    int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.w_stvec((uint64)kernelvec);struct proc *p = myproc();// save user program counter.p->trapframe->epc = r_sepc();if(r_scause() == 8){
    // system callif(p->killed)exit(-1);// sepc points to the ecall instruction,// but we want to return to the next instruction.p->trapframe->epc += 4;// an interrupt will change sstatus &c registers,// so don't enable until done with those registers.intr_on();syscall();} else if((which_dev = devintr()) != 0){
    // okif (which_dev == 2) {
    // 当which_dev为2时,表示每个CPU时间触发的中断if (p->interval != 0) {
     // 设定了时钟条件if (p->ticks == p->interval && p->alarm_goingoff == 0) {
    // 达到计时次数后// 此时处于内核中断p->ticks = 0;// A程序进入内核时,会将pc的值存到spec中,离开内核时再从spec中读取该值,返回A中代码执行的地方// 此时修改spec寄存器的值为目标函数,就能够在离开中断时返回到我们想要的地方// 但在次之前要保存当前进程的寄存器值,保证在推出目标handler时能够回到A程序正确的位置中*(p->alarm_trapframe) = *(p->trapframe);p->trapframe->epc = (uint64)p->handler;p->alarm_goingoff = 1; // 不允许递归触发handler}p->ticks++; // cpu每产生一次timer中断计数++}}} else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());p->killed = 1;}if(p->killed)exit(-1);// give up the CPU if this is a timer interrupt.if(which_dev == 2)yield();usertrapret();
}

实验结果:

image-20220111115943919

  相关解决方案