OSLab4

本文介绍了在RISC-V平台上,函数参数传递的寄存器使用,内联函数的实现,以及backtrace和Alarm功能的代码实现,探讨了中断处理和内存布局对程序执行的影响。
摘要由CSDN通过智能技术生成

RISC-V

user/call.asm

// 有关g, f, main部分的代码
int g(int x) {
   0:	1141                	addi	sp,sp,-16
   2:	e422                	sd	s0,8(sp)
   4:	0800                	addi	s0,sp,16
  return x+3;
}
   6:	250d                	addiw	a0,a0,3
   8:	6422                	ld	s0,8(sp)
   a:	0141                	addi	sp,sp,16
   c:	8082                	ret

000000000000000e <f>:

int f(int x) {
   e:	1141                	addi	sp,sp,-16
  10:	e422                	sd	s0,8(sp)
  12:	0800                	addi	s0,sp,16
  return g(x);
}
  14:	250d                	addiw	a0,a0,3
  16:	6422                	ld	s0,8(sp)
  18:	0141                	addi	sp,sp,16
  1a:	8082                	ret

000000000000001c <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:	7c850513          	addi	a0,a0,1992 # 7f0 <malloc+0xe8>
  30:	00000097          	auipc	ra,0x0
  34:	61a080e7          	jalr	1562(ra) # 64a <printf>
  exit(0);
  38:	4501                	li	a0,0
  3a:	00000097          	auipc	ra,0x0
  3e:	298080e7          	jalr	664(ra) # 2d2 <exit>

回答问题

  1. Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
  • 函数参数储存在 a0、a1、a2 等寄存器中。
  • 13 被保存在 a2 寄存器,从代码li a2,13可以看出。

  1. 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.)
  • user/call.cmain函数中对函数f进行了调用:printf("%d %d\n", f(8)+1, 13);,而在汇编代码中26: 45b1 li a1,12,直接将其替换成了12,说明了:编译器进行了内联优化,g 被内联到 f 中, f 被内联到 main 中。所以对于 main 函数的汇编代码来说,其并没有调用函数 fg ,而是在运行之前由编译器对其进行了计算。

  1. At what address is the function printf located?
000000000000064a <printf>:

void
printf(const char *fmt, ...)
{...}
  • 地址是: 0x64a

  1. What value is in the register ra just after the jalr to printf in main?
  • ra寄存器用来保存函数执行以后的下一条指令的地址,因此ra寄存器应当存放return address的地址,即0x38

  1. Run the following code.
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i)

What is the output? Here’s an ASCII table that maps bytes to characters.
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?
Here’s a description of little- and big-endian and a more whimsical description.

  • 输出是 He110 World
  • %x 打印的是16进制值,57616 转换为 16 进制为 0xe110
  • 小端从内存低位开始读,对应的 ASCII 字符为 rld
  • 大端从内存高位开始读
  • 无论是小端还是大端,区别在于储存方式,而实际值的大小是相同的。
  • 所以大端时,57616的值不改变,而 i 的值应修改为 0x00646c72

  1. 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);
  • y= 后会跟一个随机值
  • 因为在printf函数中,格式字符串中指定了两个%d占位符,但只提供了一个参数。因此,printf函数将尝试读取第二个参数时,会读取栈中下一个位置的值作为参数,而在这个寄存器中,值是未定义的,因此打印结果是一个随机值。

Backtrace

代码与实现思路

  • kernel/riscv.h
// kernel/riscv.h
// hint1:添加r_fp函数
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}

  • kernel/defs.h
// kernel/defs.h
// hint2:添加backtrace函数声明

...
// printf.c
void            printf(char*, ...);
void            panic(char*) __attribute__((noreturn));
void            printfinit(void);
void            backtrace(void);  // add
...

  • kernel/printf.c
// kernel/printf.c
// hint5:在panic调用
void
panic(char *s)
{
  pr.locking = 0;
  printf("panic: ");
  printf(s);
  printf("\n");
  backtrace();  // add
  panicked = 1; // freeze uart output from other CPUs
  for(;;)
    ;
}

// backtrace函数实现
void
backtrace(void)
{
  uint64 fp_address = r_fp();
  // PGROUNDDOWN(fp_address)为栈的底部
  while(fp_address != PGROUNDDOWN(fp_address)) {
    // 返回地址位于fp-8处,而前一个栈帧的帧指针位于fp-16
    printf("%p\n", *(uint64*)(fp_address-8));
    fp_address = *(uint64*)(fp_address - 16);
  }
}
  • kernel/sysproc.c
// kernel/sysproc.c
uint64
sys_sleep(void)
{
  int n;
  uint ticks0;
  
  backtrace();  // add

  argint(0, &n);
  if(n < 0)
    n = 0;
  acquire(&tickslock);
  ticks0 = ticks;
  while(ticks - ticks0 < n){
    if(killed(myproc())){
      release(&tickslock);
      return -1;
    }
    sleep(&ticks, &tickslock);
  }
  release(&tickslock);
  return 0;
}

实现结果

在这里插入图片描述


Alarm

代码实现

user/usys.plkernel/syscall.hkernel/syscall.cuser/user.h中添加sigalarmsigreturn的系统调用与声明。(具体代码略)

  • kernel/proc.h
// kernel/proc.h
struct proc {
  struct spinlock lock;
  
  // add start
  uint64 handler_va;   // 信号处理函数的虚拟地址
  int alarm_interval;  // 时间间隔
  int ticks;    // tick计数
  struct trapframe saved_trapframe;  // 保存中断时的 trapframe
  int have_return;     // 是否从alarm handler返回的flag
  // add end
  ...
}

  • kernel/sysproc.c
// kernel/sysproc.c
uint64
sys_sigalarm(void)
{
  int ticks;
  uint64 handler_va;

  argint(0, &ticks);
  argaddr(1, &handler_va);
  struct proc* proc = myproc();
  proc->alarm_interval = ticks;
  proc->handler_va = handler_va;
  proc->have_return = 1;
  return 0;
}

uint64
sys_sigreturn(void)
{
  struct proc* proc = myproc();
  *proc->trapframe = proc->saved_trapframe;
  proc->have_return = 1;
  return proc->trapframe->a0;
}

  • kernel/trap.c
// kernel/trap.c
void
usertrap(void)
{
  ...

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2) {
    struct proc *proc = myproc();
    if (proc->alarm_interval && proc->have_return) {
      if (++proc->ticks == 2) {
        proc->saved_trapframe = *p->trapframe;
        proc->trapframe->epc = proc->handler_va;
        proc->ticks = 0;
        proc->have_return = 0;
      }
    }
    yield();
  }

  usertrapret();
}

实现思路

kernel/sysproc.c中:

  • sys_sigalarm函数:从用户态获取参数 tickshandler_va,然后将这些值设置到当前进程的 proc 结构体中的相应字段中
  • sys_sigreturn函数:将保存的 trapframe 恢复到当前进程的 trapframe 中,然后将 have_return 标记设置为1,返回proc->trapframe->a0,恢复所有寄存器的值

kernel/trap.c中:
如果发生中断,则获取当前进程的 proc 结构体,并检查间隔非0且有返回值,tick计数加1,当满足2次时,将保存的 trapframe 赋值给当前进程的 trapframe,将 epc 设置为信号处理函数的虚拟地址,使 trap 返回时将 pc 值修改为该 alarm handler 函数的地址,并将 tickshave_return 重置为0。


实验结果

  • alarmtest

在这里插入图片描述


  • usertests -q

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值