mit6.s081 lab2: system calls

发布时间 2023-04-22 22:29:08作者: 白日梦想家-c

1.system call tracing(moderate)

要求:创建一个系统调用来实现跟踪特性,它采用一个参数来指定跟踪哪一个系统调用,例如:跟踪fork系统调用,程序调用trace(1<<SYS_fork),其中SYS_fork是kernel/syscall.h中的系统调用号。如果在掩码中设置了系统调用的编号,则必须修改须
xv6的内核,以便在每个系统调用即将返回时输出一行。这一行应包括进程id、系统调用的名称和返回值。不需要打印系统调用参数。用于跟踪的系统调用应能够跟踪调用它的进程以及随后的子进程,但不能影响其它的进程。
 
要实现的效果:
trace 32 grep hello README,表示执行grep hello README时,read系统调用发生时,跟踪并打印要求的东西。

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0

这里32表示的是1<<SYS_read的结果。
 
提示:

  • 在makefile中添加$U/_trace
  • 这个时候直接运行make qemu会发现无法编译user/trace.c,trace这个系统调用还没有实现,即在用户态下的存根还不存在。要在user/user.h中将系统调用声明出来(即int trace(int)),在user/usys.pl中添加存根(entry("trace")),在 kernel/syscall.h中添加系统调用号。
    这里Makefile 调用 perl 脚本 user/usys.pl,它生成 user/usys.S,即实际的系统调用存根,它使用 RISC-V 中的ecall回调指令转换到内核。
    在完成了上面的操作之后就算是修复了编译问题了,这时可以成功的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 ()函数以打印跟踪输出。您需要添加要索引到的系统调用名称数组。

 
可以知道的信息:
在根据提示完成相关的文件配置之后,可以知道目前需要在kernel/sysproc.c中来完成sys_trace()这个函数。
 
根据提示三以及参考sysproc.c中的其它函数,先大致做一个测试:

uint64
sys_trace(void)
{
    printf("sys_trace:hi\n");
    return 0;
}

当运行trace 32 grep hello README的时候可以看到输出就是我们在sys_trace中所打印的东西,就也意味着这里要实现trace系统调用被调用时要输出的东西。
 
再根据提示三中的从用户空间检索系统调用参数的函数在 kernel/syscall.c 中,转到syscall.c中最下面可以看到syscall函数.在这个函数中首先定义了一个整型的num来记录一个参数,当if判断不成功的时候它会打印“pid(进程号) name(所传入的调用名字):unknown sys call”这样的信息,那么大致可以知道当if判断成功的时候,应该就是对要执行的系统调用做处理,由于每次调用成功会去找到syscalls[num],那么可以推测是根据num来判断是哪一个系统调用,因此不妨再写一个存放系统调用名字的数组来打印一下试试

这时make qemu试一下,可以发现随着xv6的启动,调用的系统调用被打印出来,如下图所示:

 
这个时候就可以明确syscall是调用系统函数的一个入口,sys_trace是处理trace系统调用的地方,有了这两个函数就应该可以实现这个实验所要实现的效果。
 
在实现sys_trace函数的时候发现在其它的函数中都有这么一段代码:

int n;
if(argint(0, &n) < 0)
    return -1;

那么继续照猫画虎,把这一段搬到sys_trace中用输出调试一下,即printf("sys_trace:hi,n is %d\n",n);,可以发现这段代码是把用户态得到的掩码传给n,比如trace 32 grep hello README得到的n就是32,这个n用于决定要追踪的函数,那么现在的问题就是:如何让syscall也可以获得这个值,毕竟在syscall中需要这个掩码来打印对应得系统调用。
 
这个时候再返回到提示三中,有这样一句话:在 kernel/sysproc.c 中添加 sys _ trace ()函数,该函数通过在 proc 结构体中的新变量中记住参数来实现新的系统调用(参见 kernel/proc.h),按照这个提示,我们应该在proc中添加变量来实现两个函数共用这一掩码值,而proc结构体所存得是每一个进程的状态信息。
 
