xv6实验课程--系统调用

本文来源:

https://mp.weixin.qq.com/s/kPwvXMZ2cv8uQNZIsKcAjA

在上一个实验中,你使用系统调用编写了一些实用程序。在本实验中,你将向xv6添加一些新的系统调用,这将帮助你了解它们是如何工作的,同时,让你了解xv6内核的一些内部结构。在以后的实验中你可能会添加更多的系统调用。 

在开始编码之前,请阅读xv6手册的第2章、第4章的4.3节、4.4节以及下面所列相关源文件:

系统调用的用户空间代码:在user/user.h和user/usys.pl中。

内核空间代码:在kernel/syscall.h和kernel/syscall.c中。

与进程相关的代码:在kernel/proc.h和kernel/proc.c中。 

开始本实验前,请切换到syscall分支:

  $ git fetch

  $ git checkout syscall

  $ make clean

1. 系统调用跟踪 (难度:中等) 

任务:在xv6中添加一个系统调用跟踪功能,该功能可帮助你在以后的实验中调试程序。您将创建一个新的trace系统调用来控制跟踪。它应该有一个参数,一个整数“mask”,其位指定要跟踪的系统调用。例如,为了跟踪fork系统调用,程序调用trace(1<<SYS_fork),其中SYS_fork是kernel/syscall.h中的syscall编号。如果在掩码中设置了系统调用的编号,则必须修改xv6内核,以便在每个系统调用即将返回时输出一行。该行应包含进程id、系统调用的名称和返回值;不需要输出系统调用参数。trace系统调用应启用对调用它的进程及其随后派生的任何子进程的跟踪,但不应影响其他进程。

我们提供了一个跟踪用户级程序,该程序运行另一个启用了跟踪的程序(请参见user/trace.c)。完成后,您应该看到如下输出:

例1: trace调用grep仅仅跟踪read系统调用。32是1<<SYS_read。注:输出的第一段是进程标识符PID,第二段是系统调用名称,第三段是系统调用返回码。

例2: trace跟踪所有运行grep时调用的系统调用,其中2147583647的低31位都为1。

例3: 程序没有被跟踪,因此没有打印跟踪输出。

例4:usertests中的forkforkfork测试的所有后代的fork系统调用都被跟踪。

如果程序的行为如上所示,则你的实验方案是正确的(尽管进程ID可能不同)。

提示:

● 将$U/_trace添加到Makefile的UPROGS中

● 运行make qemu,您将看到编译器无法编译user/trace.c,这是因为系统调用的用户空间的存根(stubs)还不存在:将系统调用的原型添加到user/user.h,将存根添加到user/usys.pl中,并将syscall编号添加到kernel/syscall.h。Makefile调用perl脚本user/usys.pl,它生成user/usys.S,即实际的系统调用存根,它使用RISC-V ecall指令转换到内核。一旦你修复了编译问题,运行trace 32 grep hello README;它将失败,因为您尚未在内核中实现系统调用。

● 在kernel/sysproc.c中添加一个sys_strace( )函数,通过在proc结构的新变量中记住其参数来实现新的系统调用(请参见kernel/proc.h)。从用户空间检索系统调用参数的函数在kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。

● 修改fork( )(参见kernel/proc.c)将跟踪掩码从父进程复制到子进程。

● 修改kernel/syscall.c中的syscall( )函数以输出跟踪输出。您需要添加一个syscall名称数组来索引到其中。

实验参考步骤

步骤1:用户接口代码的修改

(1) 在user/user.h中添加系统调用函数的定义。

图片

(2) 在user/usys.pl中添加入口entry("trace")。

图片

在make时会调用usys.pl生成user/usys.S汇编程序。在该汇编程序中,每个函数有五行,其中有三条指令,其功能是将系统调用号通过li(load imm)存入a7寄存器,之后使用ecall指令进入内核态,最后返回。

make后user/usys.S中将出现下图中.global trace开始的5行代码。

图片

步骤2:内核代码的修改

(1)在kernel/syscall.h中定义系统调用号。

图片

(2)在kernel/syscall.c的syscalls函数指针数组中添加对应的函数。

图片

