MIT 6.s081 Lab2 System calls

729 阅读3分钟

不同于lab1的几个实验,只需要在user下实现1个或几个功能函数即可,lab2开始涉及到xv6的内核,实现起来更为复杂。所以,一定要根据实验要求阅读完文档相关章节和源代码后再做实验。

System call tracing

添加系统调用sys_trace

该实验需要添加一个系统调用,首先根据提示,在user/user.h中添加一个原型,user/usys.pl中添加一个stub,kernel/syscall.h中增加trace的系统调用号。

#user/user.h
// system calls
...
int trace(int);//添加trace原型

#user/usys.pl
...
entry("trace");

#kernel/syscall.h
// System call numbers
...
#define SYS_trace  22

然后,在kernel/proc.hstruct proc中添加一个参数mask,并在kernel/sysproc.c中实现sys_trace(),获取mask值并给当前进程的mask赋值。阅读文档的4.3节可以得知,用户代码将exec需要的参数放在寄存器a0a1中,而4.4节告诉我们可以通过argint来获取寄存器里的值。

#kernel/proc.h
// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  struct proc *parent;         // Parent process
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
  int mask;                    // Tracing parameter
};

#kernel/sysproc.c
uint64
sys_trace(void)
{
  int mask;
  if(argint(0, &mask) < 0)
      return -1;
  myproc()->mask = mask;
  return 0;
}

具体实现trace

因为还需要输出跟踪的信息,所以还需要对kernel/syscall.c进行修改。

...
extern uint64 sys_trace(void);

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

char* syscalls_name[] = {
[SYS_fork]    "fork",
[SYS_exit]    "exit",
[SYS_wait]    "wait",
[SYS_pipe]    "pipe",
[SYS_read]    "read",
[SYS_kill]    "kill",
[SYS_exec]    "exec",
[SYS_fstat]   "fstat",
[SYS_chdir]   "chdir",
[SYS_dup]     "dup",
[SYS_getpid]  "getpid",
[SYS_sbrk]    "sbrk",
[SYS_sleep]   "sleep",
[SYS_uptime]  "uptime",
[SYS_open]    "open",
[SYS_write]   "write",
[SYS_mknod]   "mknod",
[SYS_unlink]  "unlink",
[SYS_link]    "link",
[SYS_mkdir]   "mkdir",
[SYS_close]   "close",
[SYS_trace]   "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]();
    if((1 << num) & p->mask){//mask的二进制下为1的数位表示需要跟踪的进程
    	printf("%d: syscall %s -> %d\n",
    	       p->pid, syscalls_name[num], p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

之后,还需要对kernel/proc.c进行修改。

  • 修改fork(),让子进程继承父进程的mask
  • 此外,未调用trace时mask应该为0,否则可能会在正常运行其他调用时输出跟踪信息,需要修改allocproc()
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;
  np->mask = p->mask;
  np->parent = p;

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;

  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);

  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;

  np->state = RUNNABLE;

  release(&np->lock);

  return pid;
}

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;

found:
  p->pid = allocpid();

  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;
  p->mask = 0;//给每个进程mask赋个初值即可。

  return p;
}

运行结果

image.png

Sysinfo

实现sys_sysinfo

根据题意,仿照sys_fstat() (kernel/sysfile.c)和 filestat() (kernel/file.c)即可实现sys_sysinfo()的核心功能,把内核中的结构体sysinfo拷贝到用户区的指针中。

uint64
sys_sysinfo(void)
{
  uint64 addr;
  struct sysinfo sinfo;
  if(argaddr(0, &addr) < 0)
    return -1;
  sinfo.freemem = getfreemem();
  sinfo.nproc = getnproc();
  if(copyout(myproc()->pagetable, addr, (char *)&sinfo, sizeof(sinfo)) < 0)
    return -1;
  return 0;
}

获取空闲内存和空闲进程

接下来,完善getfreemem()getnproc()这两个函数。

getfreemem()

根据提示查看kernel/kalloc.c文件,从中可以看出,xv6系统维护着一个kmem.freelist链表来存储空闲页,遍历kmem.freelist即可计算出空闲内存。因为要遍历该链表,为防止冲突,需要先获取lock,计算完后再release

uint64
getfreemem(void)
{
  uint64 freemem = 0;
  struct run *r;
  acquire(&kmem.lock);
  r = kmem.freelist;
  while(r){
    freemem += PGSIZE;//若r存在,加上页的内存大小
    r = r->next;
  }
  release(&kmem.lock);
  return freemem;
}

getnproc()

根据提示查看kernel/proc.c,其内部定义了struct proc proc[NPROC],所以我们只要遍历这个数组,统计其中state != UNUSED的个数即可。

uint64 
getnproc(void)
{
  uint64 res = 0;
  struct proc *p;
  for(p = proc; p < &proc[NPROC]; p++){
    if(p->state != UNUSED) res++;
  }
  return res;
}

添加系统调用和函数声明

系统调用添加方式和trace一样,这里不再赘述。
因为在kernel/kalloc.ckernel/proc.c增加了getfreemem()getnproc()这两个函数,所以需要在defs.h中进行声明。
此外,kernel/sysproc.c原本没有#include "sysinfo.h",需要自行添加

运行结果

image.png

Make grade

image.png