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

2024-02-06 03:28
文章标签 学习 记录 实验 s081 traps

本文主要是介绍6.s081 学习实验记录(五)traps,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 一、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 并返回 xreturn 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 并返回 xreturn 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; //回调执行所需要ticksint cur_ticks; //当前进程的ticksvoid* callback; //回调函数struct trapframe backup; //保存trapframeint 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); //保存trapframeproc->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
    在这里插入图片描述

这篇关于6.s081 学习实验记录(五)traps的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/683004

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件