MIT6828-HW3-xv6 system calls

系统调用是内核中的一系列实现系统功能的子函数,本文将针对XV6中的一些系统调用进行管理。本章的配置过程如:作业1:XV6启动

第一部分:系统调用追踪

修改xv6内核,对于每一个系统调用,打印函数名和返回值以及参数,当xv6启动时,可以看到如下内容:

1
2
3
4
5
6
7
...
fork -> 2
exec -> 0
open -> 3
close -> 0
$write -> 1
write -> 1

这个是init fork,并执行了sh,sh确保只有两个文件描述符被打开,然后sh写了$,修改syscall.c下的syscall(),如下功能:

  • 打印系统调用名称
  • 打印返回值
  • 打印系统调用参数

syscall()原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
syscall(void)
{
int num;
struct proc *curproc = myproc();

num = curproc->tf->eax; // 系统调用的序号被保存在eax寄存器中
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
curproc->tf->eax = syscalls[num](); //系统调用返回值保存在这里
} else {
cprintf("%d %s: unknown sys call %d\n",
curproc->pid, curproc->name, num);
curproc->tf->eax = -1;
}
}

我们需要打印系统调用名称、返回值及参数,struct proc结构体描述了进程的信息,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct proc {
uint sz; // Size of process memory (bytes)
pde_t* pgdir; // Page table
char *kstack; // Bottom of kernel stack for this process
enum procstate state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
struct trapframe *tf; // Trap frame for current syscall
struct context *context; // swtch() here to run process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};

从这个结构体中,我们能够得到进程的名称,但是只知道进程名称是不行的,我们需要知道函数名称。我们可以看到,在syscall中有一个比较重要的结构体tf,即trapframe,这个结构体保存了CPU寄存器中的值,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct trapframe {
// registers as pushed by pusha
uint edi;
uint esi;
uint ebp;
uint oesp; // useless & ignored
uint ebx;
uint edx;
uint ecx;
uint eax;

// rest of trap frame
ushort gs;
ushort padding1;
ushort fs;
ushort padding2;
ushort es;
ushort padding3;
ushort ds;
ushort padding4;
uint trapno;

// below here defined by x86 hardware
uint err;
uint eip;
ushort cs;
ushort padding5;
uint eflags;

// below here only when crossing rings, such as from user to kernel
uint esp;
ushort ss;
ushort padding6;
};

记得在Lab1里有过使用eip寄存器函数追踪的方法,但是那个过于复杂,由于系统调用比较固定,我们直接写一个函数表用查表法实现即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static char *syacall_name[] = {
[SYS_fork] = "fork",
[SYS_exit] = "exit",
[SYS_wait] = "wait",
[SYS_pipe] = "pipe",
[SYS_read] = "read",
[SYS_kill] = "kill",
[SYS_exec] = "exec",
[SYS_fstat] = "fstat",
[SYS_chdir] = "chdir",
[SYS_dup] = "dup",
[SYS_getpid] = "getpid",
[SYS_sbrk] = "sbrk",
[SYS_sleep] = "sleep",
[SYS_uptime] = "uptime",
[SYS_open] = "open",
[SYS_write] = "write",
[SYS_mknod] = "mknod",
[SYS_unlink] = "unlink",
[SYS_link] = "link",
[SYS_mkdir] = "mkdir",
[SYS_close] = "close",
};

syscall()中添加下面一句:

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

第二部分:date System Call

第二部分要求我们完成一个date 系统调用,获取当前UTC时间并返回。

实现准备

首先,我们需要一个读取硬件时间的函数,cmostime(),这个函数定义在lapic.c。其次我们需要一个描述时间的结构体struct rtcdate(位于date.h),我们需要将一个rtcdate对象指针传递给cmostime()。我们可以参考其他系统调用的实现,输入

1
grep -n uptime *.[chS]

查看和uptime系统调用有关的所有内容,并参考其完成date系统调用。根据grep -n uptime *.[chS]的结果,将该添加的函数声明及宏定义添加了

1
2
3
4
5
6
syscall.c:106:extern int sys_date(void);
syscall.c:130:[SYS_date] sys_date,
syscall.c:156: [SYS_date] = "date",
syscall.h:23:#define SYS_date 22
user.h:26:int date(struct rtcdate*);
usys.S:32:SYSCALL(date)

其次,我们创建一个date.c文件,这个文件中包含两部分,第一,这个文件包含一个main函数,即我们编译后可以产生可执行二进制文件date,输入命令date后,就会调用这个文件;第二,这个文件包含了date的实现(这个是错误的)。

然而,当我在date.c中添加了date函数定义后,我遇到了如下错误:

1
2
/home/duan/Code/OS/xv6-public/usys.S:32: multiple definition of `date'
date.o:/home/duan/Code/OS/xv6-public/date.c:6: first defined here

提示我在文件usys.S中,date已经定义过了,打开usys.S,里面内容如下:

1
2
3
4
5
6
7
8
9
10
11
#define SYSCALL(name) \
.globl name; \
name: \
movl $SYS_ ## name, %eax; \
int $T_SYSCALL; \
ret

SYSCALL(fork)
SYSCALL(exit)
...
SYSCALL(date)

date带入宏定义并展开,得到结果如下:

1
2
3
4
5
.globl date;
date:
movl $SYS_date, %eax;
int $T_SYSCALL;
ret

说明我们在usys.S中,已经通过汇编的方式,定义了date这个函数,这个函数的内容是将SYS_date移到eax,然后通过中断的方式调用sys_date,因此我们不需要再定义date,只需要编写好sys_date即可。但是这个时候我又疑惑了,我们的date()是有参数的,输入的是struct rtcdate*,但是sys_开头的系统调用函数都是没有参数的

1
2
3
4
extern int sys_link(void);     // 系统调用全都没有参数
extern int sys_mkdir(void);
extern int sys_mknod(void);
extern int sys_open(void);

那么该如何处理输入参数呢?

解决输入参数的问题

后来我经过了一番寻找,终于找到了一个类似的需要输入参数的系统调用,即sys_mkdir。而mkdir.c中对mkdir

实现步骤

sys_date实现

sysproc.c中实现sys_date()函数,练习系统调用的编写过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int 
sys_date(void)
{
// Your code
char *r;
acquire(&tickslock);
if(argptr(0, &r, sizeof(struct rtcdate)) < 0 ){ // 如果输入的第一个参数有误,那么释放锁,然后返回
release(&tickslock);
return -1;
}
cmostime((struct rtcdate*)r);
release(&tickslock);
return 0;
}

在Makefile中添加调用

为了使date命令能够在xv6的shell中使用,需要在MakefileUPROGS定义中添加_date

date可执行程序实现

date.c中加入如下程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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...

exit();
}

结果

最后实现的结果如下,运行make qemu打开操作系统后,输入date,将显示系统时间(命令行中的777不知道是啥,好像是在qemu和shell切换时会产生)

图片名称

参考文献

0%