MIT 6.S081 LAB2 系统调用 笔记

系列文章目录

LAB2



前言

在开始前,要熟悉以下几个概念:

  • 用户空间的系统调用代码在user/user.h和user/usys.pl
  • 内核空间的系统调用代码在kernel/syscall.h中声明,在kernel/syscall.c中调用,在sysproc.c和sysfile.c中定义。分别代表对CPU资源的抽象和磁盘资源的抽象。
  • 和进程有关的函数在kernel/proc.h和kernel/proc.c中

系统调用过程:
用户空间调用声明在user/user.h中的函数,该函数将调用的参数放到a0-a6寄存器,调用号放到a7寄存器。随后调用ecall,进入内核空间,内核空间syscall.c中通过a7寄存器的值调用相关的系统调用。


一、System call tracing

要求实现一个trace系统调用,该系统调用接受一个mask数。mask数代表了进程中要追踪的系统调用(1 << SYS_fork 代表要追踪SYS_fork),在命令执行的过程中要打印出被追踪的系统调用的进程号、调用名称和调用的返回值。

示例:

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
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 -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$
$ grep hello README
$
$ trace 2 usertests forkforkfork
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
$   

实现过程:

1. 修改proc.h中proc结构体,在sysproc.c中添加系统调用

// Per-process state 该结构体声明在proc.h里,代表进程模型
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  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
  int traceMask;               // TraceMask 添加的字段,代表需要追踪的系统调用

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // 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)
};
// 该函数添加到sysproc.c中
uint64 sys_trace(void) {
  int traceMask = 0;
  if(argint(0, &traceMask) < 0) // 得到第一个参数mask
    return -1;
  myproc()->traceMask = traceMask; // 保存mask到进程模型中
  return 0;
}

2. 修改syscall.c/h和fork

首先添加系统调用系统调用表中

(syscall.h)
...
#define SYS_unlink 18
#define SYS_link   19
#define SYS_mkdir  20
#define SYS_close  21
#define SYS_trace  22 // 添加的系统调用
(syscall.c)
// 在syscall.c中添加以下代码
extern uint64 sys_sysinfo(void); // 声明系统调用
static uint64 (*syscalls[])(void) = { // 放入调用数组
...
[SYS_sysinfo] sys_sysinfo,
};

// 修改函数
void
syscall(void)
{
  char* names[] = {
	... // 系统调用名称数组
  };

  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->traceMask & (1 << num)) > 0) { // 如果系统调用号在mask中
      printf("%d %s %d\n", p->pid, names[num - 1], p->trapframe->a0); // 打印相关信息,注意系统调用号是从1开始的
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}
(proc.c)
// 修改proc.c中fork函数的定义,以让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;
  np->traceMask=p->traceMask; // 添加的语句,让新进程模型得到父进程的mask
  ...
}

至此内核部分修改完毕,开始修改用户空间代码

3. 用户空间

首先添加$U/_traceMakefile里的UPROGS
随后修改user/user.h和user/usys.pl

(user.h)
...
int trace(int);
...
(usys.pl)
...
entry("trace");
...

至此,实验完毕,编译运行测试。

make qemu
$ trace 2 usertests forkforkfork
usertests starting
5 sys_fork 6
test forkforkfork: 5 sys_fork 7
7 sys_fork 8
8 sys_fork 9
8 sys_fork 10
9 sys_fork 11
10 sys_fork 12
...
10 sys_fork 66
11 sys_fork 67
8 sys_fork -1
9 sys_fork -1
10 sys_fork -1
OK
5 sys_fork 68
ALL TESTS PASSED

第一部分完成

二、Sysinfo

添加Sysinfo系统调用,该调用接受一个结构体指针,结构体中包含当前系统剩余内存容量和状态不是UNUSED的进程个数。该部分添加系统调用的部分与上一部分trace完全一致,重点在于如何计算系统剩余内存容量和状态不是UNUSED的进程个数。我们需要在kalloc.c(管理内存)和proc.c(管理进程)的文件中添加相应的函数。

首先,编写获取剩余内存的函数。

(kalloc.c)
// 添加以下函数,以获取剩余内存
int freemem(void) {
  int free = 0;   // 待返回的值
  struct run *r;  // 游标(个人猜测)

  acquire(&kmem.lock); // kmem是内核的内存模型,lock是互斥锁
  r = kmem.freelist;   // freelist是剩余未分配页的链表,让游标等于首个页
  for (;r;) {  		   // 让游标向后移动,直到为空(c语言中0)
    r = r->next;
    free += 4096;      // free 加上一个页的大小
  }
  release(&kmem.lock); // 释放锁

  return free;         // 返回
}

其次,编写获得进程数的函数

(proc.h)
// 添加以下函数,以获取相应进程数
int procnum(void) {
  int ret = 0;
  for (int i = 0; i < NPROC; i++) {
    if (proc[i].state != 0) ret++; // 如果进程状态不是0(UNUSED),累加
  }
  return ret;
}

然后,需要在defs.h中添加函数声明

最后,编写sysinfo系统调用,可以写到sysfile.c或者sysproc.c或者syscall.c里。

uint64 sys_sysinfo(void) {
  struct proc *p = myproc(); // 获取当前进程模型

  uint64 sysinfoAddr; // 传入的结构体指针
  struct sysinfo si;  // 需要返回的结构体

  si.freemem = freemem(); // 获取剩余的内存容量
  si.nproc = procnum();   // 获取相应的进程数

  if(argaddr(0, &sysinfoAddr) < 0) // 读取传入的结构体指针
    return -1;
  if(copyout(p->pagetable, sysinfoAddr, (char *)&si, sizeof(si)) < 0) // 将结构体通过指针拷贝到用户空间
    return -1;

  return 0; // 如无出错,返回0
}

之后按照与trace相同的方法,在系统调用表中添加sysinfo系统调用,在用户空间中添加相应的声明。并把sysinfotest添加到Makefile里。

运行测试。

make qemu
$ sysinfotest           
sysinfotest: start
sysinfotest: OK
$ 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值