前言

第三章内容是关于使用系统陷入来实现系统调用的。

实验使用win10 + wsl2 Ubuntu 20.04完成了前两个实验。

内容总览

本实验有三个内容。

包括:

  • RISC-V assembly: 理解汇编代码。
  • Backtrace: 打印堆栈。
  • Alarm: 定期调用用户级回调函数。

reference

  • ref1 :第四章实验要求
  • ref2: 知乎
  • ref3: 在线进制转换器

十分感谢以上大佬开源的贡献。知识参考:课程内容翻译 github

内容1:RISC-V assembly

在您的 xv6 存储库中有一个文件 user/call.c。 make fs.img 对其进行编译,并在 user/call.asm 中生成程序的可读汇编版本。

阅读 call.asm 中函数 g、f 和 main 的代码。

这部分没有需要写的代码,要做的就是理解c代码里面的函数以及对照的汇编代码。

call.c

/*函数g
*/
//c代码
int g(int x)
{return x + 3;
}//汇编
int g(int x) {0:    1141                    addi    sp,sp,-162: e422                    sd  s0,8(sp)4:  0800                    addi    s0,sp,16return x+3;
}6: 250d                    addiw   a0,a0,38:   6422                    ld  s0,8(sp)a:  0141                    addi    sp,sp,16c:  8082                    ret/*函数f
*/
//c代码
int f(int x)
{return g(x);
}
//汇编
000000000000000e <f>:int f(int x) {e: 1141                    addi    sp,sp,-1610:    e422                    sd  s0,8(sp)12: 0800                    addi    s0,sp,16return g(x);
}14:    250d                    addiw   a0,a0,316:  6422                    ld  s0,8(sp)18: 0141                    addi    sp,sp,161a: 8082                    ret/*main函数
*/
//c代码
void main(void)
{printf("%d %d\n", f(8) + 1, 13);exit(0);
}//汇编代码
000000000000001c <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>
  1. 哪些寄存器包含函数的参数? 例如,在 main 对 printf 的调用中,哪个寄存器保存了 13?

    • 为了回答这个问题,我在call.c里面添加了三行代码。观察可以知道,参数通常会存在寄存器a当中…

    call.c

      // my test 1printf("x=%d y=%d", 3);// my test 2printf("x=%d y=%d", 3, 6);// my test 3printf("x=%d y=%d", 3, 6, 9);/*以下是对应的汇编代码
    */// my test 1printf("x=%d y=%d", 3);38:    458d                    li  a1,33a: 00000517            auipc   a0,0x03e:   7de50513            addi    a0,a0,2014 # 818 <malloc+0xee>42:    00000097            auipc   ra,0x046:   62a080e7            jalr    1578(ra) # 66c <printf>// my test 2printf("x=%d y=%d", 3, 6);4a:  4619                    li  a2,64c: 458d                    li  a1,34e: 00000517            auipc   a0,0x052:   7ca50513            addi    a0,a0,1994 # 818 <malloc+0xee>56:    00000097            auipc   ra,0x05a:   616080e7            jalr    1558(ra) # 66c <printf>// my test 3printf("x=%d y=%d", 3, 6, 9);5e:   46a5                    li  a3,960: 4619                    li  a2,662: 458d                    li  a1,364: 00000517            auipc   a0,0x068:   7b450513            addi    a0,a0,1972 # 818 <malloc+0xee>6c:    00000097            auipc   ra,0x070:   600080e7            jalr    1536(ra) # 66c <printf>
    
    • 24: 4635 li a2,13 知道13被存在了寄存器a2上。
  2. main 的汇编代码中对函数 f 的调用在哪里? 对 g 的调用在哪里? (提示:编译器可能内联函数。)

    • 无,直接被内联了
  3. 函数 printf 位于哪个地址?

可以从call.asm中直接找到0000000000000630 <printf>:.

也可以通过计算

  2c:    7b050513            addi    a0,a0,1968 # 7d8 <malloc+0xea>30:    00000097            auipc   ra,0x034:   600080e7            jalr    1536(ra) # 630 <printf>
  • auipc:是指将当前立即数向右移动12位,然后加上 pc 寄存器的值,赋给 ra 寄存器。pc = 0x30,立即数是0x0,因此ra = 0x30
  • jalr:函数跳转。JALR指令格式为 jalr rs rd,无条件跳转到由寄存器rs指定的指令,**并将下一条指令的地址保存到寄存器ra。**所以目标地址printf为1536(ra),即0x600 + 0x30 = 0x630
  1. 在 jalr 到 main 中的 printf 之后,寄存器 ra 中有什么值?

    • 在第三点已经解释了jalr的作用了,下一条指令的地址是0x38
  2. 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.

