mit OS Lab2: System calls

Syetem calls

这篇思路很乱,所以请确保你阅读过实验 HINT 以及如下源码:user/user.h, user.usys.pl, kernel/syscall.h, kernel/syscall.c, kernel/proc.h, kernel/proc.c, kernel/kalloc.c, kernel/vm.c。

System call tracing

本次实验和前次不同,我们并不从头写一个 c 文件,实现系统调用功能,而是在系统的架构中添加代码,来实现我们的需求。

按照 hint 一步步来。可以找到课程提供的trace程序user/trace.c。我们先在Makefile里加入$U/_trace\。此时执行sudo make qemu会发生错误。

because the user-space stubs for the system call don't exist yet,这句话大意是:系统调用的系统空间桩代码尚不存在。stubs(桩代码)指的是为了让程序正常运行而临时占坑的、尚未编辑的代码。如果你使用python写过代码,你应该很熟悉pass语句,它和stubs的用途是一样的。检查 lab1 的实验代码,可以发现我们编写的一系列系统调用程序,已经填充了stubs

因此按照hint,我们需要在系统中添加stubs。(详见配置部分)

添加stubs之后,我们执行trace 32 grep hello README,依然失败。这是由于我们未在内核中实现这项系统调用。

kernel/sysproc.c中添加sys_trace()作为实现,并在struct proctrace的实现添加一个新变量。(具体实现见源码)参考kernel/sysproc.c里其他函数的实现,调用kernel/syscall.c里的从用户空间检索系统调用的参数。

修改kernel/proc.c中的fork()函数,使得子进程在复制的时候一并复制父进程的 mask 变量。

修改kernel/syscall.c中的syscall(),使得输出符合需求。创建一个数组存储进系统调用名。

kernel/syscall.c里获取系统调用参数的函数参见这个文章https://blog.csdn.net/NP_hard/article/details/121325896

源码

// kernel/proc.h struct proc
// Per-process state
struct proc {
  // ......
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
  int mask;                    // trace mask new
};


// kernel/sysproc.c sys_trace()
uint64 sys_trace(void) {
  int mask;
  if (argint(0, &mask) < 0)// 获取第0个寄存器的信息,并用一个int型指针指向这个寄存器。
  // 类似把第0个参数赋值给int变量mask。
  // 参考其他函数的写法获取第0个参数并存入 mask
          return -1;
  myproc()->mask = mask;
  // printf("mask: %d\n", mask);
  return 0;
}

// kernel/proc.c fork()
int
fork(void)
{
  // ......
  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;
  // trace mask
  // 将父进程的掩码 赋给 子进程的掩码
  np->mask = p->mask;
  // increment reference counts on open file descriptors.
  // ......
}

// kernel/syscall.c syscall()

// ......
extern uint64 sys_sysinfo(void); // new

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

// 和系统调用号对应的系统调用名
const char* syscallName[] = {// new
    0, "fork", "exit", "wait",
    "pipe", "read", "kill", "exec",
    "fstat", "chdir", "dup", "getpid",
    "sbrk", "sleep", "uptime", "open",
    "write", "mknod", "unlink", "link",
    "mkdir", "close", "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((p->mask >> num) & 1){// new
      printf("%d: syscall %s -> %d\n", p->pid, syscallName[num], p->trapframe->a0);// new
    }// new
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

配置
首先在user/user.h中添加函数原型int trace(int)。接下来在user/usys.pl中添加stubentry("trace");,并在kernel/syscall.h为它添加一个系统调用号 22,#define SYS_trace 22

运行结果

sudo make qemu
...
xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
$ 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
6: syscall fork -> 7
test forkforkfork: 6: syscall fork -> 8
8: syscall fork -> 9
9: syscall fork -> 10
10: syscall fork -> 11
...
9: syscall fork -> -1
OK
6: syscall fork -> 69
ALL TESTS PASSED

判分程序结果

./grade-lab-syscall trace
make: 'kernel/kernel' is up to date.
== Test trace 32 grep == trace 32 grep: OK (1.5s)
== Test trace all grep == trace all grep: OK (0.9s)
== Test trace nothing == trace nothing: OK (1.0s)
== Test trace children == trace children: OK (9.0s)

Sysinfo

本实验要求实现函数sysinfo()。函数要求有以下功能:获取空闲内存大小、获取非 UNUSED 的进程以及复制内核中的信息。

源码

// kernel/syscall.c
extern uint64 sys_sysinfo(void);
static uint64 (*syscalls[])(void) = {
  // ...
  [SYS_close] sys_close,
}

const char* syscallName[] = {
  // ...
  "sysinfo",
}

// kernel/sysproc.c
uint64
sys_sysinfo(void){
  struct sysinfo info;
  struct proc *p = myproc();
  uint64 addr;
  info.freemem = kfreemem();// 获取空闲内存(单位bytes )
  info.nproc = cntproc();// 获取非UNUSED的进程
  if(argaddr(0, &addr) < 0)// 这里为了统一,使用了argaddr获取参数,有兴趣的话可以试一下传参
    return -1;
  // 将内存地址addr的内容,
  // copyout在 kernel/vm.c 中找到
  // int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
  // 根据注释可知,从内核src复制到用户dstva
  if(copyout(p->pagetable, addr, (char*)&info, sizeof(info)) < 0)
    return -1;
  return 0;
}



// kalloc.c kfreemem()
// 观察函数kalloc和kfree,以及结构体kmem
// 可以得知内存的组织结构类似链表
// 链表的头结点为keme.freelist
// 每次分配/回收空间都对freelist操作
// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc().  (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(void *pa)
{
  struct run *r;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);

  r = (struct run*)pa;

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

// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
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;
}
// new add
// get free memory
// 因此,我们遍历有多少个结点,就可以得到空余空间大小了
// 每个节点可以提供1个PGSIZE大小的空间
uint64
kfreemem(void){
  struct run *r;
  uint64 cnt = 0;
  acquire(&kmem.lock);
  r = kmem.freelist;
  while(r){
    r = r->next;
    cnt += PGSIZE;
  }
  //printf("%d\n", cnt);
  release(&kmem.lock);
  return cnt;
}



// proc.c cntproc()
// 观察函数allocproc和结构体proc
// 可以得知,进程以数组的形式存储
// 仿照allocproc的遍历方式,得到函数cntproc
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
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;

  return p;
}
// new add
// count the number of processes
// 统计不为UNUSED的进程,遍历即可
uint64
cntproc(void){
  struct proc *p;
  uint64 cnt = 0;
  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state != UNUSED) {
      cnt++;
    }
    release(&p->lock);
  }
  return cnt;
}

配置

trace()类似,首先在user/user.h中添加结构体声明struct sysinfo以及函数原型int sysinfo(struct sysinfo *)。接下来在user/usys.pl中添加stubentry("sysinfo");,并在kernel/syscall.h为它添加一个系统调用号 23。

运行结果

sudo make qemu
...
xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$ sysinfotest
sysinfotest: start
sysinfotest: OK

判分程序结果

./grade-lab-syscall sysinfotest
make: 'kernel/kernel' is up to date.
== Test sysinfotest == sysinfotest: OK (2.0s)
    (Old xv6.out.sysinfotest failure log removed)

参考文章

https://zhuanlan.zhihu.com/p/407169754

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。