MIT6.S081 Lab2:System calls

实验目的

In this lab you will add some new system calls to xv6, which will help you understand how they work and will expose you to some of the internals of the xv6 kernel. You will add more system calls in later labs.

System call tracing[moderate]

提示

  • 在Makefile中加入$U_trace
  • 此时执行make qemu后,无法编译user/trace.c,因为系统调用的用户空间stubs还不存在,要在user/user.h添加原型,user/usys.pl中添加一个stub,在kernel/syscall.h中添加系统调用号;Makefile就可以通过user/usys.pl,生成user/usys.S,这里是真实的系统调用stub,它使用RSIC-Vecall指令进入内核,以上就解决了编译问题,但系统调用还没有在内核实现
  • kernel/sysproc.c 中添加 *sys _ trace ()*函数,该函数通过在 proc 结构中的新变量中记住参数来实现新的系统调用(参见 kernel/proc.h)
  • 由于在proc结构体中添加了个新变量来保存参数,所以也要在fork()(参见kernel/proc.c)中相应把这个变量复制给子进程
  • 最后在syscall(参见kerne/syscall.c)中打印trace所要求的格式,主要包括pid,追踪的系统调用名,该系统调用的返回值等

背景知识

  • RISC-V上的C调用通常将返回值放在a0中。
    比如对于系统调用而言,syscall将其返回值记录在p->trapframe->a0中。

  • 内核的系统调用实现需要找到用户代码传递的参数,因为用户代码调用系统调用,实际上是调用它的包装函数,参数首先会存放在寄存器中,这是C语言存放参数的惯例位置。对xv6内核而言,内核trap代码将用户寄存器保存到当前进程的trapframe中,内核可以在那里找到它们。这里有三个函数,分别为argint、argaddr和argfd,它们从trapframe中以整数、指针、文件描述符的形式检索第n个系统调用参数。它们都调用argraw来获取保存的用户寄存器数据。

  • 通过用户调用exec系统调用的例子,理解系统调用的过程很重要!
    exec在这里插入图片描述
    exec syscall函数的实现过程:

void
syscall(void)
{
  int num;
  struct proc *p = myproc();

  num = p->trapframe->a7;   //syscall number
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();  //call a func in syscalls and save the return val to a0
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

实验过程
1.trace函数已经写好,trace.c,但声明还没加入到user.h
在这里插入图片描述

2.user/usys.pl中添加入口
在这里插入图片描述

3.在kernel/syscall.h中添加系统调用号
在这里插入图片描述
4.在proc结构体(kernel/proc.h)中添加新变量用于保存参数,并在fork(kernel/proc.c)中增加这一项的复制语句

struct proc {
/*
...
*/
int trace_mask;               //Trace mask
};
int fork(void) {
/*
...
*/
  //copy trace_mask to child process
  np->trace_mask = p->trace_mask;
/*
...
*/
};

5.sys_tracekernel/sysproc.c)内核实现,主要内容获取调用参数

uint64 
sys_trace(void)
{
	//get arg of syscall
	argint(0, &myproc()->trace_mask); //save arg to myproc()->trace_mask
	return 0;
}

6.在syscallkernel/syscall.c)中打印最终格式,调用进程pid,调用名称,调用返回值

/*...
extern uint64 sys_trace(void);
...*/
/*...
[SYS_trace]  sys_trace
...*/

