Lab4_1 RISC-V Assembly

我们需要运行对call.c这份代码的编译,然后回答一些问题

make fs.img编译之后我们可以找到下面的

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:	7b050513          	addi	a0,a0,1968 # 7d8 <malloc+0xea>
  30:	00000097          	auipc	ra,0x0
  34:	600080e7          	jalr	1536(ra) # 630 <printf>
  exit(0);
  38:	4501                	li	a0,0
  3a:	00000097          	auipc	ra,0x0
  3e:	27e080e7          	jalr	638(ra) # 2b8 <exit>

1.Q:Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
哪些寄存器存储了函数的参数?比如说调用printf的时候13存在哪个寄存器里面?

A:a0~a7共8个参数,其中调用printf的时候13就存在a2这个寄存器里面

2.Q: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.)
汇编代码中哪一部分出现了对函数f的调用,函数g呢?

A:其实并没有,编译器把g函数内联到f函数里面,再把f函数内联到main里面

3.Q:At what address is the function printf located?
printf的入口地址在哪里?

A:根据jalr 1536(ra)可以知道printf的地址在0x630这个里面

4.Q:What value is in the register ra just after the jalr to printf in main?
printf执行完返回到main的时候ra寄存器的值是多少?

显然ra中应是jalr下一条指令的地址,即0x38。

5.Q: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 change57616 to a different value?
输出是什么?RISC-V是小端存储的.如果是大端存储的呢?

Here’s a description of little- and big-endian and a more whimsical description.

输出”HE110 World”. 57616=0xe110。RISC-V 是 little-endian,&i处存储的字节依次为0x72:r, 0x6c:l, 0x64:d,0x00:Null char (空字符)。

如果 RISC-V 是 big-endian,则i = 0x726c6400;. 57616不需改变,因为不管怎样它的十六进制形式都不会变。

6.Q:In the following code, what is going to be printed after 'y='? (note: the answer is not a specific value.) Why does this happen?
执行下一条语句,会输出y=多少呢?

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

因为函数a0~a7都存着参数,所以说对应的输出第二个参数的内容,也就是a1寄存器的内容.

Lab4_2 BackTrace

添加一个新的功能,打印函数调用栈.在这个机器中,我们知道有一个结构叫做栈帧,可以保存当前函数调用某个函数之前的一些寄存器,返回地址和一些局部变量的信息,比如说C语言的main函数,main函数调用一个函数f1(),在进入f1()函数的执行之前,编译器会帮我们把main函数的一些寄存器、局部变量的信息保存在栈帧中放入堆栈.其中栈帧中有一个特别的元素就是上一个栈帧的地址.

本实验的要求就是在kernel/printf.c 中实现backtrace()函数。在sys_sleep中插入对该函数的调用。然后运行bttest,它调用sys_sleep。输出应如下:

1) 在def.h添加backtrace()函数的声明.
2) GCC 编译器将当前执行的函数的帧指针存储在寄存器s0中,s0就对应上面的fp指针.
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}
3) 完成backtrace()

我们知道fp指针往下走两个字节就存储的上一个函数的栈帧的最高地址,所以说我们可以循环一下,每一次循环就取上一个函数的栈帧最高地址,输出返回地址即可.

void
backtrace(void){
  uint64 fp = r_fp(), top = PGROUNDUP(fp);
  printf("backtrace:\n");
  for(;fp<top;fp=*(uint64*)(fp-16)){
    printf("%p\n", *((uint64*)(fp-8)));
  }
}

Lab4_3 Alarm

在本练习中,您将向xv6添加一项功能,该功能会在使用CPU时间的情况下定期向进程发出警报。这对于想要限制消耗多少CPU时间的计算密集型进程,或者对于想要进行计算但还希望采取一些定期操作的进程很有用。

您应该添加一个新的sigalarm(interval,handler)系统调用。 如果应用程序调用sigalarm(n,fn),则在程序每消耗n个“ tick” CPU时间之后,内核应导致调用应用程序函数fn。 当fn返回时,应用程序应从中断处恢复。 滴答是xv6中相当随意的时间单位,由硬件计时器产生中断的频率决定。 如果应用程序调用sigalarm(0,0),则内核应停止生成定期警报调用。

