代码编织梦想


本Lab主要熟悉在trap中系统调用的实现。
笔者用时约4h

概述

首先讲讲我对系统调用时trap的理解,用户程序通过系统调用进入内核态,在汇编代码中的体现就是ecall指令,会把系统调用参数保存在寄存器a7中。
在用户态中执行ecall指令之后,会将pc寄存器的内容复制到sepc中,并将模式转换为监督者模式,且会将stvec复制到pc中
由于stvec的值为函数uservec(定义在kernel/trampoline.S:37)的地址,故在执行完ecall指令之后,会跳转到uservec函数中。由于ecall指令并没有切换页表的功能,于是在用户页表中必须要维护uservec函数虚拟地址的映射,这也就是trampoline页的功能啦。
uservec函数中,首先需要将用户态中的所有寄存器内容保存到该用户进程的trapframe中,一开始trapframe的首地址保存在sscratch寄存器中,首先代码将a0寄存器与sscratch寄存器进行交换,然后开始保存所有其他寄存器的值(根据提前规定好的偏移量,定义在kernel/proc.h:44)。然后需要把当前进程trapframe中的一些字段保存到对应的寄存器中,比如kernel_sp保存到sp寄存器之类的。最后需要将该内核页表地址写入satp寄存器中,切换页表为内核页表。这里再一次体现了,内核页表和进程页表共同映射了trampoline页的原因。
uservec函数最后调用了usertrap函数(定义在kernel/trap.c:37中),在该函数中,首先向stvec寄存器写入kernelvec函数(定义在kernel/kernelvec.S中)的地址(因为这个时候在内核态,于是trap由kernelvec函数进行处理),并将当前sepc寄存器中的值保存到trapframe的epc字段中,然后将epc加4,即跳过了ecall指令,指向用户态的下一条指令。然后调用syscall函数进行系统调用的操作。
syscall函数返回时,会调用usertrapret函数(定义在kernel/trap.c:103中),该函数将uservec重新写入stvec寄存器中,将trapframe中的一些字段设置好,并将模式设置为用户模式,以便处理下一次的用户态trap。最后,将trapframe的地址作为第一个参数(保存在a0中),将用户进程跟页表地址作为第二个参数(保存在a1中),调用userret函数(定义在kernel/trampoline.S:88中)。
在函数userret中,首先切换页表为用户进程页表,且将trapframe页的地址保存到寄存器a0中,然后恢复所有寄存器的值,将原始a0寄存器的值(在系统调用情况下,trapframe中的a0字段被设置为系统调用返回值)放在sscratch寄存器中,最后交换a0寄存器和sscratch寄存器的值,并使用sret指令(将模式变为用户模式,将sepc寄存器中的内容复制到pc寄存器中)回到用户态。

RISC-V assembly

回答一些问题

  1. Q: 哪些寄存器保存函数的参数?例如,在main对printf的调用中,哪个寄存器保存13?
    A: 寄存器a0-a7保存函数参数,比如printf的调用中,第一个参数是字符串地址保存在a0中,f(8)+1保存在a1中,13保存在a2中。
  2. Q: main的汇编代码中对函数f的调用在哪里?对g的调用在哪里(提示:编译器可能会将函数内联)
    A: 被编译器优化掉了,减少了函数的调用,从li a1, 12可以看出
  3. Q: printf函数位于哪个地址?
    A: 0x630, 在call.asm中搜一下就可以看到
  4. Q: 在main中printf的jalr之后的寄存器ra中有什么值?
    A: 在main中,printf的jalr指令会将下一条指令的地址(也就是0x38)保存在寄存器ra中,于是jalr之后的寄存器ra中的值为0x38
  5. Q: 运行以下代码。
    unsigned int i = 0x00646c72;
    printf("H%x Wo%s", 57616, &i);
    
    程序的输出是什么?
    A: HE110 World
    Q: 输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i设置成什么?是否需要将57616更改为其他值?
    如果是大端存储,那么将i设置为0x726c6400即可,不需要将57616改为其他值
  6. Q: 在下面的代码中,“y=”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?
    printf("x=%d y=%d", 3);
    
    A: y将打印成什么取决于上一次a2寄存器中的内容

