MIT_6.828_2018_Homework_xv6_system_calls

作业地址:System calls

用户程序调用系统函数的流程

1.创建一个新的进程

创建一个新的进程的时候,在内核态,内核会对该进程进行相应的初始化工作:设置该进程的内核栈(PGSIZE大小),用户的空间(起初是一个PGSIZE的大小),pid,trapret,设置该进程的虚拟地址映射(这就做到了隔离)。

2.执行进程

接下来切换到用户态,trapfram里面的eip即为在用户态下执行第一条语句的地址,由于初始化为0,所以从虚拟地址0开始执行。

3.程序进行系统调用

在用户态发生系统调用的时候,首先会将系统调用号存入%eax中,再执行INT $T_SYSCALL,执行完INT指令后程序已经进入内核态。INT指令结束后,系统会根据中断向量表转到alltraps处,将用户态的寄存器保存在trapframe中(INT 已经保存了部分),随后执行trap函数。值得注意的是,用户进行系统调用的时候,相关参数是存在用户栈里面的,所以如果想要获得参数值,需要用pt->tf->esp+xxx来获得。之后trap转向执行syscall,将返回值保存在eax中,最后返回alltraps.在alltraps执行完毕后,进入trapret,对用户的状态进行恢复,IRET语句将CPL置为3,进入用户态。

现在来完成作业里面的两个题目。

Part One: System call tracing

这个题目的意思就是,在每次系统调用的时候,输出具体调用的函数,以及返回值。
首先,声明每个函数的名字(在syscall.c中),直接查表即可得到函数名:

static char * names[25] = {
[SYS_fork] =   "fork\0",
[SYS_exit]=    "exit\0",
[SYS_wait]=    "wait\0",
[SYS_pipe]=    "pipe\0",
[SYS_read]=    "read\0",
[SYS_kill]=    "kill\0",
[SYS_exec]=    "exec\0",
[SYS_fstat]=   "fstat\0",
[SYS_chdir]=   "chdir\0",
[SYS_dup]=     "dup\0",
[SYS_getpid]=  "getpid\0",
[SYS_sbrk]=    "sbrk\0",
[SYS_sleep]=   "sleep\0",
[SYS_uptime]=  "uptime\0",
[SYS_open]=    "open\0",
[SYS_write]=   "write\0",
[SYS_mknod]=   "mknod\0",
[SYS_unlink]=  "unlink\0",
[SYS_link]=    "link\0",
[SYS_mkdir]=   "mkdir\0",
[SYS_close]=   "close\0",
[SYS_date] = "date\0",
};

在syscall()里面添加输出信息的语句:

cprintf("%s -> %d\n",names[num],curproc->tf->eax);

make;make qemu结果如下:

write -> 1
fork -> 2
exec -> 0
open -> 3
close -> 0
$write -> 1
 write -> 1
Optional challenge: print the system call arguments.

输出相关参数,在syscall()里面输出用户栈里面的相关参数即可。

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

  num = curproc->tf->eax;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {

    curproc->tf->eax = syscalls[num]();
	//输出三个参数
	cprintf("args:1.0x%x     2.0x%x     3.0x%x\n",*((int*)(curproc->tf->esp+8)),
	*((int*)(curproc->tf->esp+12)),
	*((int*)(curproc->tf->esp+16)));
	//输出函数名和返回值
	cprintf("%s -> %d\n",names[num],curproc->tf->eax);
  } else {
    cprintf("%d %s: unknown sys call %d\n",
            curproc->pid, curproc->name, num);
    curproc->tf->eax = -1;
  }
}

用户的相关参数存放在用户栈中(curproc->tf->esp指向的地址),此处输出了三个参数。

Part Two: Date system call

此练习题很有做的必要,可以加深我们对系统调用的理解。
首先用户定义了一个程序date.c:

#include"types.h"
#include"user.h"
#include"date.h"

int
main(int argc, char *argv[])
{
  struct rtcdate r;

  if (date(&r)) {
    printf(2, "date failed\n");
    exit();
  }

  // your code to print the time in any format you like...

	printf(1,"%d-%d-%d %d:%d:%d\n",r.year,r.month,r.day,
	r.hour,r.minute,r.second);
  exit();
}

输出时间。
在Makefile里面的UPROGS加入_date,这样我们可以在xv6的shell里面直接使用date命令。
注意到date.c里面调用了系统函数date(struct rtcdate*),我们的任务就是在内核里面加入相关的系统调用。

首先,在user.h里面的系统调用那里加入int date(struct rtcdate *);,声明该函数的存在。

接着,在usys.S里面加入SYSCALL(date),定义用户态进入内核态的入口(通过INT)。

然后,在syscall.h里面为这个系统调用分配一个号码:#define SYS_date 22

之后,在syscall.c里面加入[SYS_date] sys_date,,可以通过查表得到sys_date函数的入口。注意:sys_date和date()不是一个函数,date()进入到sys_date里面,sys_date完成相应的功能。

最后,我们在sysproc.c里面完成sys_date的功能:

int sys_date(void)
{	
    //date()传进来的参数在用户栈里面,所以我们需要自己来获得参数
	struct rtcdate *r
	//为什么是tf->esp+4+4*6我也不太清楚,自己试出来的,
	//猜想就是:struct rtcdate里面有六个成员,所以地址在
	//距离栈顶4+4*6处
	=(struct rtcdate*)(myproc()->tf->esp+4+4*6);
	cmostime((struct rtcdate*)r);
	//我们位于东八区,所以加上8
	r->hour += 8;
	return 0;
}

至此我们完成了系统调用date(struct rtcdate*);
执行make ;make qemu,结果如下:

cpu1: starting 1
cpu0: starting 0
sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58
init: starting sh
$ date
2020-2-6 11:12:9
$ date
2020-2-6 11:12:13

可以看到圆满完成。

总结:
用户通过调用系统函数进入内核态(INT指令完成),内核在内核栈的trapframe里面保存了用户态的寄存器状态,用户传进去的参数在用户栈里面,内核完成了相关任务后,将返回值存在%eax中,通过IRET指令返回到用户态,用户程序继续执行。

END.

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值