【MIT6.S081】Lab4: traps(详细解答版)

实验内容网址:https://xv6.dgs.zone/labs/requirements/lab4.html
本实验的代码分支:https://gitee.com/dragonlalala/xv6-labs-2020/tree/traps2/

Backtrace

关键点:trapframe、栈

思路:

这道题的关键是栈结构,先阅读xv6中关于栈的知识(https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec05-calling-conventions-and-stack-frames-risc-v/5.5-stack)。
[图片]

阅读链接的资料,我们可以知道,

  1. 每调用一个函数,函数都会为自己创建一个Stack Frame。
  2. 栈是从高地址开始向低地址使用。所以栈总是向下增长。
  3. Return address总是会出现在Stack Frame的第一位,指向前一个Stack Frame的指针也会出现在栈中的固定位置
  4. 第一个寄存器是SP(Stack Pointer),它指向Stack的底部并代表了当前Stack Frame的位置。第二个寄存器是FP(Frame Pointer),它指向当前Stack Frame的顶部。FP-8位是返回地址,FP-16位是上一个stack frame的fp地址。
  5. 保存前一个Stack Frame的指针的原因是为了让我们能跳转回去。
    XV6在内核中以页面对齐的地址为每个栈分配一个页面。你可以通过PGROUNDDOWN(fp)和PGROUNDUP(fp)(参见kernel/riscv.h)来计算栈页面的顶部和底部地址。

步骤&代码:

  1. 将下面的函数添加到kernel/riscv.h
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}
  1. 在kernel/defs.h中添加backtrace的原型,在printf.c中定义backtrace函数。
void       
backtrace(void){
  // 读取当前Frame Pointer
  uint64 fp = r_fp();
  while(PGROUNDUP(fp) - PGROUNDDOWN(fp) == PGSIZE){
    // 返回地址保存在-8偏移的位置
    uint64 ret_addr = *(uint64*)(fp-8);
    printf("%p\n",ret_addr);
    // 前一个帧指针保存在-16偏移的位置
    fp = *(uint64*)(fp-16);
  }
}
  1. 在sys_sleep中添加对backtrace函数的调用。