Backtrace

这一部分需要实现一个函数backtrace,在调用处打印当前栈中所有栈帧保存的返回地址信息。
那么首先需要获取当前栈帧的帧尾指针fp,根据文档提示,我们可以在文件kernel/riscv.h中添加一个函数进行获取。

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

在xv6中,栈的数据分布如下,当进行函数调用时,会将函数的返回地址、上一个栈帧的帧尾地址、被保存的寄存器和局部变量等压入栈中形成栈帧。于是,当前栈帧的返回地址信息在fp-8指向的地址中,上一个栈帧的帧尾地址在fp-16指向的地址中。

在这里插入图片描述

那么怎么停止循环呢。由于在xv6中,每一个栈的大小都是一页,且根据文档中提示,可以使用PGROUNDOWN和PGROUNDUP求出当前栈的首地址和末地址,如果当前栈帧中上一个栈帧的帧尾地址不在当前栈的地址范围内,则退出循环,不再继续打印,所写的函数如下。

void 
backtrace()
{
  printf("backtrace:\n");
  uint64 fp = r_fp();
  uint64 l = PGROUNDDOWN(fp), r = PGROUNDUP(fp);
  while (l <= fp && fp <= r) {
    printf("%p\n", *((uint64*)(fp - 8)));
    fp = *((uint64*)(fp - 16));
  }
}

Alarm

这一部分主要是实现两个系统调用sigalarmsigreturn

test0: invoke handler

sigalarm系统调用接收两个参数tickshandler,表示在经过ticks个CPU时钟中断之后便调用函数handler进行处理。
那么需要我们在进程结构体中添加几个字段,保存系统调用的参数tickshandler,并记录当前进程已经经过多少个CPU时钟中断。

  ...
  // for sigalarm
  int ticks;
  void (*handler) ();
  int tita;
  ...

如下是sigalarm系统调用的实现

uint64 
sys_sigalarm(void)
{
  int ticks;
  uint64 handler;
  struct proc* p = myproc();
  
  if(argint(0, &ticks) < 0 || argaddr(1, &handler) < 0)
    return -1;

  p->ticks = ticks, p->handler = (void (*)())handler;

  return 0;
}

当遇到CPU时钟中断时,在usertrap函数中有相关处理(即在if(which_dev == 2)处,我们累加当前进程的tita字段即可,且如果当前tita已经为ticks,则通过修改trapframe的epc字段来调用handler函数。(注意题目规定如果ticks为0则不要进行处理)

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2) {
    if (p->ticks != 0) {
      p->tita ++;
      if (p->tita == p->ticks) {
        p->tita = 0;
        p->trapframe->epc = (uint64)p->handler;
      }
    }
    yield();
  }

test1/test2(): resume interrupted code

在通过test0之后,需要考虑的是在运行完处理函数handler之后,继续运行原始程序,文档为我们提供了一种解决方案,即通过系统调用sigreturn来恢复原始trapframe。
由于在handler处理过程中,进程的trapframe可能发生改变(其实这里对于什么时候发生改变还不是很清晰),于是在改变trapframe中的epc为handler之前,先将trapframe缓存一下,在进程结构体中添加一个字段即可。

struct trapframe *copyframe;

并且在进程分配时初始化该字段,在进程销毁时释放该字段,像trapframe字段一样即可。

