6.s081 学习实验记录(五)traps

news2024/12/24 9:33:33

文章目录

    • 一、RISC-V assembly
      • 简介
      • 问题
    • 二、Backtrace
      • 简介
      • 注意
      • 实验代码
      • 实验结果
    • 三、Alarm
      • 简介
      • 注意
      • 实验代码
      • 实验结果

一、RISC-V assembly

简介

  • git checkout traps,切换到traps分支
  • user/call.c 文件在我们输入 make fs.img 之后会被汇编为 call.asm 文件,阅读该汇编文件中的函数:f、h、main
  • call.c 中的代码比较简单:
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int g(int x) {
  return x+3;
}

int f(int x) {
  return g(x);
}

void main(void) {
  printf("%d %d\n", f(8)+1, 13);
  exit(0);
}

问题

我们需要回答以下问题:

  • 哪些寄存器用于传参? 例如,main 函数调用 printf 时持有13 这个参数

    RISC-V 寄存器作用如下图:
    通过main函数的汇编代码,可以知道参数 13 使用 a2 寄存器传递
    在这里插入图片描述
  • main函数对应的汇编代码在哪里调用了函数f ? 函数 g 的调用在哪里?(编译器可能会优化某些函数为内联函数)
    由问题一查看的汇编可知,main函数并未调用函数 f,而是直接由编译器优化得到了结果
  • printf 函数的地址是什么?
    printf 的地址为 0x64a(虚拟地址),main 函数中的调用也能说明这一点
    在这里插入图片描述
    在这里插入图片描述
  • main 函数中使用 jalrprintf 之后,ra寄存器的值是什么?
    jalr 指令执行时,会将 pc + 4 的值存放到 ra 寄存器中作为返回地址,因此ra的值为 0x38
  • 运行下面的代码,输出是什么?(输出取决于大小端,RISC-V为小端模式,如果是大端,相同输出需要将 57616 改为多少?)
    • unsigned int i = 0x00646c72;
    • printf(“H%x Wo%s”, 57616, &i);
    • 输出为:He110 World
    • 因为,57616对应16进制的 0xe110,0x00646c72 以小端存储,实际为 0x6c720064,0x6c对应r0x72对应 l0x64对应d00代表NULL,不显示)。如果是大端,则高地址在高处,因此应该存储为 0x6c720064 或者 0x6c7264
  • 下面的代码,y=后面的输出是什么?为什么?
    • printf(“x=%d y=%d”, 3);
    • 输出为:乱码,因为参数传递时使用的寄存器是确定的,因此如果第二个参数不传递,依然还是会用a2寄存器传递,此时a2中的值是caller中的残留值

二、Backtrace

简介

当发生错误时,打印当前的调用栈。每个函数栈帧包含一个返回地址以及一个指向caller栈帧frame pointer(我理解就是caller的rsp)

kernel/printf.c 中实现一个 backtrace(),在 sys_sleep 中插入对该函数的调用,然后运行 bttest,该用户程序会调用 sleep 系统调用,此时应该打印堆栈

bttestqemu中执行完毕之后,新开一个窗口执行 addr2line -e kernel/kernel (or riscv64-unknown-elf-addr2line -e kernel/kernel),将有如下输出:
在这里插入图片描述
阅读:kernel/sysproc.c:74、kernel/syscall.c:224、kernel/trap.c:85

注意

  • kernel/defs.h 中添加 backtrace() 函数的声明
  • gcc产生汇编时,会将栈指针存放在 s0 寄存器,在kernel/riscv.h 中添加如下代码:
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) ); //含义为读取 s0 寄存器中的值,赋值给 x 并返回 x
  return x;
}
  • 返回地址位于栈指针 - 8的位置,保存的栈指(caller的栈指针地址)针位于当前函数的栈指针-16处(栈指针即 rbp
  • 你需要识别最后一个栈帧并结束,由于函数栈最大为4KB,因此可以使用 PGROUNDDOWN(fp) (see kernel/riscv.h) 来判断是否到达最后一个函数栈

实验代码

思路:
1、首先 kernel/riscv.h 添加要求的代码,kernel/defs.h 声明 backtrace()
2、 backtrace() 的逻辑为:使用 r_fq() 获取当前栈指针,打印栈指针 - 8 的地址(即调用栈),并 -16,得到上一个栈帧的地址,依次类推,直到地址等于 PGROUNDDOWN(fp) (see kernel/riscv.h)

  • kernel/riscv.h
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) ); //含义为读取 s0 寄存器中的值,赋值给 x 并返回 x
  return x;
}
  • kernel/defs.h