在syscall函数中,可读取trapframe->a7获取系统调用号,之后根据该系统调用号查找syscalls数组中的对应的处理函数并调用。

图片

(3)在proc结构体中添加一个trace_mask字段,之后在创建子进程的fork函数中复制该字段到新进程。

kernel/proc.h

图片

kernel/proc.c

图片

(4)系统调用sys_trace的实现。在sysproc.c中添加函数uint64 sys_trace(void),该函数通过argint函数读取参数赋值给mask变量,然后与trace_mask字段位或即可。

图片

(5) 修改syscall函数,当系统调用号和trace_mask匹配时输出相关信息。

图片

注意上图中的黄线处的syscall_name[num],这个需要定义。如下图所示。

图片

步骤3:编写应用工具(注:源代码中已提供)

user/trace.c

图片

步骤4:修改Makefile,将$U/_trace添加到Makefile的UPROGS中。

步骤5:make qemu,然后测试。

图片

2. Sysinfo (难度:中等) 

任务:增加一个sysinfo系统调用,它收集有关运行系统的信息。该系统调用有一个参数,即指向结构sysinfo的指针(参见kernel/sysinfo.h)。内核为该结构的各个字段赋值:设置freemem字段为可用内存的字节数,设置nproc字段为状态是非UNUSED的进程数。实验提供了一个测试程序sysinfotest,如果输出“sysinfotest:OK”,则该任务通过。

提示:

● 将$U/_sysinfotest添加到Makefile的UPROGS中。

● 运行make qemu,user/sysinfotest.c将无法编译。添加系统调用sysinfo,步骤与前面的trace系统调用相同。要在user/user.h中声明sysinfo( )的原型,预先声明struct sysinfo的存在:

struct sysinfo;

int sysinfo(struct sysinfo *);

● 修改上述编译问题后,运行sysinfotest将会失败,因为你尚未在内核中实现系统调用。

● sysinfo需要将struct sysinfo复制到用户空间,请参阅sys_fstat( )(kernel/sysfile.c)和filestat( )(kernel/file.c)以获取如何使用copyout()执行此操作的示例。

● 请在kernel/kalloc.c中添加一个函数,收集可用内存量。

● 请在kernel/proc.c中添加一个函数,收集进程数。

实验参考步骤

步骤1:用户接口代码的修改

