MIT6.1810 Lab2

Lab: system calls

实验链接

实验准备

切换到 syscall 分支:

ruby 复制代码
  $ git fetch
  $ git checkout syscall
  $ make clean

gdb

需求

回答以下问题(将答案存储在answers- sycall .txt中):

  1. Looking at the backtrace output, which function called syscall?
  2. What is the value of p->trapframe->a7 and what does that value represent? (Hint: look user/initcode.S, the first user program xv6 starts.)
  3. What was the previous mode that the CPU was in?
  4. Write down the assembly instruction the kernel is panicing at. Which register corresponds to the varialable num?
  5. Why does the kernel crash? Hint: look at figure 3-3 in the text; is address 0 mapped in the kernel address space? Is that confirmed by the value in scause above? (See description of scause in RISC-V privileged instructions)
  6. What is the name of the binary that was running when the kernel paniced? What is its process id (pid)?

The solution

gdb使用简介

question one

键入以下gdb指令

scss 复制代码
(gdb) file kernel/kernel //切换到kernel可执行文件
(gdb) b syscall //在函数syscall入口打上断点
(gdb) c //启动qemu直到断点处暂停
(gdb) backtrace //回溯输出(查看哪个函数调用了syscall)

得到以下结果

shell 复制代码
#0  syscall () at kernel/syscall.c:133
#1  0x0000000080001d16 in usertrap () at kernel/trap.c:67
#2  0x0000000000000438 in ?? ()

可知是kernel/trap.c中的usertrap ()函数调用了syscall。

question two

在问题一的基础上键入指令

scss 复制代码
(gdb) n //单步调试
(gdb) n
(gdb) p/x p->trapframe->a7 //以16进制显示该变量值

可得结果$1 = 0x7,查看kernel/syscall.h可知7对应的是exec的系统调用号

question three

在问题二的基础上键入(gdb) p/x $sstatus可得结果$2 = 0x22

根据RISC-V privileged instructions中对特权指令 <math xmlns="http://www.w3.org/1998/Math/MathML"> s s t a t u s sstatus </math>sstatus 的描述:

  • The sstatus register keeps track of the processor's current operating state.
  • The SPP bit indicates the privilege level at which a hart was executing before entering supervisor mode. When a trap is taken, SPP is set to 0 if the trap originated from user mode, or 1 otherwise. When an SRET instruction (see Section 3.3.2) is executed to return from the trap handler, the privilege level is set to user mode if the SPP bit is 0, or supervisor mode if the SPP bit is 1; SPP is then set to 0.

易知若trap是来自用户模式则sstatus中的SPP位为0,若trap来自监管者模式则sstatus中的SPP位为1。

此时的sstatus为22其2进制表示为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 00010110 0001 0110 </math>00010110,第8位SSP位为0,故CPU以前的模式是用户模式。


下面给出从main创建第一个进程到shell开始运行的流程图(系统调用只画了exec),有助于理解上述三个问题

question four

首先进入kernel/syscall.c修改其中的syscall函数

ini 复制代码
  //num = p->trapframe->a7;
  num = *(int*)0;

然后重新对qemu进行调试,当运行到被修改的位置时,出现以下报错

ini 复制代码
  scause 0x000000000000000d
  sepc=0x0000000080001ff6 stval=0x0000000000000000
  panic: kerneltrap

查看kernel/kernel.asm中对应的汇编代码

ini 复制代码
  num = *(int*)0;
  80001ff6:	00002683          	lw	a3,0(zero) # 0 <_entry-0x80000000>

即可知内核正在处理的汇编指令与应于变量num的寄存器。

验证

sepc为出错的指令地址。 重新启动调试,键入以下指令

scss 复制代码
(gdb) b *0x0000000080001ff6
(gdb) layout asm
(gdb) c

可得以下结果

question five

课文图3.3

通过上图结合问题四可得出内核崩溃原因:

  1. 由于虚拟地址0未映射到任何的物理地址,故加载指令(lw)不能翻译虚拟地址0, 导致load页故障。
  2. 该页故障异常发生在内核中,xv6没有对内核态下的异常进行处理,故导致直接崩溃。

验证

scause寄存器中的值表示页面故障的类型,由问题四可知在发生页错误后其值为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 x 0 d 0x0d </math>0x0d 查阅RISC-V privileged instructions中表4.2: 可知产生的错误确实为 <math xmlns="http://www.w3.org/1998/Math/MathML"> L o a d p a g e f a u l t Load page fault </math>Loadpagefault