void
syscall(void)
{
  int num;
  struct proc *p = myproc();
  

  num = p->trapframe->a7;   //syscall number
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();  //call a func in syscalls and save the return val to a0
    //whether current syscall number is the syscall number we trace or not
    if((1 << num) & p->trace_mask) {
    	printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

这里终结一下xv shell中系统调用流程(trace 为例)
1.从shell中输入trace命令后,shell找到trace.c程序(前提是Makefile中UPROGS添加了$U/_trace\,这样才有trace对应的可执行程序);

2.将命令行参数传递给trace.c中main函数的参数argc,argv,执行用户函数,调用exec替换子进程;
usys.pl(包含所有系统调用的stubs,entry(“trace”))会生成usys.S, usys.S(汇编,源码可以看到)将系统调用号SYS_trace()保存在a7寄存器,调用ecall指令进行系统调用;

3.有了系统调用号后,就可以根据系统调用函数指针数组uint64 (*syscall[])(void) (kernel/syscall.c中),查找到系统调用的实现函数sys_trace() (kernel/sysproc.c) ,到这里系统调用一般完成。在这里每次系统调用都会通过syscall() (kernel syscall.c)调用系统调用的具体实现函数,并将sys_trace()的返回值保存在a0中。如果想进一步输出到shell,可以在syscall()函数中完成。

Sysinfo [moderate]

提示

  • 将$U/_sysinfotest 添加到Makefile UPROGS中,这个主要用于测试
  • 运行make qemu,user/sysinfotest将会编译失败,和上次提交一样,添加系统调用的必要东西,系统调用号;在user/user.h中添加函数原型,函数原型前要提前声明所用到的结构体
    struct sysinfo;
    int sysinfo(struct sysinfo*);
    解决了编译问题后,还没有有添加系统调用实现
  • sysinfo的实现函数freebytes、procnum都是在kernel文件夹中,是内核资源,需要借助copyout()函数,将内核数据拷贝到用户空间,这个函数要好好理解,涉及到页表的理解
  • 在kernel/kalloc.c中添加函数freebytes,获取空闲空间
  • 在kernel/proc.c中添加函数procnum,获取未使用的进程数

背景知识

  • 关于copyout函数的理解,源码如下
// 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);          //往低地址的方向对其4k的地址,相当后12位清零
    pa0 = walkaddr(pagetable, va0);    //get pa from pagetable and va0;  user space pa
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (dstva - va0);    //PGSIZE 4096; dstva - va0 : Offset
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);//copy n bytes to virtual addr ,pa : currrent user space addr

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

实验过程
原来的实验并不需要像上一个trace调用那样很完整,也可以通过sysinfotest,但这里把它写完整,使得可以从shell调用并显示结果

  • 在Makefile中,添加如下
UPROGS=\
/* ...*/
$U/_sysinfotest\
$U/_sysinfo\    #否则不会生成对应的可执行文件,exec sysinfo failed
  • usys.pl脚本中添加该系统调用stub;在syscall.h中添加系统调用号
// user/usys.pl
/*...*/
entry("sysinfo");

// kernel/syscall.h
#define SYS_sysinfo 23
  • 添加freebytes函数,用于获取空闲字节
//kernel/kalloc.c
/*
struct run {
  struct run *next;
};
struct {
  struct spinlock lock;
  struct run *freelist;
} kmem;
*/
//count number of bytes of free memory
void 
freebytes(uint64 *dst)
{
	*dst = 0;
	struct run *p = kmem.freelist;
	
	acquire(&kmem.lock);
	while(p) {
		*dst += PGSIZE;//一个节点对应一个4096的空间
		p = p->next;
	}
	release(&kmem.lock);
}

  • 添加procnum函数,用于获取进程数(未被使用的)
//count number of active processes
void
procnum(uint64 *dst)
{
	*dst = 0;
	struct proc *p;
	for(p = proc; p < &proc[NPROC]; p++) {  //遍历每个进程
		if(p->state != UNUSED) {
			(*dst)++;
		}
	}
}
  • 将上述两个函数的声明添加到头文件defs.h中,里面包含了所有函数的声明,以及结构体声明
//kernel/defs.h
//kalloc.c
void            freebytes(uint64 *dst);
//proc.c
void            procnum(uint64 *dst);
  • 编写sysinfo的具体实现函数,并要包含头文件sysinfo.h
//kernel/sysproc.c
#include "kernel/sysinfo.h"
//copy system information to user space
uint64 
sys_sysinfo(void)
{
	struct sysinfo info;   //struct pointer must be initialized
	
	freebytes(&info.freemem);
	procnum(&info.nproc);
	
	//get virtual addr
	uint64 dstaddr;
	argaddr(0, &dstaddr); //get user space dstaddr
	
	//copy kernel data(info) to user space dstaddr
	if(copyout(myproc()->pagetable, dstaddr, (char *)&info, sizeof info) < 0) 
	{
		return -1;
	}
	return 0;
}
  • 最后,为了实现shell调用,创建sysinfo.c文件,并编写用户函数
#include "kernel/param.h"
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/sysinfo.h"

int
main(int argc, char *argv[]) 
{
	if(argc != 1) {
		fprintf(2, "%s need not param\n", argv[0]);
		exit(1);
	}
	struct sysinfo info;
	sysinfo(&info);
	printf("free space(bytes) : %d, number of used proc : %d\n", info.freemem, info.nproc);
	
	exit(0);
	
}

实验结果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值