Skip to content

Latest commit

 

History

History
425 lines (323 loc) · 12.4 KB

Lab System calls.md

File metadata and controls

425 lines (323 loc) · 12.4 KB

Lab System calls

先切到syscall分支:

$ git fetch
$ git checkout syscall
$ make clean

System call tracing

原文要求

In this assignment you will add a system call tracing feature that may help you when debugging later labs. You'll create a new trace system call that will control tracing. It should take one argument, an integer "mask", whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from kernel/syscall.h. You have to modify the xv6 kernel to print out a line when each system call is about to return, if the system call's number is set in the mask. The line should contain the process id, the name of the system call and the return value; you don't need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.

几点Hints:

  • Add $U/_trace to UPROGS in Makefile

  • Run make qemu and you will see that the compiler cannot compile user/trace.c, because the user-space stubs for the system call don't exist yet: add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h. The Makefile invokes the perl script user/usys.pl, which produces user/usys.S, the actual system call stubs, which use the RISC-V ecall instruction to transition to the kernel. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven't implemented the system call in the kernel yet.

  • Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.

  • Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.

  • Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.

  • If a test case passes when you run it inside qemu directly but you get a timeout when running the tests using make grade, try testing your implementation on Athena. Some of tests in this lab can be a bit too computationally intensive for your local machine (especially if you use WSL).

逐一理解,首先Makefile文件就不多说了

$U/_trace\

第二个hint需要我们在user/user.huser/usys.plkernel/syscall.h添加相应的内容,可以根据上文来推测。

in user/user.h

// system calls
int fork(void);
......
......
int uptime(void);
// add a prototype for the system call
int trace(int);

in user/usys.pl

......
entry("uptime");
# a stub
entry("trace");

in kernel/syscall.h

// System call numbers
#define SYS_fork  1
......
......
#define SYS_trace 22

完成上述操作后,运行trace 32 grep hello README仍然无法成功,因为在内核中的系统调用还没有实现。

第三个hint很重要,

Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h).

解释: 在kernel/sysproc.c中添加一个sys_trace()函数,这个函数来实现这个新的系统调用。而这个新的系统调用,是通过在proc结构体中的新变量中来记住它的参数。

也就是说,要做两件事,一个是在kernel/sysproc.c中添加一个sys_trace()函数;一个是要在proc结构体中添加新的变量。

接下来看第三个hint的第二句话,

The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.

kernel/syscall.c中有很多 从用户空间 检索/获取(retrieve)系统调用参数 的函数,具体例子可以参考kernel/sysproc.c

通过阅读kernel/syscall.c可以发现有很多函数

int fetchaddr(uint64 addr,uint64 *ip);
int fetchstr(uint64 addr,char* buf,int max);
static uint64 argraw(int n);
void argint(int n,int* ip);
void argaddr(int n,uint64 *ip);
int argstr(int n,char* buf,int max);

阅读这些函数的注释,结合我们需要的命令行trace 32 grep hello README可以看见,我们主要是要获取32这个参数,所以我着重阅读了argint()函数。而第三个hint第二句话也说明了在kernel/sysproc.c中有些许使用argint()的例子。

大概的感性认识就是,argint(int n,int* ip)将系统调用参数中第n个32bit数存储到ip中。

那么我根据提示,先在kernel/sysproc.c中添加sys_trace()函数:

uint64
sys_uptime(void)
{
    ...
}

//add a sys_trace()
uint64
sys_trace(void)
{
    printf("sys_trace: test");//目前还不知道填什么
}

其实这里可以试着在xv6系统中运行一下trace 32 grep hello README可以发现这一行起作用了。

跟着提示,再去kernel/proc.h中阅读一下proc结构体。到这里就有些许眉头了,在proc结构体中可以发现一个进程的一些属性,比如pid,该进程的父进程等等。这个Lab让我们实现trace,那么需要的就是trace mask了,用于确定具体哪一个系统调用需要追踪。比如trace 32 grep hello README中,32就是1 << SYS_read也就是1左移5位,表示追踪read系统调用。

那么我们在proc结构体中添加trace_mask

struct proc {
    struct spinlock lock;
    ......
    int pid;
    struct proc *parent;
    ......
    //trace mask
    int trace_mask;
};

接着到kernel/syscall.c中先做些简单工作。

// Prototypes for the functions that handle system calls.
extern unit64 sys_fork(void);
......
extern uint64 sys_trace(void);

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

提示还说,可以从kernel/sysproc.c中阅读如何获取参数的例子。

上文我提及了argint(),另外一个就是用myproc()函数来获取当前进程的相关属性。

在填写sys_trace()函数之前,我们先暂停,继续往下看hint

第四个hint照做就行,读到fork()后有一种恍然大悟的感觉,这里就是父子进程共享内存段的代码实现。

我们把proc结构体中的新变量trace_mask复制给子进程。

int 
fork(void)
{
    int i,pid;
    struct proc *np;
    struct proc *p = myproc();
    ......
    // copy trace mask
    np->trace_mask=p->trace_mask;
    ......
}

第五个hint就是真正实现trace了,在这里先捋一下整个trace的过程。

