Lab3:Traps

参考文章:
6.S081 & 操作系统内核
操作系统MIT6.S081:Lab4->Trap

Xv6操作系统的系统调用通过 trampoline 来实现用户空间和内核空间之间的跳转,trampoline页面是在内核地址空间和用户地址空间均有相同的地址映射,也就是说:trampoline页面在物理地址空间只有一份(且是可读可执行的代码),而在内核地址空间和用户地址空间中,均将MAXVA - PGSIZE的地址(也就是第一个页面)映射到了这段代码。
这样做的好处就是,在执行trampoline中的代码时,执行内核页表和用户进程页表不会使得程序崩溃,这也就是为什么这个页面叫做trampoline(蹦床)的原因了。
为了理解Xv6的代码,需要了解部分risc-v的汇编,如果不深究的话,我认为只需要知道函数调用参数传递通过的是a0-a7寄存器,函数调用返回值通过a0寄存器返回。其他很多指令和x86汇编类似。

  • RISC-V的Trap机制:(来自xv6book Chapter3)

Xv6 trap 处理分为四个阶段:RISC-V CPU采取的硬件行为,为内核C代码准备的汇编入口,处理trap的C 处理程序,以及系统调用或设备驱动服务。

每个RISC-V CPU都有一组控制寄存器,内核写入这些寄存器来告诉CPU如何处理trap,内核可以通过读取这些寄存器来发现已经发生的trap。RISC-V文档包含了完整的叙述[1]。riscv.h(kernel/riscv.h:1)包含了xv6使用的定义。这里是最重要的寄存器的概述。
stvec:内核在这里写下trap处理程序的地址;RISC-V跳转到这里来处理trap。
sepc:当trap发生时,RISC-V会将程序计数器保存在这里(因为PC会被stvec覆盖)。sret(从trap中返回)指令将sepc复制到pc中。内核可以写sepc来控制sret的返回到哪里。
scause:RISC -V在这里放了一个数字,描述了trap的原因。
sscratch:内核在这里放置了一个值,在trap处理程序开始时可以方便地使用(trapframe的地址)。
sstatus:sstatus中的SIE位控制设备中断是否被启用,如果内核清除SIE,RISC-V将推迟设备中断,直到内核设置SIE。SPP位表示trap是来自用户模式还是supervisor模式,并控制sret返回到什么模式。
上述寄存器与在特权态模式下处理的trap有关,在用户模式下不能读或写。
对于机器模式下处理的trap,有一组等效的控制寄存器;xv6只在定时器中断的特殊情况下使用它们。

【来自用户空间的trap的处理路径是uservec(kernel/trampoline.S:16),然后是usertrap(kernel/trap.c:37);返回时是usertrapret(kernel/trap.c:90),然后是userret(kernel/trampoline.S:16)】
(Chapter4.2: Trap from user space 清楚地介绍了整个过程,包括trapframe的概念和作用,可以作为最好的最详细的参考资料)

参考文章:
小切的博客
xv6中的trap处理机制
xv6的trap机制
xv6陷入

本 lab 的任务是添加系统调用,理解 traps 的实现。

阅读指路:
kernel/trampoline.S:用户态和内核态相互切换的汇编代码(寄存器的取出和写入)
kernel/trap.c:处理所有中断的代码

1. RISC-V Assembly

参考文章:
操作系统MIT6.S081:Lab4->Trap
一文学懂riscv汇编操作
操作系统MIT6.S081:P4->RISC-V calling conventions and stack frames

2. Backtrace

kernel/printf.c 中实现函数 backtrace(),发生错误时用于打印堆栈上的函数调用列表。
(useful for debugging)

原理见下图:

准备工作:

  • Add the prototype for backtrace to kernel/defs.h so that you can invoke backtrace in sys_sleep().
void            backtrace(void);

