6.S081的Lab学习——Lab2: system calls


前言

一个本硕双非的小菜鸡,备战24年秋招。打算尝试6.S081,将它的Lab逐一实现,并记录期间心酸历程。
代码下载

官方网站:6.S081官方网站

安装方式:
通过 APT 安装 (Debian/Ubuntu)
确保你的 debian 版本运行的是 “bullseye” 或 “sid”(在 ubuntu 上,这可以通过运行 cat /etc/debian_version 来检查),然后运行:

sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

(“buster”上的 QEMU 版本太旧了,所以你必须单独获取。

qemu-system-misc 修复
此时此刻,似乎软件包 qemu-system-misc 收到了一个更新,该更新破坏了它与我们内核的兼容性。如果运行 make qemu 并且脚本在 qemu-system-riscv64 -machine virt -bios none -kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 之后出现挂起

则需要卸载该软件包并安装旧版本:

  $ sudo apt-get remove qemu-system-misc
  $ sudo apt-get install qemu-system-misc=1:4.2-3ubuntu6

在 Arch 上安装

sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-arch-extra

测试您的安装
若要测试安装,应能够检查以下内容:

$ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc (GCC) 10.1.0
...

$ qemu-system-riscv64 --version
QEMU emulator version 5.1.0

您还应该能够编译并运行 xv6: 要退出 qemu,请键入:Ctrl-a x。

# in the xv6 directory
$ make qemu
# ... lots of output ...
init: starting sh
$

一、System call tracing(moderate)

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

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$
$ grep hello README
$
$ trace 2 usertests forkforkfork
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
$

提示:

  1. 在Makefile的UPROGS中添加$U/_trace
  2. 运行make qemu,您将看到编译器无法编译user/trace.c,因为系统调用的用户空间存根还不存在:将系统调用的原型添加到user/user.h,存根添加到user/usys.pl,以及将系统调用编号添加到kernel/syscall.h,Makefile调用perl脚本user/usys.pl,它生成实际的系统调用存根user/usys.S,这个文件中的汇编代码使用RISC-V的ecall指令转换到内核。一旦修复了编译问题(注:如果编译还未通过,尝试先make clean,再执行make qemu),就运行trace 32 grep hello README;但由于您还没有在内核中实现系统调用,执行将失败。
  3. 在kernel/sysproc.c中添加一个sys_trace()函数,它通过将参数保存到proc结构体(请参见kernel/proc.h)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。
  4. 修改fork()(请参阅kernel/proc.c)将跟踪掩码从父进程复制到子进程。
  5. 修改kernel/syscall.c中的syscall()函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。

虽然题目中说需要切换工作分支,但是可以不这么做。换了分支还容易丢,只需要手动在/user文件夹里加上trace.c和sysinfotest.c两个缺乏的文件即可。
还要去/kernel文件夹里加入一个sysinfo.h文件

trace.c文件:

#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
  int i;
  //存储待跟踪程序的名称和参数
  char *nargv[MAXARG];

  //保证trace的参数不少于三个,并且跟踪的系统调用号在0-99之间
  if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){
    fprintf(2, "Usage: %s mask command\n", argv[0]);
    exit(1);
  }
  //调用trace系统调用,传入待跟踪系统调用号
  if (trace(atoi(argv[1])) < 0) {
    fprintf(2, "%s: trace failed\n", argv[0]);
    exit(1);
  }
  //保存待跟踪程序的名称和参数
  for(i = 2; i < argc && i < MAXARG; i++){
    nargv[i-2] = argv[i];
  }
  //运行待跟踪的程序
  exec(nargv[0], nargv);
  exit(0);
}

sysinfotest.c:

#include "kernel/types.h"
#include "kernel/riscv.h"
#include "kernel/sysinfo.h"
#include "user/user.h"
 
 
void
sinfo(struct sysinfo *info) {
  if (sysinfo(info) < 0) {
    printf("FAIL: sysinfo failed");
    exit(1);
  }
}
 
//
// use sbrk() to count how many free physical memory pages there are.
//
int
countfree()
{
  uint64 sz0 = (uint64)sbrk(0);
  struct sysinfo info;
  int n = 0;
 
  while(1){
    if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){
      break;
    }
    n += PGSIZE;
  }
  sinfo(&info);
  if (info.freemem != 0) {
    printf("FAIL: there is no free mem, but sysinfo.freemem=%d\n",
      info.freemem);
    exit(1);
  }
  sbrk(-((uint64)sbrk(0) - sz0));
  return n;
}
 