(1) 在user/user.h中声明struct sysinfo的存在。(注:struct sysinfo在kernel/sysinfo.h中定义。

(2) 在user/user.h中添加系统调用函数的定义。

(3) 在user/usys.pl中添加入口 entry("sysinfo")。

在make时会调用usys.pl生成user/usys.S汇编程序。在该汇编程序中,每个函数有五行,其中有三条指令,其功能是将系统调用号通过li(load imm)存入a7寄存器,之后使用ecall指令进入内核态,最后返回。

make后user/usys.S中将出现下图中.global sysinfo开始的5行代码。

步骤2:内核代码的修改

(1)在kernel/syscall.h中定义系统调用号。

(2)在kernel/syscall.c的syscalls函数指针数组中添加对应的函数及函数名。

(3)freemem()函数的实现。

阅读kalloc和kfree两个函数可知,kmem.freelist是一个保存了当前空闲内存块的链表,因此只需要统计这个链表的长度再乘以PGSIZE就可以得到空闲内存。(注:kalloc和kfree两个函数在kernel/kalloc.c文件中。)

在kernel/defs.h中声明freemem函数。

在kernel/kalloc.c中添加freemem函数。

// get free memory
uint64
freemem(void)
{
  uint64 counter = 0;
  struct run *r;
  acquire(&kmem.lock);  // 上锁
  r = kmem.freelist;    // 空闲块链表
  while(r){    // 遍历空闲块链表,统计空闲块块数
    r = r->next;
    ++counter;
  }
  release(&kmem.lock); // 释放自旋锁
  return counter * PGSIZE; // 返回空闲存储空间大小,单位字节(B)
}

(4)nproc()函数的实现。

阅读procdump和相关代码可知,xv6的进程结构体保存在proc[NPROC]数组中。而proc->state字段保存了进程的当前状态,有UNUSED、SLEEPING、RUNNABLE、RUNNING、ZOMBIE五种状态。因此只需要遍历这个数组,统计state不是UNUSED状态的就行了。

在kernel/defs.h中声明nproc函数。

在kernel/proc.c中添加nproc函数。


// get number of proc
uint64
nproc(void)
{
  uint64 counter = 0;
  struct proc *p;
  // 遍历进程控制块,即OS课程中的PCB
  for(p = proc; p < &proc[NPROC]; p++) { 
    acquire(&p->lock);
    if(p->state != UNUSED) {
      ++counter;
    }
    release(&p->lock);
  }
  return counter;
}

(5)系统调用sys_sysinfo的实现。在sysproc.c中添加函数uint64 sys_sysinfo(void)。该函数主要通过freemem和nproc两个函数来统计空闲内存量和进程数。首先在kernel/proc.c中添加头文件#include "sysinfo.h"

添加sys_sysinfo(void)系统调用代码。

 


// get sysinfo
uint64
sys_sysinfo(void)
{
  uint64 info; // user pointer
  struct sysinfo kinfo;
  struct proc *p = myproc();
  if(argaddr(0, &info) < 0){
    return -1;
  }
  kinfo.freemem = freemem();
  kinfo.nproc = nproc();
  if(copyout(p->pagetable, info, (char*)&kinfo, sizeof(kinfo)) < 0){
    return -1;
  }
  return 0;
}

步骤3:编写应用工具(注:源代码中已提供了测试程序sysinfotest)

步骤4:修改Makefile,将$U/_sysinfotest添加到Makefile的UPROGS中。

步骤5:make qemu,然后测试。

         make grade

参考资料

[1] https://pdos.csail.mit.edu/6.828/2021/labs/syscall.html

[2] https://www.cnblogs.com/YuanZiming/p/14218997.html

  • 19
    点赞
  • 121
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
xv6添加系统调用需要进行以下步骤: 1. 在 `syscall.h` 文件中添加系统调用号。具体来说,你需要在 `SYS_hoge` 列表中添加一个新的系统调用号,其中 `hoge` 是你的系统调用的名称。例如,如果你的系统调用名称是 `mycall`,则可以在 `syscall.h` 文件中添加以下代码: ``` #define SYS_mycall 22 ``` 2. 在 `syscall.c` 文件中添加系统调用的具体实现。具体来说,你需要在 `syscalls[]` 数组中添加一个新的结构体,其中包含你的系统调用的名称和具体的实现函数。例如,如果你的系统调用名称是 `mycall`,则可以在 `syscall.c` 文件中添加以下代码: ``` static int sys_mycall(void) { // 在这里编写你的系统调用的具体实现 return 0; } static struct { int nargs; int (*func)(void); } syscalls[] = { { 0, sys_fork }, { 1, sys_exit }, { 0, sys_wait }, { 1, sys_pipe }, { 4, sys_read }, { 4, sys_write }, { 2, sys_close }, { 1, sys_mycall }, // 添加新的系统调用 }; ``` 3. 在 `user.h` 文件中添加用户空间的系统调用函数声明。具体来说,你需要添加一个新的函数声明,其中包含你的系统调用的名称和参数。例如,如果你的系统调用名称是 `mycall`,则可以在 `user.h` 文件中添加以下代码: ``` int mycall(void); ``` 4. 在用户程序中调用你的系统调用。具体来说,你可以在用户程序中调用你的系统调用函数,例如: ``` #include "user.h" int main(int argc, char *argv[]) { mycall(); // 调用你的系统调用 exit(); } ``` 5. 在内核态的系统调用函数中添加系统调用的具体实现。具体来说,你需要在 `syscall()` 函数中添加一个新的 `case` 分支,其中包含你的系统调用的具体实现。例如,如果你的系统调用名称是 `mycall`,则可以在 `syscall()` 函数中添加以下代码: ``` case SYS_mycall: argint(0, &arg1); // 在这里调用你的系统调用的具体实现 return 0; ``` 6. 最后,重新编译 xv6 并运行你的用户程序。在 xv6 的命令行下输入你的程序名称即可运行。例如,如果你的用户程序名称是 `myprog`,则可以通过输入 `myprog` 命令来运行它,其中包含了你的系统调用调用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lhw---9999

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值