216.Mit6.S081-实验四-Traps

本实验探索如何使用陷阱实现系统调用。您将首先使用栈做一个热身练习,然后实现一个用户级陷阱处理的示例。

开始编码之前,请阅读xv6手册的第4章和相关源文件:

  • kernel/trampoline.S:涉及从用户空间到内核空间再到内核空间的转换的程序集
  • kernel/trap.c:处理所有中断的代码

要启动实验,请切换到traps分支:

$ git fetch
$ git checkout traps
$ make clean

一、RISC-V assembly(easy)

1.实验要求

理解一点RISC-V汇编是很重要的,你应该在6.004中接触过。xv6仓库中有一个文件user/call.c。执行make fs.img编译它,并在user/call.asm中生成可读的汇编版本。

阅读call.asm中函数gfmain的代码。RISC-V的使用手册在参考页上。以下是您应该回答的一些问题(将答案存储在answers-traps.txt文件中):

2.提示

首先,执行 make fs.img 指令,进行编译。然后查看生成的 user/call.asm 文件,其中的 main 函数如下:

void main(void) {1c:	1141                	addi	sp,sp,-161e:	e406                	sd	ra,8(sp)20:	e022                	sd	s0,0(sp)22:	0800                	addi	s0,sp,16printf("%d %d\n", f(8)+1, 13);24:	4635                	li	a2,1326:	45b1                	li	a1,1228:	00000517          	auipc	a0,0x02c:	7b050513          	addi	a0,a0,1968 # 7d8 <malloc+0xea>30:	00000097          	auipc	ra,0x034:	600080e7          	jalr	1536(ra) # 630 <printf>exit(0);38:	4501                	li	a0,03a:	00000097          	auipc	ra,0x03e:	27e080e7          	jalr	638(ra) # 2b8 <exit>

3.回答问题

问题一

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

在A0-A7中保存函数的参数。由main函数可知寄存器a2保存了13

问题二

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

其实是没有这样的代码。 g(x) 被内联到 f(x) 中,然后 f(x) 又被进一步内联到 main() 中。所以看到的不是函数跳转,而是优化后的内联函数。

问题三

printf函数位于哪个地址?

其实,直接在 user/call.asm 代码中一直找,就能找到 printf 函数的地址。

也可以通过计算得到,首先将当前程序计数器的值赋给 ra 寄存器。auipc ra, 0x0,是指将当前立即数向右移动12位,然后加上 pc 寄存器的值,赋给 ra 寄存器,由于立即数为 0,因此 ra 的值即为 pc 的值。当前指令在0x30处,因此 pc = 0x30。1536(ra) 是指 1536 加上 ra 寄存器的值,1536 转为16进制再加上0x30 即为 0x0000000000000630。刚好是 printf 的地址。

问题四

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

jalr 指令会将 pc + 4 赋给当前寄存器,刚好是其下一条指令的地址。

问题五

运行下面的代码

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

输出是什么? 如果 RISC-V 是大端序的,要实现同样的效果,需要将 i 设置为什么?需要将 57616 修改为别的值吗?

57616=0xE110,0x00646c72小端存储为72-6c-64-00,对照ASCII码表

72:r 6c:l 64:d 00:充当字符串结尾标识

因此输出为:HE110 World

若为大端存储,i应改为0x726c6400,不需改变57616

问题六

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

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

原本需要两个参数,却只传入了一个,因此y=后面打印的结果取决于之前a2中保存的数据

二、 Backtrace

1.实验要求

回溯(Backtrace)通常对于调试很有用:它是一个存放于栈上用于指示错误发生位置的函数调用列表。

kernel/printf.c中实现名为backtrace()的函数。在sys_sleep中插入一个对此函数的调用,然后运行bttest,它将会调用sys_sleep。你的输出应该如下所示:

backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898

bttest退出qemu后。在你的终端:地址或许会稍有不同,但如果你运行addr2line -e kernel/kernel(或riscv64-unknown-elf-addr2line -e kernel/kernel),并将上面的地址剪切粘贴如下:

$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D

你应该看到类似下面的输出:

kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85

编译器向每一个栈帧中放置一个帧指针(frame pointer)保存调用者帧指针的地址。你的backtrace应当使用这些帧指针来遍历栈,并在每个栈帧中打印保存的返回地址。

2.提示

  • kernel/defs.h中添加backtrace的原型,那样你就能在sys_sleep中引用backtrace

  • GCC编译器将当前正在执行的函数的帧指针保存在s0寄存器,将下面的函数添加到kernel/riscv.h

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

 并在backtrace中调用此函数来读取当前的帧指针。这个函数使用内联汇编来读取s0

  • 这个课堂笔记中有张栈帧布局图。注意返回地址位于栈帧帧指针的固定偏移(-8)位置,并且保存的帧指针位于帧指针的固定偏移(-16)位置

  • XV6在内核中以页面对齐的地址为每个栈分配一个页面。你可以通过PGROUNDDOWN(fp)PGROUNDUP(fp)(参见kernel/riscv.h)来计算栈页面的顶部和底部地址。这些数字对于backtrace终止循环是有帮助的。

一旦你的backtrace能够运行,就在kernel/printf.cpanic中调用它,那样你就可以在panic发生时看到内核的backtrace

3.具体实现

(1)首先在,kernel/defs.h添加backtrace的声明

(2)由于我们需要先获取到当前函数的栈帧 fp 的值,该值存放在 s0 寄存器中,因此需要写一个能够读取 s0 寄存器值得函数。按照实验指导书给的方法,在 kernel/riscv.h 添加读取 s0 寄存器的函数:

(3)这个函数就是实现曾经调用函数地址的回溯,这个功能在日常的编程中也经常见到,编译器报错时就是类似的逻辑,只不过题目的要求较为简单,只用打印程序地址,而实际的报错中往往打印程序文件名,函数名以及行号等信息(最后的可选练习就是实现这样的功能)。

根据提示:返回地址位于栈帧帧指针的固定偏移(-8)位置,并且保存的帧指针位于帧指针的固定偏移(-16)位置。先使用r_fp()读取当前的帧指针,然后读出返回地址并打印,再将fp定位到前一个帧指针的位置继续读取即可。

根据提示:XV6在内核中以页面对齐的地址为每个栈分配一个页面。使用PGROUNDUP(fp) - PGROUNDDOWN(fp) == PGSIZE判断当前的fp是否被分配了一个页面来终止循环。

(4)在 kernel/printf.c 文件中的 panic 函数里添加 backtrace 的函数调用;在 sys_sleep 代码中也添加同样的函数调用。

4测试结果

在xv6代码库中执行make qemu,xv6中执行bttest,bttest调用sleep(system call)。依次调用trap.c、syscall.c、sysproc.c。

三、Alarm

 1.实验要求

在这个练习中你将向XV6添加一个特性,在进程使用CPU的时间内,XV6定期向进程发出警报。这对于那些希望限制CPU时间消耗的受计算限制的进程,或者对于那些计算的同时执行某些周期性操作的进程可能很有用。更普遍的来说,你将实现用户级中断/故障处理程序的一种初级形式。例如,你可以在应用程序中使用类似的一些东西处理页面故障。如果你的解决方案通过了alarmtestusertests就是正确的。

你应当添加一个新的sigalarm(interval, handler)系统调用,如果一个程序调用了sigalarm(n, fn),那么每当程序消耗了CPU时间达到n个“滴答”,内核应当使应用程序函数fn被调用。当fn返回时,应用应当在它离开的地方恢复执行。在XV6中,一个滴答是一段相当任意的时间单元,取决于硬件计时器生成中断的频率。如果一个程序调用了sigalarm(0, 0),系统应当停止生成周期性的报警调用。

你将在XV6的存储库中找到名为user/alarmtest.c的文件。将其添加到Makefile。注意:你必须添加了sigalarmsigreturn系统调用后才能正确编译(往下看)。

alarmtesttest0中调用了sigalarm(2, periodic)来要求内核每隔两个滴答强制调用periodic(),然后旋转一段时间。你可以在user/alarmtest.asm中看到alarmtest的汇编代码,这或许会便于调试。当alarmtest产生如下输出并且usertests也能正常运行时,你的方案就是正确的:

$ alarmtest
test0 start
........alarm!
test0 passed
test1 start
...alarm!
..alarm!
...alarm!
..alarm!
...alarm!
..alarm!
...alarm!
..alarm!
...alarm!
..alarm!
test1 passed
test2 start
................alarm!
test2 passed
$ usertests
...
ALL TESTS PASSED
$

​ 当你完成后,你的方案也许仅有几行代码,但如何正确运行是一个棘手的问题。我们将使用原始存储库中的alarmtest.c版本测试您的代码。你可以修改alarmtest.c来帮助调试,但是要确保原来的alarmtest显示所有的测试都通过了。

2.提示

这项练习要实现定期的警报。首先是要通过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中主要就是完成它的设置工作。

3.具体实现

(1)在 user/user.h 中添加声明:

(2)在 user/usys.pl 添加 entry,用于生成汇编代码:

(3)在 kernel/syscall.h 中添加函数调用码:

(4)在 kernel/syscall.c 添加函数调用代码:

(5)在Makefile增加

(6)在kernel/proc.h中增加字段

(7)在kernel/sysproc.c

(8)同时记得在kernel/proc.c/allocproc中将它们初始化为0,并在freeproc中也设为0

(9)修改usertrap()

(4)测试结果

四、test1/test2(): resume interrupted code(恢复被中断的代码)

 1.实验要求

alarmtest打印“alarm!”后,很可能会在test0test1中崩溃,或者alarmtest(最后)打印“test1 failed”,或者alarmtest未打印“test1 passed”就退出。要解决此问题,必须确保完成报警处理程序后返回到用户程序最初被计时器中断的指令执行。必须确保寄存器内容恢复到中断时的值,以便用户程序在报警后可以不受干扰地继续运行。最后,您应该在每次报警计数器关闭后“重新配置”它,以便周期性地调用处理程序。

作为一个起始点,我们为您做了一个设计决策:用户报警处理程序需要在完成后调用sigreturn系统调用。请查看alarmtest.c中的periodic作为示例。这意味着您可以将代码添加到usertrapsys_sigreturn中,这两个代码协同工作,以使用户进程在处理完警报后正确恢复。

2.提示

  • 您的解决方案将要求您保存和恢复寄存器——您需要保存和恢复哪些寄存器才能正确恢复中断的代码?(提示:会有很多)

  • 当计时器关闭时,让usertrapstruct proc中保存足够的状态,以使sigreturn可以正确返回中断的用户代码。

  • 防止对处理程序的重复调用——如果处理程序还没有返回,内核就不应该再次调用它。test2测试这个。

  • 一旦通过test0test1test2,就运行usertests以确保没有破坏内核的任何其他部分。

3.具体实现

接下来要通过test1test2,要解决的主要问题是寄存器保存恢复和防止重复执行的问题。考虑一下没有alarm时运行的大致过程

  1. 进入内核空间,保存用户寄存器到进程陷阱帧
  2. 陷阱处理过程
  3. 恢复用户寄存器,返回用户空间

而当添加了alarm后,变成了以下过程

  1. 进入内核空间,保存用户寄存器到进程陷阱帧
  2. 陷阱处理过程
  3. 恢复用户寄存器,返回用户空间,但此时返回的并不是进入陷阱时的程序地址,而是处理函数handler的地址,而handler可能会改变用户寄存器

因此我们要在usertrap中再次保存用户寄存器,当handler调用sigreturn时将其恢复,并且要防止在handler执行过程中重复调用,过程如下

(1)再在struct proc中新增两个字段

(2)在allocproc和freeproc中设定好相关分配,回收内存的代码

(3)更改kernel/trap.c/usertrap函数,保存进程陷阱帧p->trapframep->alarm_trapframe

(4)更改sys_sigreturn,恢复陷阱帧

4.测试结果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【多线程】单例模式

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 单例模式的初识2. 单例模式的含义3. 单例模式实现的两种方式3.1 饿汉模式3.2 懒汉模式3.2.1 懒汉模式(单线…

Redis的缓存雪崩,击穿,穿透的介绍

1.缓存雪崩 为保证缓存中的数据与数据库的数据一致,会给Redis里的数据设置一个过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成新的缓存,因为就会访问数据库,并将数据更新到Redis里,这样后续请求就可以直接命中缓存. 当大量缓存在同一时间过期或…

Nginx和Tomcat实现负载均衡群集部署应用

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作时间&…

减少CMOS模拟开关导通电阻引起的失真

1 简介 许多数据采集系统的在多通道间选择时需要使用模拟开关&#xff0c;相比同类的机械开关&#xff0c;半导体开关锁表现出的工作特性是迥然不同的。如&#xff1a;处在闭合位置的CMOS开关的电阻&#xff08;导通电阻 “Ron”&#xff09;会因输入电压的不同而改变。该特性通…

IMU的加速度补偿、祛除向心力

目录 1. 简介2. 仅有XY偏移的修正过程3. 3D修正过程 1. 简介 一般&#xff0c;我们期望用IMU测量某个Target坐标系的加速度、角速度信息&#xff0c;然而IMU的坐标系与Target 坐标系一般存在位姿关系&#xff0c;此时IMU测量的加速度不能直接代表Target左坐标系的加速度。比如…

python库(10):SpaCy库实现NLP处理

1 SpaCy简介 自然语言处理&#xff08;NLP&#xff09;是人工智能领域中一个重要的分支。它旨在使计算机能够理解、解释和生成人类语言。Python中的SpaCy库提供了丰富的功能和工具&#xff0c;SpaCy是一个开源的软件库&#xff0c;用于处理和操作自然语言文本&#xff0c;可以…

BM42:混合搜索的新基准 - Qdrant

在过去的 40 年里&#xff0c;BM25 一直是搜索引擎的标准。它是一种简单但功能强大的算法&#xff0c;已被许多搜索引擎使用&#xff0c;包括 Google、Bing 和 Yahoo。 虽然看起来向量搜索的出现会削弱其影响力&#xff0c;但效果并不明显。目前最先进的检索方法试图将 BM25 与…

python库(11):Box库简化字典和对象之间的转换

1Box库简介 Box是一个Python库&#xff0c;它提供了一种将数据封装在字典和列表中的方式&#xff0c;同时提供了一些额外的功能&#xff0c;比如数据验证、默认值设置等。这使得Box库非常适合用于配置管理、数据传输对象&#xff08;DTO&#xff09;的创建&#xff0c;以及任何…

sqlmap使用之-post注入、head注入(ua、cookie、referer)

1、post注入 1.1、方法一&#xff0c;通过保存数据包文件进行注入 bp抓包获取post数据 将数据保存到post.txt文件 加上-r指定数据文件 1.2、方法二、通过URL注入 D:\Python3.8.6\SQLmap>python sqlmap.py -u "http://localhost/login.php" --data "userna…

替换:show-overflow-tooltip=“true“ ,使用插槽tooltip,达到内容可复制

原生的show-overflow-tooltip“true” 不能满足条件&#xff0c;使用插槽自定义编辑&#xff1b; 旧code <el-table-column prop"reason" label"原因" align"center" :show-overflow-tooltip"true" /> <el-table-column pro…

压缩文件的解析方式

Java中我们用ZipInputStream和ZipOutputStream来完成对zip文件和rar文件的读写 I /O流&#xff1a; Input:输入&#xff0c;通过“输入流”进行文件的读取操作 output:输出&#xff0c;通过“输出流”进行文件的写入操作 一、将压缩包解压缩 1.解压缩.zip格式文件&#xf…

微信小程序毕业设计-汽车维修项目管理系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

NoSQL 之Redis集群模式

一&#xff1a;Redis集群方式 Redis有三种模式&#xff1a;分别是主从复制、哨兵模式、Cluster 1&#xff1a;主从模式: 主从复制是高可用Redis的基础&#xff0c;哨兵和群集都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的…

netscaler LDAP+RADIUS传统的双因素认证方式(之一)

如果使用传统的双因素认证方式&#xff0c;可以通过在Citrix ADC (NetScaler) 13.1上配置Gateway Virtual Server来实现LDAP和RADIUS的双因素认证。当前配置方式&#xff0c;采用Cateway vServer两个Basic Authtication Policy方式实现&#xff0c;以下是详细步骤&#xff1a; …

【码题集】习题

目录 史莱姆融合 松鼠接松果 新月轩就餐 史莱姆融合 根据题意就是一道集合合并的题&#xff0c;所以要用并查集&#xff0c;不过最后我们要输出整个序列&#xff0c;所以要在合并的时候维护一个链表&#xff0c;以便最终合并成一个大集合的时候&#xff0c;输出整个链表就是…

Kotlin Misk Web框架

Kotlin Misk Web框架 1 添加依赖1.1 build.gradle.kts1.2 settings.gradle.kts1.3 gradle.properties 2 请求接口3 程序模块4 主服务类5 测试结果 Misk 是由 Square 公司开发的一个开源的多语言服务器框架&#xff0c;主要用于构建微服务。它主要使用 Kotlin 语言&#xff0c;但…

UGC与AI引领的下一个10年,丝芭传媒已经准备好

丝芭传媒最近传来的消息&#xff0c;都跟技术相关。 基于自研AI大模型“Paro&#xff08;心乐舞河&#xff09;”的AIGPT及AIGC生成工具APP“鹦鹉人”开启用户内测。2023年3月技术测试的图形化智能社交基座“美踏元宇宙”&#xff0c;也将开放首轮用户内测。 此外&#xff0c…

Studying-代码随想录训练营day31| 56.合并区间、738.单调递增的数字、968.监控二叉树、贪心算法总结

第31天&#xff0c;贪心最后一节(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 56.合并区间 738.单调递增的数字 968.监控二叉树 贪心算法总结 56.合并区间 文档讲解&#xff1a;代码随想录合并区间 视频讲解&#xff1a;手撕合并区间 题目&#xf…

高效图纸管理:彩虹图纸管理软件助您一臂之力

高效图纸管理&#xff1a;彩虹图纸管理软件助您一臂之力 在制造业的激烈竞争中&#xff0c;高效图纸管理是企业提升竞争力和降低成本的关键。然而&#xff0c;传统的图纸管理方式往往存在效率低下、信息混乱等问题。此时&#xff0c;彩虹图纸管理软件凭借其卓越的性能和丰富的功…

一个vue页面复用方案

前言 问大家一个问题&#xff0c;曾经的你是否也遇到过&#xff0c;一个项目中有好几个页面长得基本相同&#xff0c;但又差那么一点&#xff0c;想用 vue extends 继承它又不能按需继承html模板部分&#xff0c;恰好 B 页面需要用的 A 页面 80% 的模板&#xff0c;剩下的 20%…