文章封面图

Essay2026 / 09 / 09

XV6实验2-实现用户看到的Syscall

0-如何添加一个系统调用

0.1-一个系统调用的过程

用户在user空间内进行编程,包含user.h这个头文件后,更新makefile便可以生成程序,与操作系统交互。 那么一个调用的构成是这这样的。

  • 用户在user/user.h内看到了操作系统提供的接口。
  • user/usy.pl这个脚本会生成一个user/usys.S汇编程序。该汇编文件就是用户到对应系统调用的汇编程序。这个汇编程序就相当于是个钩子,把内核上系统接口的实现与用户看到的系统接口之间连接起来。它做两件事情,第一是将定义在/kernel/syscall.h的系统调用号放在a7寄存器内,随后ecall进入内核
  • 进入内核。内核内的kernel/syscall.c内的syscall会被执行。其逻辑很简单。在文件内定义了一个系统调用号与系统调用函数映射关系的 映射表 。所以syscall就会真正把用户看到的,传进来的参数传递给内核里的那个真正干事的系统调用函数。
  • 通过映射表调用相应的系统函数,并传递参数进去。内核中的系统函数被执行。然后逐层返回用户看到系统接口。一次系统调用完成

0.2-新增系统调用需要修改的地方

所以如果要无中生有一个系统调用。那么从顶至下需要依次修改

  • user/user.h 定义给用户使用的系统接口函数
  • user/usys.pl 产生接口函数所需的用户到内核的汇编程序
  • kernel/syscall.h 定义内核该系统调用的调用号
  • kernel/syscall.c 在映射表内新增调用号到真正系统调用函数的记录
  • kernel/xxx.c 实现该系统调用
  • 一些其他的内核文件,以实现系统调用的正确逻辑。如read不能只是关于文件,进程的fork,alloc等需要考虑其是否被复制继承fd表等等

1-System Call Tracing

1.1-需求分析

这里我们被要求添加一个新的系统调用 trace 其中这个系统调用被定义为 int trace(int SYS_CALL_MASK)

描述 trace接收SYS_CALL_MASK掩码得知要追踪哪些系统调用并输出。其中进程调用trace后,其自身与子进程的相关系统调用将会被跟踪,并打印在屏幕上。其中打印的格式为

[pid]: syscall [syscall_name] -> [return_value]

返回值 其中正常情况下trace将会返回0,异常情况下返回-1

1.2-预备的修改

user/user.h内添加

int trace(int);

user/usys.S内添加

entry("trace");

kernel/syscall.h内添加

#define SYS_trace 22

kernel/syscall.c的映射表内添加

extern uint64 sys_trace(void);

//在syscalls映射表内添加
[SYS_trace] sys_trace;

1.3-实现

首先我们要明确我们正在哪里捕获系统调用 在xv6内,一个进程,他的所有系统调用进入内核态的第一件事情就是被syscall这个函数接管。其会解析传递过来的系统调用号,然后调用相应的正在内核里面写的实现的系统调用函数 系统调用

所以第一件事情很明确,就是在syscall内去检查该系统调用是否被trace,如果被trace就输出

第二件事情就是trace的子进程默认也捕获的功能。这里其实很简单。就是我们在procceess的数据结构内添加trace的掩码信息来记录该进程哪些系统调用正在被trace。然后同步修改fork,保证子进程被创建时其trace掩码信息会继承自父进程。

那么第一步就是给进程的数据结构新增一个信息以记录trace掩码

// kernel/proc.h
struct proc {
    //省略代码
    int tmask;//新开一行,写下
};

接下来修改syscall,让他在系统调用号合理时。读取进程的tmask以确定该是同调用是否被trace

// kernel/syscall.c
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->tmask) & (1<<num)){//syscall had be traced
      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;
  }
}

同样修改fork

// kernel/proc.c
int 
fork(void)
{
    //省略代码
  np->tmask = p->tmask;//新增代码
  
  pid = np->pid;
    
  np->state = RUNNABLE;

  release(&np->lock);

  return pid;
}

最后实现下sys_trace,他的任务就是获取参数,然后写入到进程的tmask当中

// kernel/sysproc.c

uint64
sys_trace(void){
  int mask;
  struct proc* p = myproc();
  if(argint(0,&mask) < 0){
    return -1;
  }
  p->tmask = mask;
  return 0;
}

2-Sysinfo

2.1-需求分析

这里我们被要求实现一个系统调用函数 用户会传递一个sysinfo结构体指针,sysinfo函数需要获取空闲的内存容量与进程表中非UNUSED的进程数,并放置到用户给的结构体内

int sysinfo(void*);

返回值 其将在正常工作时返回0,异常时返回-1

2.2-准备工作

user/user.h内新增

int sysinfo(struct sysinfo*);

user/usys.pl内新增

entry("sysinfo");

kernel/syscall.h内添加

#define SYS_sysinfo 23

kernel/syscall.c内的映射表内添加相应的记录

2.3-实现

所以对于sysinfo我们的实现为

  • 获取参数,得到用户给的addr
  • 获取空闲内存量
  • 获取非UNUSED的进程数
  • 在内核态的情况下,把数据拷贝到用户addr上

所以下面即代码实现

#include "types.h"
#include "riscv.h"
#include "sysinfo.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "proc.h"

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

    if(argaddr(0,&addr)< 0){
        return -1;
    }
    
    //get the free memory
    sinfo.freemem = kcount();
    
    //get processors
    
    sinfo.nproc = proc_count();

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

接下来就是实现kcount,proc_count

kernel/kalloc.c内实现kcount

int
kcount(void){
  int n=0;
  struct run* p;
  acquire(&kmem.lock);//在计数完成前,确保内存布局不变
  p = kmem.freelist;
  while(p){
    p = p->next;
    n++;
  }
  release(&kmem.lock);
  return n*PGSIZE;
}

kernel/proc.c内实现proc_count

int
proc_count(void){
  struct proc* p;
  int count;

  count = 0;

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