void            backtrace(void);
  • kernel/printf.c
void backtrace(void){
  // 获取当前栈帧
  uint64 addr = r_fp();
  // 向上遍历
  while(addr != PGROUNDDOWN(addr)){
    printf("%p\n", *(uint64 *)(addr - 8));
    addr = *(uint64 *)(addr - 16);
  }
}
  • kernel/sysproc.c
uint64
sys_sleep(void)
{
  int n;
  uint ticks0;

  argint(0, &n);
  //...其他代码
  backtrace();
  return 0;
}

实验结果

在这里插入图片描述

在这里插入图片描述

三、Alarm

简介

添加一项新的功能,定时报警,即简单的用户级中断/故障处理程序。为以后缺页中断打下基础。

添加一个新的系统调用 sigalarm(interval, handler),如果用户态应用调用 sigalarm(n, fn),那么每当该程序消耗 ncpu ticks,内核会调用一次 fn 。当调用 sigalarm(0, 0) 时停止。

注意

  • makefile 中添加 user/alarmtest.c
  • 正确的结果输出如下:
    在这里插入图片描述

实验代码

思路:proc中添加三个字段,分别记录用户设置的tickscur_ticks以及callback,系统调用则是设置进程的 ticks 以及callback,在处理时钟中断时,给当前的进程cur_ticks 加1,判断和ticks是否相等,相等则调用callback

时钟中断的处理函数为 clockintr(),当用户态进程运行时来了时钟中断,调用路径为:usertrap---判断类型---devintr

详细流程:参考 xv6-book 第四章

trap发生时, cpu 硬件会做一些准备 (book:44页),该步骤设置了SIE位, 保存 pc,设置trap原因,进入内核态(但是不切换内核页表以及内核栈),执行 trap 处理函数(stvec寄存器保存的地址)

如果是在用户态发生 trap,此时处理函数为 uservec,该函数保存了用户寄存器以及其他状态,切换内核页表(所以trap发生时,硬件只保证切换到内核态,即cpu设置为管理模式,但是并不会切换页表,切换页表是在trap处理函数中做的),最后跳转到 usertrap()执行。

因此,我们需要在 usertrap 函数中添加对于时钟中断的判断逻辑以及用户callback的调用逻辑(需要保证不可重入,即我们如果在执行 callback 的时候再次被时钟中断trap,此时不应该再次执行callback,所以还需要在proc.h中添加一个字段用于防止重入)。usertrap中本身有对于时钟中断的判断逻辑,即进程调度的部分,具体代码如图(所以照猫画虎即可):
在这里插入图片描述
最后还有一点需要注意,我们的callback函数是应该在用户态运行的(因为内核态无法解析用户态的虚拟地址,所以无法调用callback),此时我们处于内核态,因此我们需要保存上下文切换到用户态执行完callback之后切换回内核态恢复上下文继续usertrap的执行。

如何切换到内核态呢?此时我们借助 usertrapret() 来返回用户态,所以我们需要更改trap的返回地址,当前 trap 的返回地址p->trapframe->epc += 4;(kernel/trap.c:61)。所以,我们首先保存一下当前 trapframe,然后更改 trapframe->epc = callback,这样在 usertrapret() 返回用户态后执行的就是 callback 函数,此时需要注意,callback函数是不能有参数的(因为这不是正常的函数调用)

callback执行成功了,但是我们该如何返回到用户态代码原先的地方执行呢? 因此,实验测试代码中提供了另外一个系统调用 sigreturn(),来让我们重新回到内核态,恢复进入 trap 时的用户上下文,在这一次的 usertrapret() 返回到用户进程原先执行的地方。(xv6 设计的中断、系统调用走相同的调用路径,因此这里可以执行这样的骚操作。)

总体流程:

  • 用户态程序
  • 用户态程序 —> 时钟中断 --> usertrap() (p->trapframe->epc 保存用户态程序的下一条执行语句) —> 保存trapframe 到另外一个地方(假设为A) —> 更改 p->trapframe = callback —> usertrapret() 返回用户态执行callback —> 通过sigreturn() 重新进入trap流程 —> 此时走系统调用路径 ----> sys_sigreturn() 恢复 p->trapframe 为最开始保存的(从A处读取) —>usertrapret() 返回用户态trap中断处继续执行
  • 一次 alarm trap 需要往返内核态两次

代码:

  • kernel/proc.h
