MIT 6.S081 Lab2

247 阅读4分钟

Lab: system calls

该Lab需要为xv6实现几个系统调用。

System call tracing

trace系统调用,为每个进程设定一个标记为mask,根据mask决定trace哪些系统调用。

系统调用整个流程如下:

  1. user/user.h:用户态程序调用跳板函数
  2. user/usys.S:跳板函数的实现为脚本生成的汇编,内部调用CPU的ecall指令,进入内核态
  3. kernel/syscall.c:进入内核态的统一处理函数syscall(),在这里根据系统调用编号,查找syscalls表,获取对应的函数指针
  4. kernel/sysproc.c:这里会进入sys_trace()函数,因为与进程有关,所以实现在sysproc.c里,最终就会跳转到这并执行

所以添加一个系统调用的流程如下:

  1. 在user.user.h中注册
  2. 在user/usys.pl中增加系统调用存根 entry("XXXX")
  3. 在kernel/syscall.h中定义系统调用号 #define SYS_XXXXX XX
  4. 在kernel/syscall.c中使用extern定义系统调用函数,并将其调用号-调用函数指针的映射关系加入syscalls中
  5. 在kernel中实现系统调用
  6. 若在用户态存在对应用户程序的,需在user下定义XX.c并加入Makefile

根据这个步骤完成trace,有如下改动:

为进程结构体增加标记位,以标记需要trace的调用号

//kernel/proc.h
struct proc {
  ...
  uint64 syscall_trace;        // Mask syscall trace
};

增加trace系统调用号定义

//kernel/syscall.h
#define SYS_trace  22

在syscall.c中添加trace打印时所需的检索数组,声明sys_trace函数,并将其函数指针与调用号的映射关系加入syscalls中,并且由于trace的功能是要求我们打印所有被标记的系统调用,需要在调用处实现,在syscall函数中添加打印逻辑

//kernel/syscall.c
const char *syscall_names[] = { /* trace打印时检索的调用名称 */
[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",
[SYS_sysinfo] "sysinfo",
};

extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
    ...
    [SYS_sysinfo] sys_sysinfo,
};

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 (p->syscall_trace >> num & 1)
    {
      printf("%d: syscall %s -> %d\n",
             p->pid, syscall_names[num], p->trapframe->a0);
    }
  }
  else
  {
    printf("%d %s: unknown sys call %d\n",
           p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

usys.pl中添加存根

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

user.h中添加定义

// user/user.h
int trace(int);

在进程创建时,为其syscall_trace赋初值0

// kernel/proc.c
static struct proc *
allocproc(void)
{
...
  p->syscall_trace = 0;
  return p;
}

在syspro.c中实现sys_trace

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

在fork中,使子进程可以继承父进程的syscall_trace

// kernel/proc.c
int fork(void)
{
...
  np->syscall_trace = p->syscall_trace;
...
  return pid;
}

Makefile

    $U/_trace\

修改完成,编译测试,结果如下:

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 968
3: syscall read -> 235
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 -> 968
4: syscall read -> 235
4: syscall read -> 0
4: syscall close -> 0
...

Sysinfo

创建系统调用,返回空闲内存和已创建的进程数量。

整体步骤与前一个实验相同,这里单独创建2个系统函数用于获取空闲内存数量和进程数量。

在kernel/defs.h中添加定义

uint64          count_free_mem(void);
uint64          count_process(void);

分别在kalloc.c和proc.c中实现

// kernel/kalloc.c
uint64 count_free_mem(void)
{
  acquire(&kmem.lock); /* 获取内存管理的锁 */

  uint64 mem_bytes = 0; /* 统计空闲页数,乘以页大小即空闲内存字节数 */
  struct run *r = kmem.freelist;
  while (r)
  {
    mem_bytes += PGSIZE;
    r = r->next;
  }

  release(&kmem.lock); /* 释放内存管理的锁 */

  return mem_bytes;
}
// kernel/proc.c
uint64
count_process(void)
{
  uint64 cnt = 0;
  for (struct proc *p = proc; p < &proc[NPROC]; p++)
  {
    if (p->state != UNUSED)
      cnt++;
  }
  return cnt;
}

实现sys_info(其他步骤省略)

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

Optional challenge exercises TODO

未做

Print the system call arguments for traced system calls TODO

Compute the load average and export it through sysinfo TODO


实验完成,make grade验证:

Score: 34/35