这题有点意思。

  • 输出是He110 World.

    • %x表示输出十六进制,而57616的十六进制就是e110
    • %s表示输出字符串,当是小端序表示的时候,内存中存放的数是:72 6c 64 00,刚好对应rld。当是大端序的时候,则反过来了,因此需要将 i 以16进制数的方式逆转一下。
  1. 在下面的代码中,'y='之后会打印什么? (注意:答案不是特定值。)为什么会发生这种情况?

看第一个问题的test1,它打印一个参数,参数被加载到了a1寄存器当中;看第一个问题的test2,它打印两个参数,参数被加载到了a1、a2寄存器当中;所以这题答案就是,a2有什么就打印什么。

内容2:Backtrace

在debug的时候,如果发生错误了,打印函数调用的堆栈信息是相当有用的。

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

backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898

执行bttest 后退出 qemu。 在您的终端中:地址可能略有不同,但如果您运行 addr2line -e kernel/kernel并剪切并粘贴上述地址,如下所示:

   $ addr2line -e kernel/kernel0x0000000080002de20x0000000080002f4a0x0000000080002bfcCtrl-DYou should see something like this:kernel/sysproc.c:74kernel/syscall.c:224kernel/trap.c:85

最重要的理解:The compiler puts in each stack frame a frame pointer that holds the address of the caller’s frame pointer. Your backtrace should use these frame pointers to walk up the stack and print the saved return address in each stack frame.
意思是:编译器在每个堆栈帧中放入一个帧指针,该指针保存调用者帧指针的地址。 brcktrace函数应该使用这些帧指针去向上遍历堆栈,并在每个堆栈帧中打印被保存的返回地址

提示和理解

  1. traceback()的函数原型添加到 kernel/defs.h,以便您可以在sys_sleep 中调用traceback()
  2. The GCC compiler stores the frame pointer of the currently executing function in the register s0. Add the following function to kernel/riscv.h:
static inline uint64
r_fp()
{uint64 x;asm volatile("mv %0, s0" : "=r" (x) );return x;
}