// Per-process state
struct proc {
  struct spinlock lock;
  //新增字段
  int ticks; //回调执行所需要ticks
  int cur_ticks; //当前进程的ticks
  void* callback; //回调函数
  struct trapframe backup; //保存trapframe
  int execing; //是否已经在执行了
  • makefile
UPROGS=\
	$U/_alarmtest\
  • user/user.h
int sigalarm(int ticks, void (*callback)());
int sigreturn(void);
  • user/usys.pl
entry("sigalarm");
entry("sigreturn");
  • kernel/syscall.h
#define SYS_sigalarm  22
#define SYS_sigreturn  23
  • kernel/syscall.c
extern uint64 sys_mkdir(void);
extern uint64 sys_close(void);
//新增
extern uint64 sys_sigalarm(void);
extern uint64 sys_sigreturn(void);

static uint64 (*syscalls[])(void) = {
//其他
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
// 新增
[SYS_sigalarm]   sys_sigalarm,
[SYS_sigreturn]   sys_sigreturn,
};
  • kernel/sysproc.c
uint64
sys_sigalarm(void){
  int ticks = 0;
  uint64 callback = 0;
  argint(0,&ticks);
  argaddr(1, &callback);

  struct proc* proc = myproc();
  proc->ticks = ticks;
  proc->callback = (void *)callback;
  proc->execing = 0;
  return 0;
}

uint64
sys_sigreturn(void){
  struct proc* proc = myproc();
  *(proc->trapframe) = proc->backup;
  proc->execing = 0;
  // 由于该函数返回后,返回值保存在a0中,会覆盖trapframe中原来的a0
  // 该函数返回之后就会走 usertrapret() 返回到用户态
  return proc->trapframe->a0;
}
  • kernel/trap.c
  if(killed(p))
    exit(-1);
  // 新增代码 
  if(which_dev == 2){
    struct proc* proc = myproc();
    // 设置了ticks 且没有执行
    if(proc->ticks > 0 && proc->execing == 0){
      proc->cur_ticks++;
      if(proc->cur_ticks == proc->ticks){
        proc->execing = 1;
        proc->backup = *(proc->trapframe); //保存trapframe
        proc->trapframe->epc = (uint64)proc->callback;
        proc->cur_ticks = 0;
      }
    }
  }
  // 新增结束
  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

实验结果