question six

在问题5的基础上键入以下指令即可:

swift 复制代码
(gdb) p p->name
$1 = "initcode\000\000\000\000\000\000\000"
(gdb) p p->pid
$2 = 1

System call tracing

需求

添加一个系统调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace, 它用于跟踪本进程及子进程的某些系统调用。它参数是一个整数"mask",其位指定了要跟踪哪些系统调用。例如,为了跟踪fork系统调用,用户程序需调用trace(1 << SYS_fork),其中SYS_fork是来自kernel/ sycall.h的系统调用号。在每个被跟踪的系统调用即将返回时打印一行。这一行应该包含进程id、系统调用的名称和返回值;

我们提供了一个用户级程序 "trace.c",它会exec别的程序,在此之前会调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 来指定被跟踪的系统调用(详情参见user/trace.c)。

The solution

  1. 将 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 为用户空间提供的接口原型int trace(int);添加到 user/user.h。
  2. 在 user/usys.pl 为用户空间添加系统调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 的存根,在其文件末尾加上entry("trace");即可。 注:user/usys.pl是一个Perl脚本,它按照固定的模板,生成在用户空间调用系统调用的汇编代码。
  3. 添加 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 的系统调用号,在kernel/syscall.h文件末尾加上#define SYS_trace 22即可。
  4. 在kernel/syscall.c中添加系统调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 的函数声明extern uint64 sys_trace(void);

由于需要根据用户传入的参数来跟踪本进程以及子进程的某些系统调用,故我们需要在进程间传递该参数,由于 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 在内核态中执行不好使用pipe进行进程间通信,且不知何时会fork子进程,故我们选择修改进程结构体,为其添加一个变量mask,对文件kernel/proc.h进行修改:

c 复制代码
    struct proc {
      ...
      int mask;
    };

为了实现进程间传递参数,需添加对mask的拷贝,在kernel/proc.c的fork定义中:

c 复制代码
    int
    fork(void)
    {
      ...
      // Cause fork to return 0 in the child.
      np->trapframe->a0 = 0;
      np->mask = p->mask;
      ...
    }

接下来就是要去完成系统调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 的函数定义,在该系统调用会接收用户态传递的参数,并将其赋值给mask,我们将定义写在kernel/sysproc.c中:

c 复制代码
    uint64
    sys_trace(void){
      int n;
      argint(0,&n);//接收参数
      myproc()->mask = n;//赋给mask
      return 0;
    }

为了在syscall中能调用sys_trace,我们需要将其函数入口地址存入数组static uint64 (*syscalls[])(void)中,在其末尾加入[SYS_trace] sys_trace,即可。

最后我们要在syscall调用完系统调用后,通过本进程的mask来确认这个系统调用是不是被追踪的,若是则输出相关信息,为了方便信息输出,我们为其定义一个字符串数组,修改的文件为kernel/syscall.c:

c 复制代码
    char *str[]={
    [SYS_fork]    "syscall fork",
    [SYS_exit]    "syscall exit",
    [SYS_wait]    "syscall wait",
    [SYS_pipe]    "syscall pipe",
    [SYS_read]    "syscall read",
    [SYS_kill]    "syscall kill",
    [SYS_exec]    "syscall exec",
    [SYS_fstat]   "syscall fstat",
    [SYS_chdir]   "syscall chdir",
    [SYS_dup]     "syscall dup",
    [SYS_getpid]  "syscall getpid",
    [SYS_sbrk]    "syscall sbrk",
    [SYS_sleep]   "syscall sleep",
    [SYS_uptime]  "syscall uptime",
    [SYS_open]    "syscall open",
    [SYS_write]   "syscall write",
    [SYS_mknod]   "syscall mknod",
    [SYS_unlink]  "syscall unlink",
    [SYS_link]    "syscall link",
    [SYS_mkdir]   "syscall mkdir",
    [SYS_close]   "syscall close",
    [SYS_trace]   "syscall trace",
    };
    void
    syscall(void)
    {
      int num;
      struct proc *p = myproc();

      num = p->trapframe->a7; //系统调用号
      if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
        // Use num to lookup the system call function for num, call it,
        // and store its return value in p->trapframe->a0
        p->trapframe->a0 = syscalls[num](); //系统调用的返回值
        if((p->mask >> num) & 1) //若该系统调用被跟踪
        	printf("%d: %s -> %d\n",p->pid,str[num],p->trapframe->a0);//输出信息
      } else {
        printf("%d %s: unknown sys call %d\n",
                p->pid, p->name, num);
        p->trapframe->a0 = -1;
      }
    }