到了这里,基本功能都能实现了,但是我们发现提示4:“修改 fork ()(参见 kernel/proc.c) ,将跟踪掩码从父进程复制到子进程”好像一直没有用到,于是去查看proc.c中的fork,发现在这里面有两个结构体np和p,我们需要将父进程p的相关信息复制给np,于是添加一行:np->trace_mask=p->trace_mask;
 
实现:
对syscall的修改

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

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]();//返回值放在a0这个寄存器中
      int mask=p->trace_mask;
      if((mask>>num)&1){
          printf("%d: syscall %s -> %d\n",p->pid,syscall_names[num-1],p->trapframe->a0);
      }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

对sys_trace的修改:

uint64
sys_trace(void)
{
    int mask;//n是从用户态传进来的掩码,比如追踪的是fork传进来的就是32
    if(argint(0, &mask) < 0)
        return -1;
    //printf("sys_trace:hi,n is %d\n",mask);

    struct proc *p = myproc();
    p->trace_mask=mask;//把掩码放到进程信息中

    return 0;
}

 
补充:
在测试grep hello README的时候发现输出和预期的不太一样,在这里输出了上一次所跟踪的信息,后来发现在proc.c中根据注释发现有一个freeproc函数用于释放进程信息,我们需要在里面添加一行p->proc_mask=0

2.sysinfo(moderate)

要求:写一个sysinfo这个system call,需要获取当前空闲的内存大小填入struct sysinfo.freemem中,获取当前所有不是UNUSED的进程数量填入struct sysinfo.nproc中
 
提示:

  • 在makefile中添加$U/_sysinfotest
  • 运行 make qemu; user/sysinfotest.c 将无法编译。添加系统调用 sysinfo,步骤与前面的任务相同。要在 user/user.h 中声明 sysinfo ()的原型,需要预先声明 struct sysinfo 的存在:
struct sysinfo;
int sysinfo(struct sysinfo *);
  • Sysinfo 需要将一个 struct sysinfo 复制回用户空间; 请参阅 sys _ fstat ()(kernel/sysfile.c)和 filestat ()(kernel/file.c)以获得如何使用 copy out ()实现这一点的示例。
  • 要收集空闲内存量,请向 kernel/kalloc.c 添加一个函数
  • 要收集进程数,请向 kernel/proc.c 添加一个函数
  • 在sysproc.c中完成系统调用函数的编写,在syscall中记得对syscalls修改并extern
  • 和前面一样先要在user/user.h中声明system callint sysinfo(struct sysinfo *)以及struct sysinfo,在user/usys.pl中注册entry,在kernel/syscall.h中注册一个syscall number,在kernel/sysproc.c中对uint64 sys_sysinfo(void)进行实现,注意要在sysproc.c中添加头文件sysinfo.h
     

实现:
sysproc.c中实现系统调用

uint64
sys_sysinfo(void)
{
    struct sysinfo info;
    uint64 addr;
    struct proc *p=myproc();

    info.freemem=acquire_freemem();
    info.nproc=acquire_nproc();

    if(argaddr(0, &addr) < 0)//入参放到addr里
        return -1;

    if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)//函数运算后的info结果放到addr里
        return -1;

    return 0;
}

kalloc.c中实现获取空闲内存

uint64 acquire_freemem()
{
    struct run *r;

    uint64 cnt=0;

    acquire(&kmem.lock);
    r = kmem.freelist;
    while(r)
    {
        r=r->next;
        cnt++;//链表的长度就是页表的个数
    }
    release(&kmem.lock);

    return cnt*PGSIZE;//页表的个数乘以页表的大小就是整个系统空闲内存的大小
}

proc.c中实现获取使用进程数

uint64 acquire_nproc()
{
    struct proc *p;
    int cnt=0;

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

3测试结果