test0:调用处理程序

1.修改Makefile,为sigalarm和sigreturn系统调用添加桩代码。
2.现在,sys_sigreturn应该只返回零。
3.sys_sigalarm()应该将nl和handler存储在proc结构的新字段中(在kernel/proc.h 中)。
4.您需要跟踪该进程自上次调用alarm handler以来已经过去了多少ticks。为此,您还需要struct proc中的一个新字段。您可以在proc.c的allocproc() 中初始化这个字段。
5.每当tick时,硬件时钟都会强制中断,该中断在kernel/trap.c的usertrap()中处理。
6.时钟中断是:if(which_dev == 2) …
7.您需要修改usertrap()以在进程的警报间隔到期时,用户进程执行handler。

test1/test2(): 恢复中断的代码

8.handler完成后,控制权返回到用户程序最初被中断的指令。必须确保寄存器内容恢复,以及重置警报计数器。以便定期调用handler。
9.user alarm handler需要在完成后调用sigreturn系统调用。这意味着您可以将代码添加到usertrap和sys_sigreturn中,它们会协同工作以使用户进程在处理完警报后正确恢复。
10.确保正确地保存和恢复寄存器。
11.当计时器到期时,让 usertrap 在 struct proc 中保存足够的状态,以便sigreturn可以正确返回 到被中断的用户代码。
12.防止对处理程序的重入调用——如果处理程序尚未返回,内核不应再次调用它。

首先就是把添加系统调用的路子走一遍.

1) 在user.h中添加声明:
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
2) 在user.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);
......
[SYS_sigalarm]  sys_sigalarm,
[SYS_sigreturn] sys_sigreturn,
5) 在proc的进程结构体中添加成员,保存什么时候中断,中断执行什么,过多久就进行一次中断,还保存中断的时候栈顶指针和保存的栈帧.
int interval;// interupt count
void (*handler)(); //handle programme
int count; //tick interupt count
uint64 interrupt_ra; //trapframe pointer.
struct trapframe *saved_trapframe;//saved_trapframe
int isworking;
6) 在allocproc函数中初始化这些成员.
  p->interval = 0;
  p->handler = 0;
  p->count = 0;
  p->is working = 0;
7) 完成sigalarm的系统调用,做法就是从寄存器获得参数然后赋值给proc的元素中.
uint64
sys_sigalarm(void){
  struct proc* p = myproc();
  int in;
  uint ft;
  int interval;
    uint64 pt;
    if(argint(0, &in) < 0)
      return -1;
    if(argaddr(1, &ft) < 0)
      return -1;

    p->interval=in;
    p->handler=(void*)ft;
    return 0;
}
8) 完成时钟
if(which_dev == 2){
    //the proc no call the sysalaram.
    if(p->interval==0||p->working)
    {
      yield();
      usertrapret();
    }
    else{
      p->count++; 
      if(p->count==p->interval)
      {
        //save the trapframe.
        p->saved_trapframe=(struct trapframe*)kalloc();
        memmove(p->saved_trapframe,p->trapframe,PGSIZE);
        
        //save the trapped address
        p->interrupt_ra=p->trapframe->epc;
        //the programme will start at handler.
        p->trapframe->epc=(uint64)p->handler;
        usertrapret();
        yield();
      }
    }
  }

处理的方式是分类,如果之前没有调用过sigalarm的话,interval为0,就和原来一样,如果不是的话就代表调用过.接着count++,如果count和interval一样的话就代表要跳转了,保存当前的trapframe以及当前被中断的指令的地址,修改epc进行跳转.还有就是要记录working,如果这个函数已经被打断了就不要再重新打断一次.

9) 完成返回的操作.
uint64 sys_sigreturn(void)
{
  struct proc* p = myproc();
  p->count=0;
  p->trapframe->epc=p->interrupt_ra;
  memmove(p->trapframe,p->saved_trapframe,PGSIZE);
  p->isworking = 0;
  return 0;
}

把之前保存的断点地址和栈帧读出来,清空时钟即可.


0 条评论

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注

隐藏