注:mask需要初始化为0,不然可能在程序调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace 前就会输出一些系统调用信息,但仔细阅读 kernel/proc.c可以发现,进程结构体是定义在全局的struct proc proc[NPROC];故不需考虑mask是否为0。

Sysinfo

需求

添加一个系统调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> s y s i n f o sysinfo </math>sysinfo,它收集有关正在运行的系统的信息。这个系统调用接受一个参数:一个指向结构体sysinfo的指针(参见kernel/sysinfo.h).内核应该填写这个结构体的字段:freenen字段应该设置为空闲内存的字节数,nproc字段应该设置为状态不是unused的进程数。我们提供了一个测试程序sysinfotest,如果它打印"sysinfotest: OK",你就通过了这个任务。

The solution

前四步与 <math xmlns="http://www.w3.org/1998/Math/MathML"> t r a c e trace </math>trace需要做的是类似的,需要注意的是在user/user.h中声明sysinfo()的原型时,需要预先声明结构体sysinfo的存在:

c 复制代码
        struct sysinfo;
        int sysinfo(struct sysinfo *);

首先是获取非unused的进程数,借鉴kernel/proc.c中的allocproc()函数即可,其核心就是遍历进程结构体数组proc,并判断其元素的state。

c 复制代码
    uint64 get_used_proc(){
      struct proc *p;
      uint64 n = 0;
      for(p = proc; p < &proc[NPROC]; p++) {
        if(p->state != UNUSED) 
          n++;
      }
      return n;
    }

然后是获取空闲内存,通过查看文件kernel/kalloc.c可知每个物理内存页的单位是PGSIZE=4096字节, 以一个单链表的形式管理空闲内存页,我们只需遍历该单链表获取空闲页数,每页计一个PGSIZE即可。

c 复制代码
    uint64 get_free_memory(){
      uint64 n=0;
      struct run* r = kmem.freelist;
      while(r){
        r=r->next;
        n += PGSIZE;
      }
      return n;
    }

我们还需要处理的一个问题就是如何将结构体sysinfo复制回用户空间,对于该问题我们可以使用kernel/vm.c中定义的copyout()函数,其原型为:

c 复制代码
    // Copy from kernel to user.
    // Copy len bytes from src to virtual address dstva in a given page table.
    // Return 0 on success, -1 on error.
    int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len);

最后就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s y s i n f o sysinfo </math>sysinfo 的函数定义:

c 复制代码
    uint64 sys_sysinfo(){
      uint64 st;
      argaddr(0, &st);//获取从用户空间传入的指针。
      struct sysinfo p;//将信息存在结构体中
      p.nproc = get_used_proc();
      p.freemem = get_free_memory();
      if(copyout(myproc()->pagetable, st, (char *)&p, sizeof(p)) < 0)//拷贝回用户空间
        return -1;
      return 0;
    }
相关推荐
阑梦清川1 天前
linux操作系统课程学习02
操作系统
阑梦清川1 天前
linux操作系统课程学习01
操作系统
望获linux4 天前
【实时Linux实战系列】CPU 隔离与屏蔽技术
java·linux·运维·服务器·操作系统·开源软件·嵌入式软件
数据智能老司机4 天前
Linux内核编程——网络驱动程序
linux·架构·操作系统
数据智能老司机4 天前
Linux内核编程——字符设备驱动程序
linux·架构·操作系统
数据智能老司机5 天前
Linux内核编程——Linux设备模型
linux·架构·操作系统
望获linux5 天前
【Linux基础知识系列】第四十篇 - 定制彩色终端与 Prompt
linux·运维·前端·chrome·操作系统·开源软件·嵌入式软件
望获linux15 天前
【实时Linux实战系列】实时I/O操作与中断处理
linux·服务器·microsoft·操作系统·交互·rtos·嵌入式软件
redreamSo15 天前
世俗点,假如幸福能量化,公式是什么?
操作系统
智践行15 天前
ROS2 Jazzy:编写可组合节点(C++)
操作系统