lab2 system calls

200 阅读4分钟

overview:

这个实验自己做的很不好,参考了很多资料,自己阅读文献学习的能力较弱,阅读源码能力较弱,分析能力较弱,收获满满,多多总结学习方法,提高阅读文献和源码的能力

sysinfo

预处理工作与代码编写无关

1.在kernel/syscall.h中添加系统调用的序号

#define SYS_sysinfo 23

2.在kernel/user.h声明函数

int sysinfo(struct sysinfo *);

3.在user/usys.pl系统接口文件添加接口

entry("sysinfo");

4.在kernel/syscall.c中添加函数声明

extern uint64 sys_sysinfo(void);

5.在kernel/syscall.c中添加

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

代码编写以及源码解读

获取进程状态为unused的进程数量

根据hint进入kernel/proc.h,查看proc结构体,我们可以看到,如果要使用某些特殊的状态必须保持(持有)p->lock.

struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  struct proc *parent;         // Parent process
  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 umask;                   // Process Umask
  // 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)
};

进入proc.c可以看到

struct proc proc[NPROC]; 可以看到这是一个进程数组所以我们可以编写如下获取数量的代码

//这个版本没有单独写acquire持有锁函数
uint64 getnum(void)
{
  // 获取进程状态后再进行计数
  int num = 0;
  for (int i = 0; i < NPROC; i++)
  {
    if(proc[i].state != UNUSED) 
    num++;
  }
  
  return num;
}
// 持有锁最后释放锁的版本
// Return the number of processes whose state is not UNUSED
uint64
getnum(void)
{
  struct proc *p;
  // counting the number of processes
  uint64 num = 0;
  // traverse all processes
  for (p = proc; p < &proc[NPROC]; p++)
  {
    // add lock
    acquire(&p->lock);
    // if the processes's state is not UNUSED
    if (p->state != UNUSED)
    {
      // the num add one
      num++;
    }
    // release lock
    release(&p->lock);
  }
  return num;
}

获取空闲内存

我们知道内存的最小粒度是页,所以这题我们要获取空闲的页的数量,返回页的数量乘以每一页持有的内存大小.

根据hint,进入kernel/kalloc.c
可以看到这里用链表来定义了内存可用的block,每个块大小相同,每个节点内有存放这个块状态的结构体lock和指向下一个块的freelist

struct run {
  struct run *next;
};

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

struct spinlock {
  uint locked;       // Is the lock held?

  // For debugging:
  char *name;        // Name of lock.
  struct cpu *cpu;   // The cpu holding the lock.
};

可以看到这个函数传递进来一个地址,这个地址就是页的首地址,将该地址中数据置为一后采用头插法接在kmem.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);
}

代码如下

uint64
freeMemorySize(void){
   uint64 size = 0;
   struct run * cur = kmem.freelist;
   while (cur)
   {
    size++;
    cur = cur->next;
   }
   
   return size * PGSIZE;
}

系统调用代码的编写

根据hint我们需要复制一个struct sysinfo 到 用户空间,查看kernel/sysfile.c 和 kernel/file.c

可以联想到用一个指针st接受第一个参数指向用户区 argaddr(0, &st)

运用copyout进行一个复制

uint64
sys_fstat(void)
{
  struct file *f;
  uint64 st; // user pointer to struct stat

  if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
    return -1;
  return filestat(f, st);
}

// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
int
filestat(struct file *f, uint64 addr)
{
  struct proc *p = myproc();
  struct stat st;
  
  if(f->type == FD_INODE || f->type == FD_DEVICE){
    ilock(f->ip);
    stati(f->ip, &st);
    iunlock(f->ip);
    if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)
      return -1;
    return 0;
  }
  return -1;
}

// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (dstva - va0);
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);

    len -= n;
    src += n;
    dstva = va0 + PGSIZE;
  }
  return 0;
}

int
argaddr(int n, uint64 *ip)
{
  *ip = argraw(n);
  return 0;
}

static uint64
argraw(int n)
{
  struct proc *p = myproc();
  switch (n) {
  case 0:
    return p->trapframe->a0;
  case 1:
    return p->trapframe->a1;
  case 2:
    return p->trapframe->a2;
  case 3:
    return p->trapframe->a3;
  case 4:
    return p->trapframe->a4;
  case 5:
    return p->trapframe->a5;
  }
  panic("argraw");
  return -1;
}

代码如下

uint64
sys_sysinfo(void)
{
    // 从内核复制回来采取copyout函数
    struct sysinfo info;
    uint64 io; // user pointer to struct sysinfo
    struct proc * p = myproc();
    

    if(argaddr(0, &io) < 0) // 让io等于第0个参数即内存位置
    return -1;
    
    info.freemem = freeMemorySize();
    info.nproc = getnum();
    
    // copy from kernel to user
    if(copyout(p->pagetable,io,(char *)&info,sizeof(info)) < 0)
    return -1;
    return 0;
}

trace

系统调用全流程

user/user.h:		用户态程序调用跳板函数 trace()
user/usys.S:		跳板函数 trace() 使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c	到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c	syscall() 根据跳板传进来的系统调用编号,查询 syscalls[] 表,找到对应的内核函数并调用。
kernel/sysproc.c	到达 sys_trace() 函数,执行具体内核操作

预处理

和sysinfo的预处理差别不大,不做具体赘述了

代码编写

uint64
sys_trace(void)
{
   int n; // 获取参数 
   if(argint(0, &n) < 0) 
   return -1;
   myproc()->umask = n;
   printf("umask : %d \n",n);
   return 0;
   
}

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(1 << num & p->umask){ // 这里不能用 == 用==grep all 时候只能捕获一个 
      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;
  }

}