前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MIT_6.s081_Lab4:Xv6 and Trap

MIT_6.s081_Lab4:Xv6 and Trap

作者头像
用户7267083
发布2022-12-08 14:51:33
5630
发布2022-12-08 14:51:33
举报
文章被收录于专栏:sukuna的博客

MIT_6.s081_Lab4:Xv6 and Trap

于2022年3月5日2022年3月5日由Sukuna发布

Lab4_1 RISC-V Assembly

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

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

代码语言:javascript
复制
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.

代码语言:javascript
复制
	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=多少呢?

代码语言:javascript
复制
	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指针.
代码语言:javascript
复制
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}
3) 完成backtrace()

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

代码语言:javascript
复制
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中添加声明:
代码语言:javascript
复制
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
2) 在user.pl添加函数入口以生成汇编代码.
代码语言:javascript
复制
entry("sigalarm");
entry("sigreturn");
3) 在syscall.h添加系统调用号.
代码语言:javascript
复制
#define SYS_sigalarm  22
#define SYS_sigreturn 23
4) 在syscall.c中添加关于这两个系统调用的声明.
代码语言:javascript
复制
......
extern uint64 sys_sigalarm(void);
extern uint64 sys_sigreturn(void);
......
[SYS_sigalarm]  sys_sigalarm,
[SYS_sigreturn] sys_sigreturn,
5) 在proc的进程结构体中添加成员,保存什么时候中断,中断执行什么,过多久就进行一次中断,还保存中断的时候栈顶指针和保存的栈帧.
代码语言:javascript
复制
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函数中初始化这些成员.
代码语言:javascript
复制
  p->interval = 0;
  p->handler = 0;
  p->count = 0;
  p->is working = 0;
7) 完成sigalarm的系统调用,做法就是从寄存器获得参数然后赋值给proc的元素中.
代码语言:javascript
复制
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) 完成时钟
代码语言:javascript
复制
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) 完成返回的操作.
代码语言:javascript
复制
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;
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年3月5日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MIT_6.s081_Lab4:Xv6 and Trap
    • Lab4_1 RISC-V Assembly
      • Lab4_2 BackTrace
        • Lab4_3 Alarm
          • test0:调用处理程序
            • test1/test2(): 恢复中断的代码
            相关产品与服务
            对象存储
            对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档