MIT-6.S081 | Lab2 syscall: System calls(2021)

108 阅读4分钟

Lab syscall: System calls

系统调用过程

// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
int ntas();
int crash(const char*, int);
int mount(char*, char *);
int umount(char*);

​ 这些函数都是汇编实现的,在user/usys.S中,以fork为例:

.global fork
fork:
 li a7, SYS_fork
 ecall
 ret

​ 注意到li a7, SYS_fork 命令中,li的形式

li,rd,imm

a7是寄存器 ;SYS_fork是一个立即数,被定义在kernel/syscall.h中。

// System call numbers
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_read    5
#define SYS_kill    6
#define SYS_exec    7
#define SYS_fstat   8
#define SYS_chdir   9
#define SYS_dup    10
#define SYS_getpid 11
#define SYS_sbrk   12
#define SYS_sleep  13
#define SYS_uptime 14
#define SYS_open   15
#define SYS_write  16
#define SYS_mknod  17
#define SYS_unlink 18
#define SYS_link   19
#define SYS_mkdir  20
#define SYS_close  21

​ 到内核后,会进入一个内核处理函数,调用syscall,根据a7中的调用号,去调用相应的服务。[SYS_fork] sys_fork,在C++中以弃用,int arr[]={[3] 123,[4] 321},arr[3]的元素为123,不存在的为0.

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

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

System call tracing

实现一个追踪特定进程系统调用的系统调用,叫做 trace。比如有个进程调用了这个 trace,那么 trace 就会以特定格式输出这个进程调用过的系统调用。其中,有一个 mask 作为参数,指定有哪些调用需要被追踪。

grep是朝招文件中符合条件的字符串或表达式。

grep hello README 就是在README中找hello

​ We provide a trace user-level program that runs another program with tracing enabled (see user/trace.c).

#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 error\n", argv[0]);
        exit(1);
    }
    // 重新构造参数
    for(i=2; i<argc&&i<MAXARG; i++)
    {
        nargv[i-2] = argv[i];
    }
    exec(nargv[0], nargv);
    exit(0);
}

​ 然后跟着提示往后做,添加*$U*/_trace\user/user.h 中添加int trace(int)kernel/syscall.h中添加#define SYS_trace 22kernel/syscall.c

extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void);   //here 实现在sysproc.c中

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

​ 在struct proc结构体中添加int trace_mask;,这样,在中转函数 syscall() 中,我们只需要检测当前进入内核的这个进程的 trace_mask 就行了。

新的syscall函数

const char *syscall_names[] = {
    [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](); //通过num找到对应的系统调用函数
    int trace = p->trace_mask;  //获取当前进程的trace值
    if ((trace >> num) & 1) // 如果trace值中包含当前系统调用号
    {
      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;
  }
}

sys_trace的实现

注意这里的 argint(0, &mask) 这句话,其用处是读取第一个 32 位的参数。

//参考文件中其他函数的实现
uint64 sys_trace(void)
{
  int mask;
  if(argint(0, &mask) < 0)  // get the mask from the user
    return -1;
  struct proc *p = myproc();  // get the current process
  p->trace_mask = mask;
  return 0;
}

​ 最后的收尾就是在proc.c中的 freeproc函数最后添加p->trace_mask = 0;

还没结束,因为提示中的fork()还没用。

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

​ 创建新进程的时候,为新添加的 syscall_trace 附上默认值 0(否则初始状态下可能会有垃圾数据)。

// kernel/proc.c
static struct proc*
allocproc(void)
{
  ......

  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;

  p->syscall_trace = 0; //  为 syscall_trace 设置一个 0 的默认值

  return p;
}

​ 显然fork中的np是新进程的,所以为了让子进程中有和父进程相同的trace_mask在代码中加入np->trace_mask = p->trace_mask;即可。

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;

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

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

  np->trace_mask = p->trace_mask; //here

  // 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;

  release(&np->lock);

  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);

  acquire(&np->lock);
  np->state = RUNNABLE;
  release(&np->lock);

  return pid;
}

Sysinfo

实现一个系统调用,用于收集当前系统的空闲内存,和运行进程的数量。系统调用接收一个 struct sysinfo*,在系统调用中需要把信息写进这个结构体里。

​ 和前面一样,需要先在各种文件中把这个系统调用注册上,然后才能开始实现。按hint写即可。内核中并没有提供给我们获取可用内存和当前进程数的函数,所以我们需要自己实现。根据 lab 的要求,应该实现在 kernel/kalloc.c 这个文件里。

struct run {
  struct run *next;  
};
struct {
  struct spinlock lock;
  struct run *freelist;
} kmem;

void* kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

​ 根据这些代码的名称可以看出kmem是个链表,每个元素指向一个大小4KB的内存页。所以我们就可以遍历这个链表来得到空闲的空间。

uint64 get_fremem(){
  //返回空闲内存
  uint64 ret = 0;
  acquire(&kmem.lock);  //获取锁
  struct run *r = kmem.freelist; //获取空闲内存链表
  while (r)
  {
    r = r->next;
    ret++;
  }
  release(&kmem.lock); //释放锁
  return ret * PGSIZE; // 返回时,需要乘以一个页的大小
}

还需要正在运行的进程数,按照 lab 的要求,要把这个函数实现在 kernel/proc.c 中。

uint get_proc_cnt(){
  struct proc* cur_proc;
  uint cnt = 0;
  for(cur_proc=proc;cur_proc<&proc[NPROC];cur_proc++)
  {
    acquire(&cur_proc->lock);
    if(cur_proc->state != UNUSED)
    {
      cnt++;
    }
    release(&cur_proc->lock);
  }
  return cnt;
}

​ 上面两个函数要在defs.f中声明。在sysproc.c编写的sys_sysinfo()函数copyout()参考file.c中filestat()的写法。

uint64 sys_sysinfo(){
  struct sysinfo info;
  struct proc *p = myproc();
  uint64 addr;
  if (argaddr(0, &addr) < 0)
    return -1;
  info.freemem = get_fremem();
  info.nproc = get_proc_cnt();
  // copy sysinfo to user space
  if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
    return -1;
  return 0;
}