// allocproc
  if((p->copyframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }
// freeproc
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;

还要注意的是,文档要求如果当前handler函数如果还在调用中,那么不需要重复调用。于是需要加一个字段表示当前是否正在调用中。

int calling;

于是,在usertrap函数中,如果当前CPU时钟中断次数到达阈值,则缓存trapframe并设置calling为1和trapframe的epc以调用handler。

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2) {
    if (p->ticks != 0) {
      p->tita ++;
      if (p->tita == p->ticks) {
        p->tita = 0;
        if (p->calling == 0) {
          memmove(p->copyframe, p->trapframe, sizeof (struct trapframe));
          p->trapframe->epc = (uint64)p->handler;
          p->calling = 1;
        }
      }
    }

    yield();
  }

在系统调用sigreturn中,则需要恢复缓存下来的trapframe,并设置calling为0.

uint64 
sys_sigreturn(void)
{
  struct proc* p = myproc();
  memmove(p->trapframe, p->copyframe, sizeof (struct trapframe));
  p->calling = 0;
  return 0;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cpp_juruo/article/details/129824958

Mit6.S081学习记录-爱代码爱编程

Mit6.S081学习记录 前言一、课程简述二、课程资源1,课程主页2,参考书3,实验环境三、学习过程Mit6.S081-实验环境搭建Mit6.S081-GDB使用Mit6.S081-xv6参考书翻译[Mit6.S081-实验1-Xv6 and Unix utilities](https://blog.csdn.net/u013577996/art

Mit6.S081-实验4-Traps-爱代码爱编程

Mit6.S081-实验4-Traps 一、RISC-V assembly1,实验准备2,实验要求3,相关问题二、Backtrace1,实验要求2,具体实现3,执行效果三、Alarm-test0():invoke handler1,实验要求2,具体实现3,测试效果三、Alarm-test1/test2(): resume interrupted c

6.S081 Xv6 Lab 4 traps-爱代码爱编程

Lab: traps 6.S081 的 Xv6 RISC-V Lab traps,实验内容: https://pdos.csail.mit.edu/6.S081/2020/labs/traps.html$ git fetch $ git checkout traps $ make clean RISC-V assembly 这题没什么具体要做

MIT6.S081学习总结-lab4:traps-爱代码爱编程

lab4 是traps相关 Backtrace 添加一个backtrace函数,sys_sleep调用这个函数后可以打印出函数调用栈 实现: kernel/riscv.h里添加函数来获取frame pointer: 在kernel/printf.c里添加backtrace函数: sys_sleep里调用即可 Alarm 实现两个系统调用:in

操作系统实验Mit6.S081笔记 Lab4: Traps-爱代码爱编程

RISC-V assembly (easy) 要求: 理解一点RISC-V组装是很重要的,这在6.004中已经介绍过了。 在您的xv6 repo中有一个文件user/call.c。make fs.img编译它,并在user/call.asm中生成程序的可读汇编版本。 阅读call.asm中函数g、f和main的代码。RISC-V的使用说明书在参考页。

mit 6.s081 lab4 Traps-爱代码爱编程

chapter 4 trap的类型 syscallexception(除0,访问非法内存)interrupt(读/写磁盘操作结束)trap流程 1.控制权转换给kernel 2.kernel保存寄存器以及状态,以便代码的执行的恢复 3.kernel执行对应的trap处理代码(syscall实现或者设备驱动) 4.kernel恢复之前保存的寄存器和状态,

[MIT 6.S081] Lab 4: traps-爱代码爱编程

Lab 4: traps Lab Guidance: Lab: trapsLab Code: https://github.com/VastRock-Huang/xv6-labs-2020/tree/trapsRISC-V assembly (easy) 预处理 使用如下指令编译文件 user/call.c, 生成可读的汇编程序文件 user/cal

MIT 6.S081 Lab4: traps-爱代码爱编程

介绍 写在前面 这次实验主要是写与 traps(中断陷阱)有关的代码,实验指导书在这里。总体来讲,代码量不是很大,大部分时间都思考应该怎么写、为什么这么写可以、函数是怎么运行的等问题。终于在思考了几天后完成了这次实验,实验代码在我的 github 上。建议在做实验之前看看课程或者讲义,感觉这次的课很精髓,对做实验很有帮助。 前置知识 主要是 bac

2021 MIT6.S081 LAB4 backtrace alarm-爱代码爱编程

实验四 原网址 Lab: traps RISCV-V assembly Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf? call.asm中地址0x24的指令将13放在了a2

【操作系统】MIT 6.s081 LAB4-爱代码爱编程

LAB 4: Traps 原文地址:YSBLOG 参考:[mit6.s081] 笔记 Lab4: Traps | 中断陷阱 实验目的:探索如何通过trap实现系统调用。 RISC-V assembly (easy) 阅读call.asm函数代码,回答一下问题: 1、哪些寄存器保存函数的参数?例如,在main对printf的调用中,哪个寄存器保存1

MIT 6.s081学习笔记-爱代码爱编程

MIT 6.s081学习笔记 introduction 计算机组织结构: 最底部是一些硬件资源,包括了CPU,内存,磁盘,网卡最上层会运行各种应用程序,比如vim,shell等,这些就是正在运行的所有程序,它们都运行在同一个空间中,这个空间通常会被称为用户空间(Userspace)。区别于用户空间程序,有一个特殊的程序总是会在运行,它称为Kernel

MIT6.S081 2021-爱代码爱编程

MIT6.S081 2021 环境配置Xv6 and Unix utilitiesvscode格式化头文件排序问题以地址空间的视角看待变量其他代码参考system callstraceSysinfo lab地址 环境配置 虚拟机基本配置 下载Ubuntu镜像华为镜像站, 在software&update中修改软件源(我选的是阿里源)。

mit6.s081 operating system engineering xv6 lab概览_codefreestyle的博客-爱代码爱编程

文章目录 背景VX6操作系统常用命令实验相关链接博客记录推荐链接github 背景 该博客系列是在做MIT公开课实验过程中一些记录6.s081 Operating System Engineering VX6操作系统常用命令 ctrl + p等价于ps退出系统ctrl+a x进入qemu的console界面atrl+a c实验相关链接

2020 mit6.s081 os lab: xv6 traps_codefreestyle的博客-爱代码爱编程

文章目录 实验链接实验RISC-V assemblyBacktraceAlarm提交结果查看结果课程涉及知识汇总寄存器page table指令GDBTraplineTrapframe常用命令参考链接Github 实验链接 https://pdos.csail.mit.edu/6.S081/2020/labs/pgtbl.html 实验 R

操作系统 mit6.s081 lab4 trap_air浩瀚的博客-爱代码爱编程

操作系统 MIT6.S081 Lab4 Trap 实验原理 ① Trap:系统调用、异常和中断会导致CPU停止当前工作,这三种情况统称为trap Xv6 将所有的 Traps 都放在内核中处理: 对于系统调用,则执行

mit 6.s081 lab4 traps-爱代码爱编程

#Lab4: traps 目录 #Lab4: traps#My Code#Motivation #Backtrace#Motivation#Solution#Result #Alarm#Motivation

os lab 【traps】-爱代码爱编程

一、实验内容: Part A RISC-V assembly 见问题回答 Part B Backtrace Add the prototype for your backtrace() to kernel/defs

mit 6.s081 实验4 笔记与心得_usertrap sigreturn-爱代码爱编程

lab 4:Traps 文章目录 lab 4:Traps前期准备RISC-V assembly题目翻译题目答案 BackTrace(回溯)题目翻译题目答案 Al

操作系统mit6.s081:lab4-爱代码爱编程

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

mit 6.s081学习笔记_panic virtio_disk_intr-爱代码爱编程

  课程主页   xv6 book   GDB User Manual   MIT 6.S081 2020 操作系统 [中英文字幕]   课程视频中文文字翻译版   Xv6手册中文翻译—概览   Xv6手册中文翻译   Fa