Lab2: system calls 引导

Lab2: system calls 引导

System call tracing(moderate)

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

#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];

  if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){
    fprintf(2, "Usage: %s mask command\n", argv[0]);
    exit(1);
  }

  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);
}

仔细阅读trace.c可以发现,它需要的参数是一个int型,猜测是 系统调用号

Add $U/_trace

  • 运行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;但由于您还没有在内核中实现系统调用,执行将失败。
  • 在***kernel/sysproc.c***中添加一个sys_trace()函数,它通过将参数保存到proc结构体(请参见***kernel/proc.h***)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在***kernel/syscall.c***中,您可以在***kernel/sysproc.c***中看到它们的使用示例。
  • 修改fork()(请参阅***kernel/proc.c***)将跟踪掩码从父进程复制到子进程。
  • 修改***kernel/syscall.c***中的syscall()函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。

第一步

**user/usys.pl 文件中的代码 **

print "# generated by usys.pl - do not edit\n";

print "#include \"kernel/syscall.h\"\n";

sub entry {
    my $name = shift;
    print ".global $name\n";
    print "${name}:\n";
    print " li a7, SYS_${name}\n";
    print " ecall\n";
    print " ret\n";
}

下文是本人粗浅的理解

第8行是将 系统调用号(估计是头文件中kernel/syscall.h 中定义的 数字) 载入 a7 寄存器
第9行ecall指令 进行 系统调用

所以第一步 按照要求完成即可
将系统调用的原型添加到***user/user.h***,存根添加到***user/usys.pl***,以及将系统调用编号添加到***kernel/syscall.h***

第二步

查看***kernel/sysproc.c***中的函数

uint64
sys_kill(void)
{
  int pid;

  if(argint(0, &pid) < 0)
    return -1;
  return kill(pid);
}

根据 kill 模仿 写出 trace ,用argint读取参数,并保存到proc结构体

不过首先得添加一个新变量在proc中

第三步

因为打印的该行应该包含进程id、系统调用的名称和返回值

所以应该在fork中传递参数(copy the trace mask from the parent to the child process) 以便 通过掩码显示系统调用的名称

要传递的参数就是在proc添加的新变量

第四步 打印

static uint64 (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
[SYS_exit]    sys_exit,
[SYS_wait]    sys_wait,
[SYS_pipe]    sys_pipe,
[SYS_read]    sys_read,
[SYS_kill]    sys_kill,
[SYS_exec]    sys_exec,
[SYS_fstat]   sys_fstat,
[SYS_chdir]   sys_chdir,
[SYS_dup]     sys_dup,
[SYS_getpid]  sys_getpid,
[SYS_sbrk]    sys_sbrk,
[SYS_sleep]   sys_sleep,
[SYS_uptime]  sys_uptime,
[SYS_open]    sys_open,
[SYS_write]   sys_write,
[SYS_mknod]   sys_mknod,
[SYS_unlink]  sys_unlink,
[SYS_link]    sys_link,
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
[SYS_trace]   sys_trace, // trace
};

我们可以先来看一下这个东西,本质上是一个函数指针数组初始化,传入int,调用函数

void
syscall(void)
{
  int num;
  struct proc *p = myproc();
  
  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num](); //系统调用命令 trace
    /*
                 这里写代码
    */
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

所以在看这个函数,我们只需要在里面添加一个 函数名称数组,判断是否是trace调用并按照要求完成输出即可

到这里就完成了 lab2

总结一下

这个东西就是想让我们了解一个系统调用的全过程

画一张图(可能与实际略有出入,不太肯定),应该就是trace的全流程(细节不太肯定)

Lab2: system calls 引导

Sysinfo(moderate)

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

添加$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 中添加一个函数

第一步

遵循和上一个作业一样的步骤添加sysinfo系统调用

1、将系统调用的原型添加到***user/user.h***同时声明 struct sysinfo
2、存根添加到***user/usys.pl***,
3、以及将系统调用编号添加到***kernel/syscall.h***
4、调整***syscall.c*** 在***kernel/sysproc.c***中添加一个sys_sysinfo()函数

第二步

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

sys_fstat()filestat()的copyout(),可以发现这sys_fstat 系统调用的功能将一个 将一个struct file复制回用户空间,所以理论上sysinfo模仿这个调用写即可,

int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len);

uint64
sys_fstat(void)
{
  struct file *f;
  uint64 st; // user pointer to struct stat

  if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
    return -1;
  return filestat(f, st);
}
// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
int
filestat(struct file *f, uint64 addr)
{
  struct proc *p = myproc();
  struct stat st;
  
  if(f->type == FD_INODE || f->type == FD_DEVICE){
    ilock(f->ip);
    stati(f->ip, &st);
    iunlock(f->ip);
    if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) //重点
      return -1;
    return 0;
  }
  return -1;
}

第三步

kernel/kalloc.c中添加一个函数

分配器从哪里获得内存来填充该数据结构呢?它将每个空闲页的run结构存储在空闲页本身,因为在那里没有存储其他东西。空闲列表受到自旋锁(spin lock)的保护(kernel/kalloc.c:21-24)。列表和锁被封装在一个结构体中,以明确锁在结构体中保护的字段。 参考书第三章内容

函数kfree (kernel/kalloc.c:47)首先将内存中的每一个字节设置为1。这将导致使用释放后的内存的代码(使用“悬空引用”)读取到垃圾信息而不是旧的有效内容,从而希望这样的代码更快崩溃。然后kfree将页面前置(头插法)到空闲列表中:它将pa转换为一个指向struct run的指针r,在r->next中记录空闲列表的旧开始,并将空闲列表设置为等于r

获取空闲内存量,要先读一读kallo.c的变量信息,根据参考书目可以写出函数返回空闲内存量

struct run { //空闲页
  struct run *next;
};

struct { //空闲链表
  struct spinlock lock;
  struct run *freelist;
} kmem;
uint64 freeMem(void) {
  struct run *pg = kmem.freelist;
  uint64 n = 0;
  while(pg) { //计算页的数量
    n++;
    pg = pg->next;
  }
  return n * PGSIZE;
}

第四步

kernel/proc.c中添加一个函数,获取进程数

struct proc proc[NPROC]; //最大进程数为64

这个数组就保存着所有的进程,其中没用的是UNUSED,遍历即可得到进程数

模仿allocproc中寻找UNUSED的代码即可

// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;

故所写代码如下

uint64 procNum(void) {
  uint64 n = 0;
  struct proc *p;
  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state != UNUSED) n++;
    release(&p->lock);
  }
  return n;
}

第五步

kernel/sysproc.c 按要求实现 函数 sys_sysinfo()

struct sysinfo {
  uint64 freemem;   // amount of free memory (bytes)
  uint64 nproc;     // number of process
};
uint64
sys_sysinfo(void) {
  uint64 addr;
  struct sysinfo info;
  struct proc *p = myproc();
  
  if(argaddr(0, &addr) < 0)
    return -1;
  
  info.freemem = freeMem();
  info.nproc = procNum();

  if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
      return -1;

  return 0;
}

上一篇:kernel图显系统中DDC的代码实现原理


下一篇:网络中的网络(NiN)