并在traceback()中调用此函数以读取当前帧指针。 此函数使用内联汇编来读取 s0。

  1. 请注意,返回地址(return address)位于距堆栈帧的帧指针(fp)的固定偏移量 (-8) 处,而保存的帧指针(saved frame pointer)位于距帧指针的固定偏移量 (-16) 处。如下图所示,fp-16指向上一个栈帧,可以用来遍历。

  1. xv6 为 xv6 内核中的每个堆栈在 PAGE 对齐地址处分配一个页面。 您可以使用PGROUNDDOWN(fp)PGROUNDUP(fp) 计算堆栈页面的顶部和底部地址(请参阅 kernel/riscv.h。这些数字有助于回溯终止其循环。
  2. 一旦你的回溯工作正常,从 kernel/printf.c 中的 panic 调用它,这样你就可以看到 kernelpanic 时的回溯。

做法和代码

参考ref2,可以轻松通过,也可以相像提示说的,使用 addr2line -e kernel/kernel打印所在的函数行数

核心代码

void backtrace(void) {uint64 fp = r_fp(), top = PGROUNDUP(fp);printf("backtrace:\n");//fp-16就是上一个栈帧,//fp-8就是当前的返回地址。for(; fp < top; fp = *((uint64*)(fp-16))) {printf("%p\n", *((uint64*)(fp-8)));}
}

内容3:Alarm

这个实验好长!

在本练习中,您将向 xv6 添加一个功能,该功能会在进程使用 CPU 时间时定期提醒它
这对于想要限制进程占用CPU的时间,或者对于想要采取一些定期操作的进程可能很有用。 更一般地说,您将实现一种原始形式的用户级中断/故障处理程序; 例如,您可以使用类似的东西来处理应用程序中的页面错误。

如果它通过了alarmtestusertests,则答案正确。

您应该添加一个新的 sigalarm(interval, handler) 系统调用。 如果应用程序调用 sigalarm(n, fn),那么在程序消耗的每 n 个 CPU 时间ticks之后,内核应该调用应用程序函数 fn。 当 fn 返回时,应用程序应该从中断的地方继续。 在 xv6 中,tick 是一个相当任意的时间单位,由硬件定时器产生中断的频率决定。 如果应用程序调用 sigalarm(0, 0),内核应停止生成定期警报调用。

您将在 xv6 存储库中找到文件user/alarmtest.c。 将其添加到 Makefile。 在您添加 sigalarmsigreturn 系统调用之前,它不会正确编译(见下文)。

alarmtesttest0 中调用 sigalarm(2,periodic) 以要求内核每 2 个滴答声强制调用一次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 表明所有测试都通过了。

test0: invoke handler

这里是整个实验的第一步,修改内核以跳转到用户空间的警报处理程序handler开始。尽管跳转完系统程序会崩溃,但是无伤大雅,只有把test1完成了才会全部完成。

下面是提示

  1. 您需要修改 Makefile 以使 alarmtest.c 编译为 xv6 用户程序。
  2. 放入 user/user.h 的正确声明是:
    int sigalarm(int ticks, void (*handler)());int sigreturn(void);
  1. 更新 user/usys.pl(生成 user/usys.S)、kernel/syscall.hkernel/syscall.c 以允许 alarmtest 调用 sigalarmsigreturn 系统调用。
  2. 现在,您的 sys_sigreturn 应该只返回零。
  3. 您的 sys_sigalarm() 应该将警报间隔指向处理函数的指针存储在 proc 结构(在 kernel/proc.h 中)的新字段中。
  4. 您需要跟踪自上次调用(或留到下一次调用)到进程的警报处理程序以来已经过去了多少tricks; 为此,您还需要 struct proc 中的一个新字段。 您可以在 proc.c 中的 allocproc() 中初始化 proc 字段。
  5. 每个tick,硬件时钟都会强制中断,该中断在 kernel/trap.c 中的 usertrap() 中处理。
  6. 如果有计时器中断,您只想操纵进程的警报滴答声; 你会用到下面的代码
  if(which_dev == 2) ...
  1. 仅当进程有未完成的计时器时才调用警报函数。 请注意,用户的警报函数的地址可能为 0(例如,在 user/alarmtest.asm 中,周期性在地址 0 处)。

  2. 您需要修改 usertrap() 以便在进程的警报间隔到期时,用户进程执行处理函数。 当 RISC-V 上的陷阱返回到用户空间时,是什么决定了用户空间代码恢复执行的指令地址?

  3. 如果您告诉 qemu 只使用一个 CPU,那么使用 gdb 查看陷阱会更容易,您可以通过运行make CPUS=1 qemu-gdb

  4. 如果alarmtest 打印出**“alarm!”**,你就成功了。

后话

关于为什么暂停了实验,主要有两个原因:

  1. 实验内容不足够有趣,在我当前的理解基础上,有些枯燥,或者说我还get不到很有用的点,这一点CSAPP是给足了我惊喜,而6.S081却没有这种感觉。
  2. 五月份开始后要忙更多学校的项目、课程项目。

有点可惜,后面会开始新的论文研究以及关于【Unix环境高级环境编程】的学习,加油!

【MIT 6.S081】实验四:traps (实验暂停)相关推荐

  1. 计算机图形学——实验四 纹理映射实验

    实验四 纹理映射实验 实验项目性质:设计性实验 所属课程名称:计算机图形学A 实验计划学时:3学时 一.实验目的和要求 掌握纹理映射的基本原理,利用VC++ OpenGL实现纹理映射技术. 二.实验原 ...

  2. 201671010412 郭佳 实验四附加实验

    实验四附加实验 问题编号 问题描述 这个作业属于哪个课程 软件工程任教教师 实验四附加实验 项目互评 点评1博客地址 201671010445杨爱婷<英文文本统计分析>结对项目报告 点评2 ...

  3. 实验四 VLAN 实验——实现同一个vlan组下PC互通,不同vlan组下的不能互通

    实验四 VLAN 实验 一.实验内容:华为系列交换机 VLAN 配置方法 二.实验目的:掌握 Huawei 系列中低端交换机端口的 VLAN 配置方法 三.实验设备:Huawei交换机,PC 机,Co ...

  4. 软件工程实训有必要吗_软件工程实验(四个实验)

    <软件工程实验(四个实验)>由会员分享,可在线阅读,更多相关<软件工程实验(四个实验)(21页珍藏版)>请在人人文库网上搜索. 1.武汉轻工大学软件工程实验报告院系: 数学和计 ...

  5. C#面向对象程序设计课程实验四:实验名称:C#面向对象程序设计基础

    C#面向对象程序设计课程实验四:实验名称:C#面向对象程序设计基础 实验内容:C#面向对象程序设计基础 一.实验目的 二.实验环境 三.实验内容与步骤 3.1.1.实验内容 3.1.2.实验步骤 3. ...

  6. 201671010434王雯涵 实验四附加实验:项目互评

    我的评价对象: 1.201671010445杨爱婷<英文文本统计分析>结对项目报告 2.201671010452 周海瑞 <英文文本统计分析>结对项目报告 1.实验内容和步骤 ...

  7. oracle实验四运动会,实验四oracle的安全性和完整性控制

    实验四 oracle的安全性和完整性控制 实验目的: 1.通过本实验能够熟练应用sql语言进行用户权限的授予和回收. 2.熟练掌握实体完整性,参照完整性及用户定义的完整性的定义. 3.并体会oracl ...

  8. 计算机进程管理与虚拟机实验答案,实验四虚拟机实验报告解读.doc

    电子科技大学 信 息 网 络 技 术 实 验 报 告 政治与公共管理学院 2016-03-17 实验名称 虚拟机上安装Linux系统并调试实验 实验编号 004 姓名 罗佳 学号 2014120101 ...

  9. 实验四 串行通信实验

    电信19-2 翁大弟 一.实验目的 熟练掌握Keil和Proteus软件的C51设计与仿真操作 锻炼算法设计能力 熟练掌握定时计数器.外部中断的编程设计 掌握串行通信编程方法 三.实验实现的功能说明 ...

  10. 201671010403 陈倩倩 实验四附加实验

    实验内容和步骤: 按名单中指定互评小组,对对方小组的<实验四 结对项目>的项目成果进行评价,具体要求如下: (1) 对项目博文作业进行阅读并进行评论,评论要点包括:博文结构.博文内容.博文 ...

最新文章

  1. (广搜)Dungeon Master -- poj -- 2251
  2. 【笔记】二叉树递归算法和非递归算法的实现 先序/中序/后续遍历 打印结点以及顺序数 构造二叉树
  3. 9.1 ps:查看进程
  4. 罗马数字转整数(C实现)
  5. 误码率越高越好还是越低越好_夜间护理步骤越多越好还是越少越好?NFF
  6. 媒体播放器三大底层架构
  7. java成员内部类_Java中的内部类(二)成员内部类
  8. 从Client应用场景介绍IdentityServer4(二)
  9. 机器学习实战 k-近邻算法 约会网站
  10. Python编曲实践(十):用Ableton Live 10手工扒的Grunge摇滚数据集,涵盖Grunge时期四大代表乐队的经典专辑
  11. modbus-tcp协议通过Java代码获取从机数据
  12. 手机号码检测开通微信查询方法
  13. SAP根据采购申请,采购入库
  14. C++builder应用程序设计流程
  15. 思科视频会议系统+服务器,Cisco思科MCU5310视频会议系统服务器
  16. 计算机功能自定义,设计大师学教学:自定义鼠标右键功能提升CAD绘图效率-鼠标右键菜单设置...
  17. Foobar 2000 EIKO 增强版 取消“最小化到托盘”设置
  18. 1998年图灵奖--詹姆斯·格雷简介
  19. 后端 学习 前端 Vue 框架基础知识
  20. 错题本——Python

热门文章

  1. 一、思科模拟器教程了解软件
  2. php采集规则编写,织梦dedecms图片采集规则的编写方法
  3. 提供博客里提到的几个程序的下载地址
  4. 清华大学android源码下载网站地址
  5. fastboot usb 驱动相关
  6. fckeditor for java_配置FCKeditor(FCKeditor for java)
  7. 怎样备份计算机里的驱动程序,怎么备份电脑的驱动 驱动人生备份驱动方法
  8. 全国高等学校非计算机专业计算机水平考试一级,全国高等学校计算机一级考试选择题题库及答案参考...
  9. LabVIEW Arduino RS-485智能农业监测系统(项目篇—4)
  10. 速达服务器远程登录设置,ERP速达软件online远程客户端登录常见问题(三)