  • make qemu
    在这里插入图片描述
  • ./grade-lab-traps running alarmtest
    在这里插入图片描述

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

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

相关文章

Mysql架构系列——生产常用的高可用部署模式介绍

模式 高可用模式 Galera Cluster是由Codership开发的MySQL多主集群,包含在MariaDB中,同时支持Percona xtradb、MySQL,是一个易于使用的高可用解决方案,在数据完整性、可扩展性及高性能方面都有可接受的表现。 将会基于Galera C…

SpringBoot注解--04--01--注解@Mapper在IDEA中自动注入警告的解决方案

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 问题原因 解决方案方法1:为 Autowired 注解设置required false方法2:用 Resource 替换 Autowired方法3:在Mapper接口上加上Repo…

elastic-job VS xxl-job

1、Elastic-job介绍 Elastic-job 是由当当网基于quartz 二次开发之后的分布式调度解决方案 , 由两个相对独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成 。Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务…

页面单跳转换率统计案例分析

需求说明 页面单跳转化率 计算页面单跳转化率,什么是页面单跳转换率,比如一个用户在一次 Session 过程中访问的页面路径 3,5,7,9,10,21,那么页面 3 跳到页面 5 叫一次单跳,7-9 也叫一次单跳, 那么单跳转化率就是要统计…

贪心算法篇2

“星辰野草&#xff0c;造出无边的天地~” 最⻓递增⼦序列 (1) 题目解析 (2) 算法原理 class Solution { public:int lengthOfLIS(vector<int>& nums) {// 使用dp int n nums.size(), ret 1;// 初始化为1vector<int> dp(n1,1);// 从第二个位置…

不需英文基础也可以轻松学编程,中文编程开发工具免费版下载,编程工具构件箱之扩展控制面板构件用法

不需英文基础也可以轻松学编程&#xff0c;中文编程开发工具免费版下载&#xff0c;编程工具构件箱之扩展控制面板构件用法 一、前言 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载——常…

ECharts 图表嵌入表格样式的demo

心累。。。 如果条件允许&#xff0c;还是强烈建议 用 Echartshtml 来实现&#xff08;表格部分由 html 来弄&#xff09;。 这里是调研阶段&#xff0c;想看看 ECharts 原生能做到什么程度。 先贴上样图&#xff1a; 贴上完整代码&#xff1a; <!DOCTYPE html> <…

Mysql索引优化建议

1&#xff0c;最左前缀法则 如果为一张表创建了多列的组合索引&#xff0c;要遵守最左前缀法则。就是指查询从索引的最左前列开始并且不要跳过索引中的列。&#xff08;因为Mysql的InnoDB引擎的索引树是一个按顺利排序存储的数据结构&#xff08;BTREE&#xff09;&#xff0c…

SQLserver2008 r2 下载安装配置、使用、新建登录用户及通过Navicat远程连接

目录 一、下载 二、安装配置 1.安装 2.许可条款 3.安装程序支持文件 4.功能选择 5.实例配置 6.服务器配置 7.数据库引擎配置 8.Reporting Services 配置 9.安装进度 ​编辑 10.完成 三、使用 四、新建登录用户 1.新建登录名 2.常规 3.服务器角色 4. 用户映…

[鹤城杯 2021]easy_crypto

下载一看发现是核心价值观编码 核心价值观编码

Seata下载与配置、启动

目录 Seata下载Seata配置启动Seata Seata下载 首先&#xff0c;我们需要知道我们要使用哪个版本的seata&#xff0c;这就要查看spring-cloud-alibaba版本说明&#xff0c;找到我们对应的seata。 spring-cloud-alibaba版本说明: 地址链接 下面是部分版本说明&#xff1a; s…

【go】延迟执行和定时器实现

目录 time.Sleep time.After time.NewTimer time.NewTicker time.Sleep time.Sleep可以实现延时执行 func TestSleep(t *testing.T) {fmt.Println("start time:", time.Now().Format("2006-01-02 15:04:05.000"))time.Sleep(2 * time.Second)fmt.Pri…

蓝桥杯每日一题----第k个数

题目&#xff1a;第k个数 主要为了学习数字的构造方法&#xff0c;如何快速求1~n中前缀为pre的数字的个数。 题目分析 一开始想的是把数字转化为字符串&#xff0c;然后丢给sort排序就行了&#xff0c;但是n太大了&#xff0c;会出现溢出问题。走到这里也从侧面反映了对所有…

DBeaver添加阿里maven镜像

1、点击数据库->驱动管理器 2、选择任意数据库&#xff0c;点击编辑按钮 3、点击下载/更新(D) 4、点击下载配置 5、点击添加 6、添加阿里云地址 http://maven.aliyun.com/nexus/content/groups/public/ 7、将阿里云地址移动到首位并点击"应用并关闭"

【亿级数据专题】「高并发架构」盘点本年度探索对外服务的百万请求量的API网关设计实现

盘点本年度探索对外服务的百万请求量的API网关设计实现 背景介绍高性能API网关API网关架构优化多级缓存架构设计多级缓存富客户端漏斗模型数据读取架构 异步刷新过期缓存网关异步化调用模型高性能批量API调用&#xff08;减少对于网关的交互和通信&#xff09;并行调用和请求合…

使用PHPStudy搭建本地web网站并实现任意浏览器公网访问

文章目录 [toc]使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点&#xff0c;测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中&#xff0c;查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2…

跟着pink老师前端入门教程-day18

3、CSS3 3D转换 生活中的环境是3D的&#xff0c;照片就是3D物体在2D平面呈现的例子 特点&#xff1a;近大远小&#xff0c;物体后面遮挡不可见 3.1 三维坐标系 三维坐标系其实就是指立体空间&#xff0c;立体空间是由3个轴共同组成的 x轴&#xff1a;水平向右 注意&#x…

【字符串】字典树

字典树就是利用一个这样的树状结构&#xff0c;可以记录字符串有没有出现过 放个板子 int nxt[100000][26], cnt; bool st[100000]; // 该结点结尾的字符串是否存在 void insert(string s, int l) // 插入字符串&#xff0c;l是字符串长度 { int p 0;for (int i 0; i < …

QT Linux下无法使用CTRL+ALT+P快捷键,不生效

文章目录 一、背景二、排查&#xff08;1&#xff09;检查创建&#xff0c;发现没问题。&#xff08;2&#xff09;查看 shortcutMap 是否注册&#xff08;3&#xff09;排查xcb有没有获取到该事件&#xff08;4&#xff09;排查是否是系统的问题&#xff08;5&#xff09;www.…

Android meminfo 查看方法及解析

目录 Android 上查看memory 信息的方法 内存限制的信息 手动释放缓存 例 adb shell dumpsys meminfo pid 解析 adb shell dumpsys meminfo 汇总信息说明 Total RAM Free RAM ION Used RAM Lost RAM ZRAM /proc/meminfo 参考文档 Android 上查看memory 信息的方法 …