主要工作:

  • The GCC compiler stores the frame pointer of the currently executing function in the register s0 【GCC编译器将当前运行的函数的 frame pointer 存储在寄存器 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;
    }
    and call this function in backtrace to read the current frame pointer. 【在 backtrace() 函数中调用 r_fp() 读取当前 frame pointer】.This function uses in-line assembly to read s0.
  • Note that the return address lives at a fixed offset (-8) from the frame pointer of a stackframe, and that the saved frame pointer lives at fixed offset (-16) from the frame pointer.【当前函数的返回地址在栈帧-8的地址存储,上一个栈帧的 frame pointer 在当前栈帧-16的地址存储】
    (见上图原理)
  • Xv6 allocates one page for each stack in the xv6 kernel at PAGE-aligned address. You can compute the top and bottom address of the stack page by using PGROUNDDOWN(fp) and PGROUNDUP(fp) 【用定义的两个宏,计算栈的地址上下界,判断backtrace循环迭代的结束点】 (see kernel/riscv.h. These number are helpful for backtrace to terminate its loop.
// [kernel/printf.c]
void
backtrace()
{printf("backtrace:\n");uint64 fp = r_fp();  // 获取当前栈帧uint64 bottom = PGROUNDUP(fp);  // 栈由高地址向低地址增长, 栈底是当前页的最高地址!while(fp < bottom){ // 当帧指针在有效地址范围内:printf("%p\n", *((uint64*)(fp - 8)));// 当前fp-8的地址空间存储着函数返回地址(指针, %p形式输出)fp = *((uint64*)(fp - 16));   // 当前fp-16的地址空间存储着上一个调用函数的栈帧的fp, 循环迭代输出}
}
#define PGROUNDUP(sz)  (((sz)+PGSIZE-1) & ~(PGSIZE-1))
// [helpful for backtrace to terminate its loop]
#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1))
//uint64 sz: Size of process memory (bytes)

在系统调用中加入backtrace():

  • Once your backtrace is working, call it from panic in kernel/printf.c so that you see the kernel’s backtrace when it panics.【在kernel/printf.c的panic()函数中加入backtrace()调用;在kernel/sysproc.c的sys_sleep()函数中加入backtrace()调用】
void panic(char *s)
{// ......printf("\n");backtrace(); //调用backtracepanicked = 1; // freeze uart output from other CPUs// ......
}
uint64
sys_sleep(void)
{// ......release(&tickslock);backtrace();// 调用backtracereturn 0;
}

3. Alarm

目标:
添加一个新的系统调用,实现警报功能,定期向使用 CPU 时间的进程发出警报。

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

test0: invoke handler

准备工作:

  • You’ll need to modify the Makefile to cause alarmtest.c to be compiled as an xv6 user program.
  • The right declarations to put in user/user.h are:
    int sigalarm(int ticks, void (*handler)());
    int sigreturn(void);
  • Update user/usys.pl (which generates user/usys.S), kernel/syscall.h, and kernel/syscall.c to allow alarmtest to invoke the sigalarm and sigreturn system calls.
  • For now, your sys_sigreturn should just return zero.

主要工作:

  • Your sys_sigalarm() should store the alarm interval and the pointer to the handler function in new fields in the proc structure (in kernel/proc.h).
    【在proc.h中添加新的数据结构,由sys_sigalarm()存储 alarm间隔 & 指向handler函数的指针
  • You’ll need to keep track of how many ticks have passed since the last call (or are left until the next call) to a process’s alarm handler; you’ll need a new field in struct proc for this too.
    【对进程的每一个alarm handler,维护当前经过了多少ticks(或者距离下次调用还有多少ticks),维护proc结构体内部新定义的数据结构实现】
  • You can initialize proc fields in allocproc() in proc.c.
// [kernel/proc.h]
// Per-process state
struct proc {struct spinlock lock;// p->lock must be held when using these:enum procstate state;        // Process statestruct proc *parent;         // Parent processvoid *chan;                  // If non-zero, sleeping on chanint killed;                  // If non-zero, have been killedint xstate;                  // Exit status to be returned to parent's waitint pid;                     // Process ID// these are private to the process, so p->lock need not be held.uint64 kstack;               // Virtual address of kernel stackuint64 sz;                   // Size of process memory (bytes)pagetable_t pagetable;       // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context;      // swtch() here to run processstruct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)//[store the alarm interval and the pointer to the handler function in new fields in the proc structure]int alarm_interval; // alarm intervaluint64 pointer; // pointer to the handler functionint ticks;  // ticks passedstruct trapframe alarm_trapframe;int alarm_flag; // a flag for alarm};
// [kernel/proc.c]
static struct proc*
allocproc(void)
{struct proc *p;......// [Lab3:Trap:alarm] initialize proc fields addingsp->alarm_interval = 0;p->pointer = 0;p->ticks = 0;p->alarm_flag = 1;  // admit alarmreturn p;
}
  • Every tick, the hardware clock forces an interrupt, which is handled in usertrap() in kernel/trap.c.
    【每个tick时间,硬件产生一次中断,由 kernel/trap.c 中的 usertrap() 函数处理】
  • You only want to manipulate a process’s alarm ticks if there’s a timer interrupt; you want something like
    if(which_dev == 2) … 【usertrap(): 通过 if 语句判断 timer interrupt 并特殊处理】
  • Only invoke the alarm function if the process has a timer outstanding. Note that the address of the user’s alarm function might be 0 (e.g., in user/alarmtest.asm, periodic is at address 0).
  • You’ll need to modify usertrap() so that when a process’s alarm interval expires, the user process executes the handler function. When a trap on the RISC-V returns to user space, what determines the instruction address at which user-space code resumes execution?
    (sepc, 当系统调用发生时PC存放到此处,以便系统调用返回时能从下一条指令开始执行sret: sepc -> pc)
    【6.S081 & 操作系统内核】这篇文章 “test1&test2” 部分有介绍:
    (在handler函数内部的最后会调用sigreturn系统调用,随后在syscall函数执行完sys_sigreturn之后,跳转到usertrapret,usertrapret中会在spec寄存器中存入用户进程被打断的指令的地址,usertrapret最后会调用userret函数,在userret中会使用alarm_trapframe恢复用户寄存器,并且在执行完最后的sret之后,安全返回用户进程之前的状态与位置)
// [kernel/trap.c]
// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
void
usertrap(void)
{......// a timer interrupt.if(which_dev == 2){if(p->alarm_interval > 0){   // 进程存在interval机制://(要求interval > 0, 由题目中描述, interval = 0时要求停止生成定期警报)p->ticks++;  // ticks数量+1if(p->ticks == p->alarm_interval && p->alarm_flag){// 进程到达 interval 周期且允许中断并执行 handler function:p->alarm_flag = 0;  // 【禁止新的中断】p->ticks = 0; // 重设ticks【handler function返回后ticks从0开始计数】memmove(&(p->alarm_trapframe), p->trapframe, sizeof(struct trapframe));    // 【存储进程当前栈帧信息】p->trapframe->epc = p->pointer;    // 【进程的程序计数器(下一条指令所在单元的地址)设为 handler function 的函数指针】}}yield();}usertrapret();
}
// [kernel/sysproc.c]
// 系统调用sigalarm:
uint64
sys_sigalarm(void)
{// 收到系统调用参数, 存储在进程状态中int interval;uint64 pointer;// 从s0寄存器中取参数, 拿到间隔ticks时长, 存储为intervalif(argint(0, &interval) < 0)return -1;/* 检查到最后发现是argaddr函数写错了(pointer有错误)第一个参数应该是1 */// 从s1寄存器中取参数, 拿到handler function函数指针, 存储为pointer)if(argaddr(1, &pointer) < 0) return -1;struct proc *p = myproc();p->alarm_interval = interval;    //【interval > 0, 在 trap.c 中处理时钟中断时进行更新和检查】p->pointer = pointer;return 0;
}

test1/test2(): resume interrupted code

user alarm handlers are required to call the sigreturn system call when they have finished.
【上述任务还没有完成与sigalarm相配合的sigreturn系统调用 ,只是简单地让sigreturn函数返回0】
This means that you can add code to usertrap and sys_sigreturn that cooperate to cause the user process to resume properly after it has handled the alarm.

  • Your solution will require you to save and restore registers—what registers do you need to save and restore to resume the interrupted code correctly? (Hint: it will be many).
    【我选择的策略是,在调用handler function前,将进程的整个p->trapframe进行复制保存】
  • Prevent re-entrant calls to the handler----if a handler hasn’t returned yet, the kernel shouldn’t call it again. test2 tests this.
    【proc.h中添加flag设置是否允许interval中断调用handler function】

【注意到任何handler function的最后语句必须调用sigreturn进行返回,返回时允许下一次调用】

// [kernel/sysproc.c]
uint64
sys_sigreturn(void)
{struct proc *p = myproc();if(p->alarm_flag == 0){    // 如果当前禁止中断memmove(p->trapframe, &(p->alarm_trapframe), sizeof(struct trapframe));    // 【重新加载trapframe】  memmove(dst, src, size)p->alarm_flag = 1;   // 设置为允许中断}return 0;
}

XV6 lab3:Trap相关推荐

  1. ICC 图文学习——LAB3:Placement 布局

    floorplan完成了芯片的整体规划后,需要对标准单元进行摆放.布局阶段主要内容包括: · 完成布局和时序优化的设置 · 完成DFT和功耗优化的设置 · 完成标准单元位置的摆放 · congesti ...

  2. XV6 Lab7:Locks

    Lab7:Locks 本 lab 的任务是优化 xv6 锁,以减少锁竞争. 我们将会重新设计代码以降低锁竞争,提高多核机器上系统的并行性,需要修改数据结构和锁的策略. 详细要求及提示见链接: http ...

  3. linux捕捉信号sigint失败,为shell布置陷阱:trap捕捉信号方法论

    本文目录: 1.1 信号说明 1.2 trap布置陷阱 1.3 布置完美陷阱必备知识 家里有老鼠,快消灭它!哎,又给跑了.老鼠这小东西跑那么快,想直接直接消灭它还真不那么容易.于是,老鼠药.老鼠夹子或 ...

  4. XV6 Lab2:Page Tables

    Lab2:Page Tables 本 lab 的任务是理解 xv6 页表的实现. 参考文章: xv6实验课程–页表(2021) 6.S081-2021FALL-Lab3:pgtbl MIT6.S081 ...

  5. 操作系统MIT6.S081:[xv6参考手册第4章]->Trap与系统调用

    本系列文章为MIT6.S081的学习笔记,包含了参考手册.课程.实验三部分的内容,前面的系列文章链接如下 操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口 操作系统MIT6. ...

  6. XV6实验(2020)

    XV6实验记录(2020) 环境搭建 参考连接 Lab guidance (mit.edu) 6.S081 / Fall 2020 (mit.edu) xv6 book中文版 Lab1:Xv6 and ...

  7. MIT JOS lab3保姆级试验记录,附满分代码

    操作系统Lab3 User environments 本次 lab 的目标是创建进程运行环境.在本实验中,(1)将实现运行受保护的用户模式环境(即"进程")所需的基本内核功能(2) ...

  8. XV6源代码阅读-中断与系统调用

    XV6源代码阅读-中断与系统调用 Exercise1 源代码阅读 1.启动部分: bootasm.S bootmain.c 和xv6初始化模块:main.c bootasm.S 由16位和32位汇编混 ...

  9. XV6陷入,中断和驱动程序

    陷入,中断和驱动程序 运行进程时,cpu 一直处于一个大循环中:取指,更新 PC,执行,取指--.但有些情况下用户程序需要进入内核,而不是执行下一条用户指令.这些情况包括设备信号的发出.用户程序的非法 ...

最新文章

  1. 解读Python的命名空间
  2. php数独游戏开发,使用vue如何开发数独游戏
  3. DBCP2配置详细说明(中文翻译)
  4. matlab中imresize函数的用法,为何 MATLAB imresize 函数和 OpenCV resize 函数结果不同
  5. [Everyday Mathematics]20150107
  6. virtualBox中的ubuntu共享文件夹
  7. android自定义url协议,Android自定义URL方案…?
  8. 0x80070659系统策略禁止这个安装 vc_不安装DNS解析服务器下安装Vcenter6.7
  9. 非域计算机上模拟域用户,App-V如何让非域内(工作组)PC 也能享受应用程序虚拟化...
  10. [XSY] 简单的数论题(数学、构造)
  11. linux 内网文件传输工具_不管你是新手PHP程序员还是大佬都要知道的PHP十大必备工具...
  12. 商业智能BI的数据价值
  13. 计算机及应用学习顺序,自考计算机及应用专业经验谈
  14. 从使用Python开发一个Socket示例说到开发者的思维和习惯问题
  15. laravel html转pdf和转图片 (laravel-snappy的使用记录)
  16. vm虚拟机出现目标主机不支持CPUID的情况
  17. vue中将字符转换成数字的简单做法
  18. 华熙LIVE·五棵松再添新地标,北京市新能源汽车旗舰体验中心正式落户!
  19. 中国2-己氧乙醇市场发展现状与投资前景分析报告2022-2028年
  20. SMS平台发短信的代码

热门文章

  1. 怎么使用远程管理卡登录服务器、远程连接数据库
  2. APM_ArduCopter源码解析学习(三)——无人机类型
  3. php讲图片转换成二进制,如何把php中的图片转换成二进制
  4. 基于Java的学生请销假审批管理系统的设计与实现毕业设计源码130939
  5. 如何制作专题地图(设计、规划、测绘制作图斑)标绘使用详解
  6. Maven命令报错读取jar时出错
  7. Windows(2003/2008/2012)弹性云系统盘扩容方法
  8. 黑色炫酷网址安全跳转源码 GO跳转PHP页面
  9. 【高德地图API】如何进行坐标转换?坐标拾取工具
  10. Java配置Path和JAVA_HOME(windows)