void
testmem() {
  struct sysinfo info;
  uint64 n = countfree();
 
  sinfo(&info);
 
  if (info.freemem!= n) {
    printf("FAIL: free mem %d (bytes) instead of %d\n", info.freemem, n);
    exit(1);
  }
 
  if((uint64)sbrk(PGSIZE) == 0xffffffffffffffff){
    printf("sbrk failed");
    exit(1);
  }
 
  sinfo(&info);
 
  if (info.freemem != n-PGSIZE) {
    printf("FAIL: free mem %d (bytes) instead of %d\n", n-PGSIZE, info.freemem);
    exit(1);
  }
 
  if((uint64)sbrk(-PGSIZE) == 0xffffffffffffffff){
    printf("sbrk failed");
    exit(1);
  }
 
  sinfo(&info);
 
  if (info.freemem != n) {
    printf("FAIL: free mem %d (bytes) instead of %d\n", n, info.freemem);
    exit(1);
  }
}
 
void
testcall() {
  struct sysinfo info;
 
  if (sysinfo(&info) < 0) {
    printf("FAIL: sysinfo failed\n");
    exit(1);
  }
 
  if (sysinfo((struct sysinfo *) 0xeaeb0b5b00002f5e) !=  0xffffffffffffffff) {
    printf("FAIL: sysinfo succeeded with bad argument\n");
    exit(1);
  }
}
 
void testproc() {
  struct sysinfo info;
  uint64 nproc;
  int status;
  int pid;
 
  sinfo(&info);
  nproc = info.nproc;
 
  pid = fork();
  if(pid < 0){
    printf("sysinfotest: fork failed\n");
    exit(1);
  }
  if(pid == 0){
    sinfo(&info);
    if(info.nproc != nproc+1) {
      printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc+1);
      exit(1);
    }
    exit(0);
  }
  wait(&status);
  sinfo(&info);
  if(info.nproc != nproc) {
      printf("sysinfotest: FAIL nproc is %d instead of %d\n", info.nproc, nproc);
      exit(1);
  }
}
 
int
main(int argc, char *argv[])
{
  printf("sysinfotest: 0 start\n");
  testcall();
  printf("sysinfotest: 1\n");
  testmem();
  printf("sysinfotest: 2\n");
  testproc();
  printf("sysinfotest: OK\n");
  exit(0);
}

sysinfo.h文件

struct sysinfo {
  uint64 freemem;   // amount of free memory (bytes)
  uint64 nproc;     // number of process
};

解析:

想要实现上述例程,根据提示在目标文件中模仿着写就行。
在Makefile的UPROGS中添加$U/_trace。
首先需要在user/user.h 中添加
int trace(int);
然后在 user/usys.pl 中添加:
entry(“trace”);
最后在 kernel/syscall.h 中添加:
#define SYS_trace 22
这三项写完之后我们可以看到可以编译通过了,但是没有在内核中实现系统调用,执行将失败
在这里插入图片描述
然后进行下一步,在kernel/sysproc.c中添加一个sys_trace()函数
这个函数的作用是通过将参数保存到proc结构体里的一个新变量中来实现新的系统调用。
则先对kernel/proc.h中的结构体proc进行新变量的添加

int trace_mask;

这个结构体其实就是记录每个进程状态的结构体。
然后编写sys_trace()函数,核心就是给我们刚才的新变量赋值。(仿写就好)

uint64
sys_trace(void)
{
  int mask;

  if(argint(0, &mask) < 0)
    return -1;
  myproc()->trace_mask = mask;
  return 0;
}

注:argint函数从trap页面读出int,这个trap是RISC-V的重要的控制寄存器,具体看文章吧。

接下来去kernel/proc.c修改fork(),目标是将跟踪掩码从父进程复制到子进程
很简单,加一句就成

  safestrcpy(np->name, p->name, sizeof(p->name));
  /*添加*/
  np->trace_mask = p->trace_mask;

  pid = np->pid;

最后修改kernel/syscall.c中的syscall()函数以打印跟踪输出。需要添加一个系统调用名称数组以建立索引。

按照格式先把引用变量加上

.......
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
......
[SYS_close]   sys_close,
[SYS_trace]   sys_trace,
};

再加上系统调用名称数组,对应的成员就是上面里的成员。

char *syscall_name[22] = {"fork", "exit", "wait", "pipe", "read",
                           "kill", "exec", "fstat", "chdir", "dup",
                           "getpid", "sbrk", "sleep", "uptime", "open",
                           "write", "mknod", "unlink", "link", "mkdir",
                           "close", "trace"};