uint64
sys_sleep(void)
{
  int n;
  uint ticks0;

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

未解:

这里说的stack是存放在用户页表中的吗

Alarm

关键点:从用户空间陷入陷阱、epc寄存器、trapframe

思路:(参考自https://xv6.dgs.zone/labs/answers/lab4.html)

这项练习要实现定期的警报。首先是要通过test0,如何调用处理程序是主要的问题。程序计数器的过程是这样的:

  1. ecall指令中将PC保存到SEPC
  2. 在usertrap中将SEPC保存到p->trapframe->epc
  3. p->trapframe->epc加4指向下一条指令
  4. 执行系统调用
  5. 在usertrapret中将SEPC改写为p->trapframe->epc中的值
  6. 在sret中将PC设置为SEPC的值
    可见执行系统调用后返回到用户空间继续执行的指令地址是由p->trapframe->epc决定的,因此在usertrap中主要就是完成它的设置工作。
    接下来要通过test1和test2,要解决的主要问题是寄存器保存恢复和防止重复执行的问题。考虑一下没有alarm时运行的大致过程
  7. 进入内核空间,保存用户寄存器到进程陷阱帧
  8. 陷阱处理过程
  9. 恢复用户寄存器,返回用户空间
    而当添加了alarm后,变成了以下过程
  10. 进入内核空间,保存用户寄存器到进程陷阱帧
  11. 陷阱处理过程
  12. 恢复用户寄存器,返回用户空间,但此时返回的并不是进入陷阱时的程序地址,而是处理函数handler的地址,而handler可能会改变用户寄存器
    因此我们要在usertrap中再次保存用户寄存器,当handler调用sigreturn时将其恢复,并且要防止在handler执行过程中重复调用。
    步骤&代码:
    根据题目的提示进行编程
  13. 需要修改Makefile以使alarmtest.c被编译为xv6用户程序。
UPROGS=\
...
    $U/_zombie\
    $U/_alarmtest\
  1. 在user/user.h中放入函数声明:
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
  1. 更新user/usys.pl(此文件生成user/usys.S)、kernel/syscall.h和kernel/syscall.c以允许alarmtest调用sigalarm和sigreturn系统调用。
# user/usys.pl文件中
...
entry("sleep");
entry("uptime");
entry("sigalarm");
entry("sigreturn");

// syscall.h文件添加
#define SYS_sigalarm  22
#define SYS_sigreturn  23

// syscall.c文件中
... 
extern uint64 sys_uptime(void);
extern uint64 sys_sigalarm(void);// 新添加
extern uint64 sys_sigreturn(void);// 新添加



... 
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
[SYS_sigalarm]   sys_sigalarm,// 新添加
[SYS_sigreturn]   sys_sigreturn,// 新添加
...
  1. 在sysproc.c文件中添加函数
// 新添加
uint64 sys_sigalarm(void){

  return 0;
}
uint64 sys_sigreturn(void){

  return 0;
}
  1. 根据提示在struct proc 的定义中添加新字段,分别代表报警间隔,指向处理程序函数的指针,用于跟踪自上次调用到进程的报警处理程序间经历了多少滴答。
 int alarm_interval;           // 报警间隔
  void (*alarm_handle);           // 报警处理函数
  int last_tick_time;           // 上次报警的滴答时刻
  1. 初始化进程时初始化上面的三个新参数。
// 在allocproc函数中添加
  ...
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;
  p->alarm_handle = 0;
  p->alarm_interval = 0;
  p->last_tick_time = 0;
  ...
  1. 根据提示,每一个滴答声,硬件时钟就会强制一个中断,这个中断在kernel/trap.c中的usertrap()中处理。因此在usertrap()中添加如下代码
// give up the CPU if this is a timer interrupt.
  if(which_dev == 2){
    // 获取当前的时钟数
    int xticks;
    acquire(&tickslock);
    xticks = ticks;
    release(&tickslock);
    if(xticks - p->last_tick_time >= p->alarm_interval && p->alarm_interval != 0 && p->alarm_handle != 0 ){
      // 调用处理函数
      p->trapframe->epc = (uint64)p->alarm_handle;
      // 更新上次的时钟
      p->last_tick_time = xticks;
    }
    yield();
  }

上面这个代码中获取时间的方式可能比较的弯弯绕绕。其实可以添加一个tick_count的字段,因为每次滴答中断都会进入这段代码,直接对这个变量进行累积即可。
8. 对sys_sigalarm函数进行编写,获得报警间隔和报警处理函数,并将值赋给新添加的字段

uint64 sys_sigalarm(void){
  int interval;
  uint64 handle;
  if(argint(0, &interval) < 0)
    return -1;
    
  if(argaddr(0, &handle) < 0)
    return -1;
  myproc()->alarm_interval = interval;
  myproc()->alarm_handle = (void *)handle;
  return 0;
}

上面8个步骤可以通过test0。但通不过test1 2,会出现panic。原因是执行完报警处理函数后返回用户空间,但此时返回的并不是进入陷阱时的程序地址,而是处理函数handler的地址,而handler可能会改变用户寄存器。必须确保完成报警处理程序后返回到用户程序最初被计时器中断的指令执行。必须确保寄存器内容恢复到中断时的值,以便用户程序在报警后可以不受干扰地继续运行。
8. 为了实现备份寄存器与防止对处理程序的重复调用,在struct proc定义中添加2个新字段。

 struct trapframe *trapframe_copy; // data page for trampoline.S copy
  int is_alarming;      
  1. 初始化进程时对这两个字段进行初始化。在allocproc函数中添加如下代码:
 ...
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }
  // Allocate a trapframe page(copy).
  if((p->trapframe_copy = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }
  ...
  ...
  p->alarm_interval = 0;
  p->last_tick_time = 0;
  p->is_alarming = 0;
  ...
  1. 进程释放时也要对新增的trapframe进行释放,因为开辟了一页内存空间,需要释放,在freeproc函数中添加:
 if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->trapframe_copy)
    kfree((void*)p->trapframe_copy);
  p->trapframe_copy = 0;
  ...
  1. 在usertrap函数中,进入报警处理函数前,需要备份一下trapframe。修改usertrap函数代码
// give up the CPU if this is a timer interrupt.
  if(which_dev == 2){
    // 获取当前的时钟数
    int xticks;
    acquire(&tickslock);
    xticks = ticks;
    release(&tickslock);
    if(xticks - p->last_tick_time >= p->alarm_interval && p->alarm_interval != 0 && p->alarm_handle != 0 && p->is_alarming == 0){
      // 设置为正处于报警程序
      p->is_alarming = 1;
      // 备份trapframe
      memmove(p->trapframe_copy, p->trapframe, sizeof(struct trapframe)); 
      // 调用处理函数
      p->trapframe->epc = (uint64)p->alarm_handle;
      // 更新上次的时钟
      p->last_tick_time = xticks;
    }
    yield();
  }
  1. 补充sys_sigreturn函数,这个函数是由测试程序调用的。在执行完报警处理函数后执行。使得trapframe中寄存器的值恢复成报警前的状态。
uint64 sys_sigreturn(void){
  // 还原寄存器
  memmove(myproc()->trapframe, myproc()->trapframe_copy, sizeof(struct trapframe));
  myproc()->is_alarming = 0;
  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/561882.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kafka导航【Kafka】

Kafka导航【Kafka】 前言版权推荐Kafka随堂笔记 第三章 生产者3.4生产者分区3.4.1.分区好处3.4.2 生产者发送消息的分区策略3.4.3 自定义分区器 3.5 生产经验——生产者如何提高吞吐量3.6 生产经验——数据可靠性3.7 生产经验——数据去重3.7.1 数据传递语义3.7.2 幂等性3.7.3生…

python爬虫笔记1

1 爬虫介绍 爬虫概述&#xff1a; 获取网页并提取和保存信息的自动化程序 1.获取网页 2.提取信息 css选择器 xpath 3.保存数据&#xff08;大数据时代&#xff09; 4.自动化 爬虫&#xff08;资产收集&#xff0c;信息收集&#xff09; 漏扫&#xff08;帮我发现漏洞&#xff…

FK中的一些方法

1. 隔离法与整体法 目标&#xff1a;对一个拉新邀请任务&#xff0c;识别出其中的作弊用户。 欺诈类的数据&#xff0c;黑样本不足&#xff0c;需要自己去找&#xff0c;可按IP、昵称、手机号相似性等。虽然有 会员等级、注册时长、注册地址、成交订单等特征&#xff0c;但分类…

内网抓取Windows密码明文与hashdump思考题笔记整理

目录 思考题 第一题 第二题 第三题 第四题 第五题 思考题 1.windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff0c;密文存在哪个文件下&#xff0c;该文件是否可以打开&#xff0c;并且查看到密文 2.我们通过hashdump 抓取出 所有用户的密文&#xff0c;分为…

扩散卷积模型 笔记

1 Title Diffusion Convolutional Neural Networks&#xff08;James Atwood and Don Towsley&#xff09;【NeurIPS 2016】 2 Conclusion This paper presents diffusion-convolutional neural networks (DCNNs), a new model for graph-structured data. Through the introd…

国内如何用GPT4

许多人曾向我咨询是否有一个稳定且不折腾的全球AI大模型测试网站&#xff0c;既能确保真实可靠性&#xff0c;又能保障稳定、快速的运行&#xff0c;避免频繁出现故障、错误或漫长的等待时间。到目前为止&#xff0c;我已经尝试了国内超过10个镜像站点&#xff0c;但遗憾的是&a…

【R语言】概率密度图

概率密度图是用来表示连续型数据的分布情况的一种图形化方法。它通过在数据的取值范围内绘制一条曲线来描述数据的分布情况&#xff0c;曲线下的面积代表了在该范围内观察到某一数值的概率。具体来说&#xff0c;对于给定的连续型数据&#xff0c;概率密度图会使用核密度估计&a…

分布式锁实现方案-基于zookeeper的分布式锁实现(原理与代码)

目录 一、基于zookeeper的分布式锁 1.1 基于Zookeeper实现分布式锁的原理 1.1.1 分布式锁特性说明 1.1.1.1 特点分析 1.1.1.2 本质 1.1.2 Zookeeper 分布式锁实现原理 1.1.2.1 Zookeeper临时顺序节点特性 1.1.2.2 Zookeeper满足分布式锁基本要求 1.1.2.3 Watcher机制 …

重生奇迹mu坐骑怎么升级

重生奇迹mu坐骑怎么升级 1、前期&#xff0c;都是主线任务&#xff0c;我们必须要跟着主线任务走&#xff0c;前面的话升级一次需要的经验很少的&#xff0c;一天下来可以升级100级是轻轻松松的&#xff0c;主线任务是比较多的&#xff0c;我们跟着任务一直做差不多可以到150级…

陇剑杯 省赛 攻击者1 CTF wireshark 流量分析

陇剑杯 省赛 攻击者1 题目 链接&#xff1a;https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码&#xff1a;haek ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志分析 │ │ acce…

MATLAB实现遗传算法优化BP神经网络预测数值(GABP)

遗传算法&#xff08;Genetic Algorithm, GA&#xff09;和反向传播&#xff08;Back Propagation, BP&#xff09;神经网络是两种强大的算法&#xff0c;分别用于优化和机器学习。将遗传算法与BP神经网络结合&#xff0c;可以利用遗传算法的全局搜索能力来优化BP神经网络的初始…

CAP实战Demo理论

问题 分布式微服务研发总会遇到CAP理论相关&#xff0c;但是没有相应例子&#xff0c;总是遗忘。 实践 实验 节点1 含有App1 Data1 节点2 含有App2 Data1 当节点1写请求执行成功&#xff0c;节点1数据为Data2&#xff0c;网络不通无法同步时 节点2 发送读请求 满足A可用性…

HBase安装部署

Apache HBase是按列存储数据的NoSQL类型数据库&#xff0c;其数据文件是存储在Hadoop集群中&#xff0c;支持数据以及服务的高可用性以及支持集群节点的大规模可扩展性&#xff0c;本文主要描述HBase的安装部署。 如上所示&#xff0c;HBase的总体架构&#xff0c;HBase Master…

「GO基础」变量

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【java】(软考)面向对象---责任链解析

目录 责任链的意义 手写笔记 ​编辑 责任链的意义 当您把请求给出时&#xff0c;如果某对象不能实现您的操作&#xff0c;责任链会自动把您的请求传给它的下一级 从而避免请求的发送者和接受者之间的耦合关系 这里以2007年下半年试题七进行说明 题目描述 某企业的采购审批…

EFK安装与使用!!!

一、将你的项目进行打包。 二、上传到docker&#xff0c; 启动项目 三、修改前端的代理路径 四、EFK相关配置 1、docker-compose.yml&#xff1a; version: 3 services:kibana:image: kibana:7.14.0ports:- "5601:5601"environment:- ELASTICSEARCH_HOSTShttp://19…

Excel数据处理:使用条件格式

选中你要特别体现的列 然后在开始处点击条件格式 标记大于1500000的值 点击突出显示 点击大于 输入1500000 设置单元格格式 点击条件格式 点击清除 去掉表头的格式 标记重复值强调显示 公式的使用 要去锁定单元格的值&#xff08;加上F4&#xff09; 比较后一列相同位置…

数据结构(七)——B树和B+树

7.4.1_1 B树 5叉查找树 //5叉排序树的结点定义 struct Node {ElemType keys[4]; //最多4个关键字struct Node &child[5]; //最多5个孩子int num; //结点中有几个关键字 }; 如何保证查找效率&#xff1f; eg:对于5叉排序树&#xff0c;规定…

kafka学习1 - 线程、进程消息通信方式、JMS模型、Kafka原理图

1、线程和线程之间的数据交互 两个不同的线程之间是可以通过堆内存的方式进行数据交互的&#xff1b; T1线程的数据发送得到堆内存&#xff0c;T2线程就可以共享堆内存的数据。 存在的问题&#xff1a; T1线程发送数据的速率是50/s&#xff0c;T2线程消费的速率是30/s&#…

Jmeter-非GUI模式下运行jmeter脚本-适用于服务器上持续集成测试

背景 大部分Jmeter脚本都是部署在Linux上运行&#xff0c;利用Jenkins做接口自动化&#xff0c;定时巡检任务。 执行命令 1.进入jmeter的目录&#xff0c;bin文件夹 cd C:\path\to\jmeter\bin2.运行脚本文件 jmeter -n -t D:\{脚本文件目录}\xxx.jmx -l D:\{脚本文件目录}…