首先trace 32 grep hello README中,

用户态运行trace 32,在user/trace.c中的main函数可以看见调用了trace()函数。

然后就从用户态进入到内核态,执行sys_trace()函数,这个函数通过某种方式把参数(mask)传给syscall()函数,然后syscall()函数输出结果,这个结果包括当前进程的pid,系统调用的名字及其返回值.

这里所说的"通过某种方式"其实已经明了,使用myproc()对当前进程的proc结构体中的trace_mask赋值即可.

in kernel/sysproc.c:

uint64
sys_trace(void)
{
    struct proc *p = myproc();

    int mask;
    argint(0, &mask);

    p->trace_mask=mask;
    return 0;
}

in kernel/syscall.c:

// syscall_names
char* syscall_names[] = {
    "fork",
    "exit",
    ......
    "trace",
}


void
syscall(void)
{
    int num;
    struct proc *p = myproc();

    num = p->trapframe->a7;
    if(num > 0 && num < NELEM(syscalls) && syscalls[num] ) {
        //这里num是系统调用的数 比如SYS_read是5,
        //然后syscalls[nums]()返回的是系统调用的返回值,存到a0寄存器里面.
        p->trapframe->a0 = syscalls[nums]();

        // solution
        if( (p->trace_mask >> num) & 1 ){
            printf("%d: syscall %s -> %d\n",p->pid,syscall_names[num - 1],p->trapframe->a0);
        }
    }else{
        ......
    }
}

经检验,通过测试.

Sysinfo

原文要求:

In this assignment you will add a system call, sysinfo, that collects information about the running system. The system call takes one argument: a pointer to a struct sysinfo (see kernel/sysinfo.h). The kernel should fill out the fields of this struct: the freemem field should be set to the number of bytes of free memory, and the nproc field should be set to the number of processes whose state is not UNUSED. We provide a test program sysinfotest; you pass this assignment if it prints "sysinfotest: OK".

前两点提示与trace实验类似,在Makefile里面添加一行,在user/user.huser/usys.plkernel/syscall.h以及kernel/syscall.c中添加类似的代码即可。

注意在user/user.h中要添加一个

struct sysinfo;
int sysinfo(struct sysinfo*);

到这里编译没有问题。

查看sysinfo结构体内部,有两个东西。一个是freemem一个是nproc(其状态不能是UNUSED)

接下来第三个提示就比较重要。

目的是把结构体sysinfo从内核空间复制到用户空间。而需要我们借鉴sys_fstat()filestat()两个函数的实例,来学会使用copyout()

copyout()个人理解:

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

按照注释也能感性认识,从src中取len字节数据传给dstva(destination vitural address)的用户空间。

那么结合sys_fstat()filestat(),在kernel/sysproc.c把函数写完。

uint64
sys_sysinfo(void)
{
    struct sysinfo si;// sysinfo
    uint64 addr; // user pointer to struct sysinfo
    struct proc* p = myproc();

    argaddr(0, &addr);// only one argument: struct sysinfo* ,so we use argaddr()

    // waiting for implement
    si.freemem = -1;
    si.nproc = -1;

    // copy from kernel to user space;
    if(copyout(p->pagetable, addr, (char*)&si, sizeof(si)) < 0){
        return -1;
    }

    return 0;
}

第四第五个提示也很明确了,前往kernel/kalloc.ckernel/proc.c添加两个函数,获取sysinfo结构体中的两个变量:freememnproc即可。

in kernel/kalloc.c:

这个文件中kmem是全局的结构体变量,内部有个锁lock,以及一个链表freelist;我们数完freelist之后,数量乘以每页的大小(4096)即可。(注意看kalloc()函数,分配以PGSIZE为单位)

uint64
collect_freemem(void)
{
    struct run *r;
    uint64 cnt = 0;// number of freemem

    acquire(&kmem.lock);
    r = kmem.freelist;

    while(r){
        r = r->next;
        cnt++;
    }
    release(&kmem.lock);

    return PGSIZE * cnt;// bytes per page times cnt;
}

in kernel/proc.c:

数进程数很简单,注意是‘非UNUSED’进程。阅读这个文件可以知道有个进程池类似的概念(变量proc数组)就从这里数。

uint64
collect_nproc(void)
{
    struct proc* p;
    uint64 cnt = 0;

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

把这俩实现的函数在kernel/sysproc.c中声明,然后在sys_sysinfo()中调用即可。

uint64 collect_freemem(void);
uint64 collect_nproc(void);

......

uint64
sys_sysinfo(void)
{
    struct sysinfo si;// sysinfo
    uint64 addr; // user pointer to struct sysinfo
    struct proc* p = myproc();

    argaddr(0, &addr);// only one argument: struct sysinfo* ,so we use argaddr()

    // implement
    si.freemem = collect_freemem();
    si.nproc = collect_nproc();

    // copy from kernel to user space;
    if(copyout(p->pagetable, addr, (char*)&si, sizeof(si)) < 0){
        return -1;
    }

    return 0;
}

运行sysinfotest这个实验就此结束了。