在成功执行了对应的系统调用之后,判断这个系统调用是否需要trace打印

f(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();
    if (p->trace_mask & (1 << num))
      printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }

完成,还是比较成功的!
在这里插入图片描述

二、Sysinfo(moderate)

在这个作业中,您将添加一个系统调用sysinfo,它收集有关正在运行的系统的信息。系统调用采用一个参数:一个指向struct sysinfo的指针(参见kernel/sysinfo.h)。内核应该填写这个结构的字段:freemem字段应该设置为空闲内存的字节数,nproc字段应该设置为state字段不为UNUSED的进程数。我们提供了一个测试程序sysinfotest;如果输出“sysinfotest: OK”则通过。

提示:

在Makefile的UPROGS中添加$U/_sysinfotest
当运行make qemu时,user/sysinfotest.c将会编译失败,遵循和上一个作业一样的步骤添加sysinfo系统调用。要在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中添加一个函数。

解析:

还是一样的操作:
在Makefile的UPROGS中添加$U/_sysinfotest。
首先在user/user.h加入函数声明

struct stat;
struct rtcdate;
struct sysinfo;
......
void *memcpy(void *, const void *, uint);
int trace(int);
int sysinfo(struct sysinfo *);

然后在 user/usys.pl 中添加:
entry(“sysinfo”);
最后在 kernel/syscall.h 中添加:
#define SYS_sysinfo 23
这三项写完之后我们可以看到可以编译通过了,但是没有在内核中实现系统调用,执行将失败
在这里插入图片描述
接下来可以先写sysinfo这块,也可以先搞定取空闲内存量和获取进程数这两个函数。我先从后往前写了

空闲内存的函数在kernel/kalloc.c中添加。根据该文件的代码以及我对内存管理的浅薄理解:内存分配底层是通过链表实现的(具体详见侯捷老师的内存管理课程)。可以参考kalloc函数去写。
kalloc函数:

void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  //核心在这里:从 kmem 结构的 freelist 成员中获取一个指向空闲内存块的指针,并将其赋值给 r。如果 r 不为空(即该空闲内存块存在),则更新 kmem.freelist 指向 r 的下一个内存块。这样,r 所指向的内存块就被从空闲列表中移除了。
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

问题变得简单了,求这个freelist链表有多长。
则:

uint64
freemem(void)
{
  struct run *r;
  uint64 nums = 0;

  acquire(&kmem.lock);
  r = kmem.freelist;
  
  while (r) {
  	r = r->next;
  	nums++;
  }
  release(&kmem.lock);
  // 注意内存分配是按页大小分配的,即4096个字节,所以我们输出的时候要乘4096,这样才是空闲的字节数
  return nums * PGSIZE;
}

接下来在在kernel/proc.c中添加获取进程数函数
可以参考同文下的allocproc函数,主要是遍历进程控制块数组

uint64
collectproc()
{
  struct proc *p;
  uint64 nums = 0;

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    //如果进程块已使用,就++
    if(p->state != UNUSED) {
      n++;
    }
    release(&p->lock);
  }
  return nums;
}

这两个函数需要添加到kernel/defs.h中

//kernel/kalloc.c
uint64		freemem(void);

//kernel/proc.c
uint64		collectproc();

最后写系统调用函数sys_sysinfo

//记着加头文件
#include "sysinfo.h"

uint64
sys_sysinfo(void)
{
  struct sysinfo info;
  uint64 addr;
  argaddr(0, &addr);
  info.freemem = freemem();
  info.nproc = procnum();
  //将 info 结构的内容复制到用户空间的地址 addr
  if(copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0)
    return -1;
  return 0;
}

忘记按着上个题目配置了。。。
在syscall.c中添加新的系统调用,跟上题一样

......
extern uint64 sys_trace(void);
extern uint64 sys_sysinfo(void);
.......
[SYS_trace]   sys_trace,
[SYS_sysinfo] sys_sysinfo,
.......
char *syscall_name[23] = {"fork", "exit", "wait", "pipe", "read",
                           "kill", "exec", "fstat", "chdir", "dup",
                           "getpid", "sbrk", "sleep", "uptime", "open",
                           "write", "mknod", "unlink", "link", "mkdir",
                           "close", "trace", "sysinfo"};
.......

最后也是成功输出
在这里插入图片描述

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力找工作的小菜鸡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值