XV6:增加系统调用
本文介绍了XV6
操作系统(Unix Version 6)中系统调用的工作流程,此外也介绍了如何为XV6
操作系统添加函数调用
1. 目标
稍后我们会讲解XV6
中的系统调用的工作原理,包括如何从用户态跳转到内核态,系统调用功能号等等。
但是光听不练假把式,所以为了更好的能够去理解XV6
中系统调用的工作原理,我们还要实践一下,也就是说为XV6
增加新的系统调用。
本文中我们需要增加两个系统调用,分别是trace(const char *pathname)
和getcount()
这两个系统调用的功能是,trace
接受一个文件路径,此后系统开始追踪该文件,被追踪的文件每被打开一次,那么计数器就会加一,而getcount
将会返回该文件被打开的次数。
两个系统调用的函数原型如下:
int trace(const char *pathname)
int getcount(void)
接下来我们将首先介绍XV6
中系统调用的原理,而后再增加这两个系统调用
2. 系统调用全流程
前面我们介绍了XV6
项目的Makefile
,我们从Makefile
的kernel
和fs.img
这两个目标中就能够看出来,操作系统本身就是一个程序,而用户写的程序和操作系统这个大程序在地位上是相等的,大家都有main
函数。
例如,内核的main
函数如下
// Bootstrap processor starts running C code here.
// Allocate a real stack and switch to it, first
// doing some setup required for memory allocator to work.
int
main(void)
{
kinit1(end, P2V(4*1024*1024)); // phys page allocator
kvmalloc(); // kernel page table
mpinit(); // detect other processors
lapicinit(); // interrupt controller
seginit(); // segment descriptors
picinit(); // disable pic
ioapicinit(); // another interrupt controller
consoleinit(); // console hardware
uartinit(); // serial port
pinit(); // process table
tvinit(); // trap vectors // == 中断向量表
binit(); // buffer cache
fileinit(); // file table
ideinit(); // disk
startothers(); // start other processors
kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
userinit(); // first user process // == 开启第一个用户进程
mpmain(); // finish this processor's setup
}
而用户编写的ls
这个程序中的main
函数如下
int
main(int argc, char *argv[])
{
int i;
if(argc < 2){
ls(".");
exit();
}
for(i=1; i<argc; i++)
ls(argv[i]);
exit();
}
因此,操作系统和用户程序都是相互独立的、完整的程序。
那么就存在一个问题,系统调用究竟是如何工作的呢?
A. 不同视角下的系统调用
在讲解系统调用的工作原理之前,我们先分别从用户程序(程序员)和操作系统(内核开发者)的角度分别来看看系统调用的样子
User Program’s (Developer’s) Perspective
我们首先从用户程序的视角来看看系统调用的样子。
test_trace.c
是我在已经写好了需要新增加的两个系统调用之后为了测试写的一个用户程序。
// User program test_trace.c
#include "types.h"
#include "stat.h"
#include "user.h"
#include "fcntl.h"
int main(int argc, char *argv[])
{
int stdout = 1;
printf(stdout, "Start my syscall testing...\n");
trace("ls");
trace("pwd");
trace("test");
trace("666");
printf(stdout, "ls count = %d\n", getcount("ls"));
open("ls", O_RDONLY);
printf(stdout, "ls count = %d\n", getcount("ls"));
open("ls", O_RDONLY);
printf(stdout, "ls count = %d\n", getcount("ls"));
open("ls", O_RDONLY);
printf(stdout, "ls count = %d\n", getcount("ls"));
open("pwd", O_RDONLY);
printf(stdout, "pwd count = %d\n", getcount("pwd"));
open("test", O_RDONLY);
printf(stdout, "test count = %d\n", getcount("test"));
open("pwd", O_RDONLY);
printf(stdout, "pwd count = %d\n", getcount("pwd"));
open("666", O_RDONLY);
printf(stdout, "666 count = %d\n", getcount("666"));
printf(stdout, "test finished\n");
exit();
}
从上面的代码中其实就能够看出来,trace
和 getcount
虽然说是两个系统调用,可是在用户程序的视角看来,系统调用的外貌和函数没有什么两样。故
From the perspective of user program, system call are just like function calls
虽然在外形、使用上系统调用和和我们自己定义的函数差不多,但是我们需要知道,我们调用自己写的函数的时候,所有的功能其实都是我们自己写的。但是对于系统调用来说,功能都是由操作系统提供的,即:
By using these special function (System Call), the user program can leverage functionalities offered by the kernel
Kernel’s (Operating System’s) Perspective
我们上面讲解了我们程序员在写代码时候所看到的系统调用的样子,接下来看看操作系统眼中的系统调用是什么样子。
我们以kill
这个系统调用为例进行讲解。上面说到,系统调用的功能由操作系统提供,因此kill
的函数定义在内核的源代码proc.c
中
// proc.c
// Kill the process with the given pid.
// Process won't exit until it returns
// to user space (see trap in trap.c).
int
kill(int pid)
{
struct proc *p;
acquire(&ptable.lock);
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p->pid == pid){
p->killed = 1;
// Wake process from sleep if necessary.
if(p->state == SLEEPING)
p->state = RUNNABLE;
release(&ptable.lock);
return 0;
}
}
release(&ptable.lock);
return -1;
}
所以,其实站在内核的角度,所谓的系统调用只不过是内核程序中定义的函数而已,即:
So, from the perspective of kernel, system call are just normal functions defined in kernel.
可是,这样就存在一个很关键的问题:我们在上面说过,操作系统和用户程序都是独立的程序。而我们在编译的时候是分别编译的用户程序和内核,我们并没有将两者链接到一起。但是我们却能够在用户程序中,调用在操作系统这个独立的程序中定义的函数。
C. 从用户程序到内核
为了解决上面讲的我们,我们接下来讲解从用户程序到内核的全过程。
1) usys.S
System call的第一步就是usys.S
这个程序。
usys.S
是一个AT&T
格式的汇编程序,其中主要定义了一个宏函数SYSCALL
#include "syscall.h"
#include "traps.h"
#define SYSCALL(name) \
.globl name; \
name: \
movl $SYS_ ## name, %eax; \
int $T_SYSCALL; \
ret
SYSCALL(fork)
SYSCALL(exit)
SYSCALL(wait)
SYSCALL(pipe)
SYSCALL(read)
SYSCALL(write)
SYSCALL(close)
SYSCALL(kill)
SYSCALL(exec)
SYSCALL(open)
SYSCALL(mknod)
SYSCALL(unlink)
SYSCALL(fstat)
SYSCALL(link)
SYSCALL(mkdir)
SYSCALL(chdir)
SYSCALL(dup)
SYSCALL(getpid)
SYSCALL(sbrk)
SYSCALL(sleep)
SYSCALL(uptime)
汇编中##
是字符拼接操作符, 所以如果我们把fork
作为SYSCALL
的参数传入的话,宏函数的内容就会像下面这样
; SYSCALL(fork) will expand as
#define SYSCALL(fork) \
.globl fork; \
fork: \
movl $SYS_fork, %eax; \
int $T_SYSCALL; \
ret
而$SYS_call
这个符号变量定义在 syscall.h
中
// syscall.h
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
#define SYS_pipe 4
#define SYS_read 5
#define SYS_kill 6
#define SYS_exec 7
#define SYS_fstat 8
#define SYS_chdir 9
#define SYS_dup 10
#define SYS_getpid 11
#define SYS_sbrk 12
#define SYS_sleep 13
#define SYS_uptime 14
#define SYS_open 15
#define SYS_write 16
#define SYS_mknod 17
#define SYS_unlink 18
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
而符号变量T_SYSCALL
的值是64
,定义在traps.h
中
// These are arbitrarily chosen, but with care not to overlap
// processor defined exceptions or interrupt vectors.
#define T_SYSCALL 64 // system call
#define T_DEFAULT 500 // catchall
总的来说,宏函数SYSCALL
干的是就是把系统调用号(System Call Number)存到eax
寄存器中,而后使用软中断指令int
调用64
号中断。
In summary, the macro function SYSCALL
defines a assembly function that store the system call number into eax
register and then call #64 interrput.
And usys.S
simply defines assembly functions for all system call.
因此,usys.S
这个程序中其实就是为每个系统调用定义了一个汇编函数,汇编函数的作用就是把系统调用号放入eax
中
2) vector.S
系统调用的下一步就是在vector.S
中。vector.S
中定义了所有的中断处理函数。
SYSCALL
宏的最后跳转到了64
号中断,64
号中断的定义如下
.globl vector64
vector64:
pushl $0
pushl $64
jmp alltraps
AT&T
格式的汇编中,立即数前要加$
,因此64
号中断的内容就是先把0
压入栈,然后再把64
压入栈。最后再跳转到alltraps
函数中
3) trapasm.S
alltraps
是一个汇编函数,定义在trapasm.S
中。alltraps
函数的定义如下
.globl alltraps
alltraps:
# Build trap frame.
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal
# Set up data segments.
movw $(SEG_KDATA<<3), %ax
movw %ax, %ds
movw %ax, %es
# Call trap(tf), where tf=%esp
pushl %esp
call trap
addl $4, %esp
alltraps
函数干了三件事情:
- 首先把
ds
、es
、fs
和gs
等段寄存器的值压入栈中,然后再把通用寄存器的值压入栈中 - 重新设置数据为内核数据段
- 调用
trap
函数
4) trap.c
trap
是一个C语言函数,定义在trap.c
中。
这里我们首先需要关注的是:
trap
函数接受一个struct trapframe *
类型的参数tf
。C语言间相互调用函数时候传递参数是非常简单的,例如我们在
my_trap_call_test.c
文件中想要调用trap
函数的话,直接以trap(mytf)
这样的形式调用即可。可问题是,
trap
这个C语言函数是被trapasm.S
汇编程序中alltraps
汇编函数调用的。所以关键问题就在于:汇编语言调用C语言函数的时候,该如何传参?
//PAGEBREAK: 41
void
trap(struct trapframe *tf)
{
if(tf->trapno == T_SYSCALL){
if(myproc()->killed)
exit();
myproc()->tf = tf;
syscall();
if(myproc()->killed)
exit();
return;
}
switch(tf->trapno){
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
lapiceoi();
break;
case T_IRQ0 + IRQ_IDE:
ideintr();
lapiceoi();
break;
case T_IRQ0 + IRQ_IDE+1:
// Bochs generates spurious IDE1 interrupts.
break;
case T_IRQ0 + IRQ_KBD:
kbdintr();
lapiceoi();
break;
case T_IRQ0 + IRQ_COM1:
uartintr();
lapiceoi();
break;
case T_IRQ0 + 7:
case T_IRQ0 + IRQ_SPURIOUS:
cprintf("cpu%d: spurious interrupt at %x:%x\n",
cpuid(), tf->cs, tf->eip);
lapiceoi();
break;
//PAGEBREAK: 13
default:
if(myproc() == 0 || (tf->cs&3) == 0){
// In kernel, it must be our mistake.
cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",
tf->trapno, cpuid(), tf->eip, rcr2());
panic("trap");
}
// In user space, assume process misbehaved.
cprintf("pid %d %s: trap %d err %d on cpu %d "
"eip 0x%x addr 0x%x--kill proc\n",
myproc()->pid, myproc()->name, tf->trapno,
tf->err, cpuid(), tf->eip, rcr2());
myproc()->killed = 1;
}
解决汇编传参给C语言函数的关键就是使用栈,即:
- 当我们在C语言间调用函数的时候,编译器会自动帮我们把主调函数中的参数值压入栈中
- 而函数调用本质上是通过
jmp
指令实现的,因此sp
和ss
这两个保存了栈顶和栈底的寄存器的值是没有改变的 - 因此,在被调函数中通过出栈就可以获得主调函数中传递的参数值
通过栈这种方式,C语言实现了主调函数为被调函数中的形参赋值。而编译器则帮助我们完成了压栈和出栈的过程。
因此,现在我们在汇编中调用C语言函数的话,我们需要自己把参数全部压入到栈中。由于压栈和出栈的顺序是反过来的,所以我们需要去看一下trapframe
这个结构体的内容。
trapframe
的定义和alltraps
压栈的顺序如下:
// definition of trapframe in x86.h
//PAGEBREAK: 36
// Layout of the trap frame built on the stack by the
// hardware and by trapasm.S, and passed to trap().
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;
};
// push in alltraps
.globl alltraps
alltraps:
# Build trap frame.
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal
# Set up data segments.
movw $(SEG_KDATA<<3), %ax
movw %ax, %ds
movw %ax, %es
# Call trap(tf), where tf=%esp
pushl %esp
call trap
addl $4, %esp
我们可以看到对应如下:
alltraps
压栈的内容和trapframe
中25行以前的内容一一对应。- 26行的
trapno
是在vector.S
中压栈的,其值就是中断号的值,即64
号中断
最关键的是,目前我们的程序已经运行到了trap.c
中,而trap.c
是内核程序的一部分,换而言之,我们此刻正在执行内核代码,即:
- 我们已经进入了内核态
- 我们从用户态跳转到了内核态
因此,操作系统使用三个汇编语言函数来实现从用户程序跳转到内核程序中,从而使得程序A中运行定义在程序B中的函数称为可能。而实现跳转的关键,就是通过中断
话说回来,上面我们说到,运行到trap
函数其实就已经在执行内核代码。
我们精简一下trap
函数,主要看看其中的逻辑。很明显,就是根据trapno
来判断要执行那些动作:
- 时钟中断发生了之后,调用操作系统的Scheduler来调度下一个程序
- IO中断发生了之后,调用驱动和硬件交互
- 处理系统调用中断
因此trap
函数里面的逻辑主要就是根据不同的中断号来执行不同的代码。
//PAGEBREAK: 41
void
trap(struct trapframe *tf)
{
if(tf->trapno == T_SYSCALL){
...
}
switch(tf->trapno){
case T_IRQ0 + IRQ_TIMER:
...
break;
case T_IRQ0 + IRQ_IDE:
...
break;
case T_IRQ0 + IRQ_IDE+1:
// Bochs generates spurious IDE1 interrupts.
break;
case T_IRQ0 + IRQ_KBD:
...
break;
case T_IRQ0 + IRQ_COM1:
...
break;
case T_IRQ0 + 7:
case T_IRQ0 + IRQ_SPURIOUS:
...
break;
//PAGEBREAK: 13
default:
...
}
我们这里因为关注如何给XV6
增加一个系统调用,所以我们其实真正关注的内容就是第一个if
中的内容,即
if (tf->trapno == T_SYSCALL)
{
if (myproc()->killed)
exit();
myproc()->tf = tf;
syscall();
if (myproc()->killed)
exit();
return;
}
我们在上面说过trapno
中的值是在vector.S
中压入栈的,而vector64
就将trapno
的值设置为了64
,和T_SYSCALL
的值一样。
因此,操作系统就明白了此时发生了系统调用,于是就调用syscall
去处理系统调用
D. Syscall函数
接下来,我们关注syscall
函数。syscall
函数是一个C语言函数,定义在syscall.c
中。
相关的代码如下
// syscall.c
extern int sys_chdir(void);
extern int sys_close(void);
extern int sys_dup(void);
extern int sys_exec(void);
extern int sys_exit(void);
extern int sys_fork(void);
extern int sys_fstat(void);
extern int sys_getpid(void);
extern int sys_kill(void);
extern int sys_link(void);
extern int sys_mkdir(void);
extern int sys_mknod(void);
extern int sys_open(void);
extern int sys_pipe(void);
extern int sys_read(void);
extern int sys_sbrk(void);
extern int sys_sleep(void);
extern int sys_unlink(void);
extern int sys_wait(void);
extern int sys_write(void);
extern int sys_uptime(void);
static int (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
};
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]();
} else {
cprintf("%d %s: unknown sys call %d\n",
curproc->pid, curproc->name, num);
curproc->tf->eax = -1;
}
}
其中,比较关键的如下:
- 变量
syscalls
是一个函数指针数组,即数组中的每一个元素都是一个指向函数的指针。 而SYS_xxx
这些宏常量的值都定义在syscall.h
中 (参考 syscall.h) - 此外,系统调用号(Syatem Call Number)存储在
eax
中 (参考 usys.S).
因此,syscall
函数首先通过当前进程的eax
寄存器获得系统调用号(line #55),然后根据系统调用号去从syscalls
函数数组中选择对应的函数去执行(line
57)
需要注意的是,因为函数数组中的函数都是在别的文件中定义的,所以需要在最前面使用 extern
关键字去声明一下外部符号常量,例如:sys_exit
、sys_close
和 sys_mkdir
E. sys_XXX系统调用
最后,我们看一下sys_xxx
之类的系统调用的定义(以sys_kill
为例)
sys_kill
定义在sysproc.c
中
// sysproc.c
int
sys_kill(void)
{
int pid;
if(argint(0, &pid) < 0)
return -1;
return kill(pid);
}
而kill
则定义在在proc.c
中。
// proc.c
// Kill the process with the given pid.
// Process won't exit until it returns
// to user space (see trap in trap.c).
int
kill(int pid)
{
struct proc *p;
acquire(&ptable.lock);
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p->pid == pid){
p->killed = 1;
// Wake process from sleep if necessary.
if(p->state == SLEEPING)
p->state = RUNNABLE;
release(&ptable.lock);
return 0;
}
}
release(&ptable.lock);
return -1;
}
F. 传递参数
在XV6
的实现中,syscall
中使用函数数组就是为了方便未来扩展系统调用,这样未来我们如果自己想要添加系统调用的话,直接给数组中新增加函数就行了。可是这样做又会造成新的问题,即参数传递问题。
用户想要使用系统调用的话,需要include user.h
这个头文件,其中提供了所有系统调用的函数原型
// user.h
// system calls
int fork(void);
int exit(void) __attribute__((noreturn));
int wait(void);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
可以有一个问题,我们上面在syscall.c
的syscalls
函数数组中所有的函数都是int xxxx(void)
,即所有的函数都是不接受参数的(参考 syscall.c)
extern int sys_chdir(void);
extern int sys_close(void);
extern int sys_dup(void);
extern int sys_exec(void);
extern int sys_exit(void);
extern int sys_fork(void);
extern int sys_fstat(void);
extern int sys_getpid(void);
extern int sys_kill(void);
extern int sys_link(void);
extern int sys_mkdir(void);
extern int sys_mknod(void);
extern int sys_open(void);
extern int sys_pipe(void);
extern int sys_read(void);
extern int sys_sbrk(void);
extern int sys_sleep(void);
extern int sys_unlink(void);
extern int sys_wait(void);
extern int sys_write(void);
extern int sys_uptime(void);
但是很多系统调用都是需要参数的,例如kill
就需要进程的PID。因此,为了解决参数传递问题,XV6
中又提供了新的机制。
前面解释过,C语言通过栈传递参数。而我们C语言再声明函数的时候,通过int xxx(char a, int b)
来通知编译器,主调函数调用函数前把一个char
类型的数据(一般是一个字节)先压入栈中,而后再把一个int
类型的数据(一般是四个字节)压入栈中。而后进入被调函数之后就从栈中弹出五个字节即可。
因此,我们其实在知道的了一个函数接受的所有的参数的数据类型的情况下,我们其实可以手动从栈中去读取数据
而XV6
中提供了两个函数去从当前栈(中间sp
和ss
一直没变)中获取参数。
这两个函数分别是argstr
和argint
,定义在syscall.c
中。
// Fetch the int at addr from the current process.
int
fetchint(uint addr, int *ip)
{
struct proc *curproc = myproc();
if(addr >= curproc->sz || addr+4 > curproc->sz)
return -1;
*ip = *(int*)(addr);
return 0;
}
// Fetch the nul-terminated string at addr from the current process.
// Doesn't actually copy the string - just sets *pp to point at it.
// Returns length of string, not including nul.
int
fetchstr(uint addr, char **pp)
{
char *s, *ep;
struct proc *curproc = myproc();
if(addr >= curproc->sz)
return -1;
*pp = (char*)addr;
ep = (char*)curproc->sz;
for(s = *pp; s < ep; s++){
if(*s == 0)
return s - *pp;
}
return -1;
}
// Fetch the nth 32-bit system call argument.
int
argint(int n, int *ip)
{
return fetchint((myproc()->tf->esp) + 4 + 4*n, ip);
}
// Fetch the nth word-sized system call argument as a string pointer.
// Check that the pointer is valid and the string is nul-terminated.
// (There is no shared writable memory, so the string can't change
// between this check and being used by the kernel.)
int
argstr(int n, char **pp)
{
int addr;
if(argint(n, &addr) < 0)
return -1;
return fetchstr(addr, pp);
}
argint和fetchint函数
argint
函数用于从程序的栈中获取第n
个int
数据。而fetchint
则是给定一个地址,而后在进行完地址检查之后,从该地址中取一个int
出来。
因此argint
中计算完偏移量之后,直接把程序当前的栈顶地址+偏移量
传给fectint
函数。而fetchint
函数中直接进行的值复制,因此最后传入argint
的ip
(integer· pointer)就储存了整数值的拷贝。
而在argstr
函数中,首先调用了argint
。而调用argint
的目的是:
- 利用
argint
计算偏移地址,从而得到字符串的开头地址,而后通过fetchint
检查字符串的开头地址是否非法(地址高于栈底地址)。
最后,argstr
调用fetchstr
将字符串指针pp
指向了字符串开头的地址。
到此为止,我们就介绍完了XV6
中系统调用的全部流程以及必要的函数
3. 增加trace
和getcount
系统调用
接下来,我们就将添加trace
和getcount
这两个系统调用
1. 增加系统调用号
syscall.h
中定义了所有的系统调用号,而系统调用号将会在syscall.c
和usys.S
中用的
// my syscall
#define SYS_trace 22
#define SYS_getcount 23
2. 增加SYSCALL宏函数
接下来要做的,就是在usys.S
文件中为我们的系统调用增加宏函数。
SYSCALL(trace)
SYSCALL(getcount)
3. syscall.c
中定义函数原型
接下来我们要做的就是在syscall.c
中增加我们的函数到syscalls
函数数组中去
// my syscall
extern int sys_trace(void);
extern int sys_getcount(void);
static int (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace,
[SYS_getcount] sys_getcount
};
4. 实现trace
和getcount
最后,我们要实现这trace
和getcount
这两个系统调用,因为需要和open
系统调用配合,所以就在 sysfile.c
中定义了
// sysfile.c
// my syscall data structure
typedef struct trace_file
{
char filename[256];
int open_counter;
int trace_enabled;
} trace_file;
int num_traced;
trace_file tracing_file;
int sys_trace(void) {
// get param from stack char *path;
char *path;
int length = argstr(0, &path);
if (length < 0)
return -1;
int k = 0;
for (k = 0; k < length; k++)
tracing_file.filename[k] = path[k];
tracing_file.filename[k] = '\0';
tracing_file.open_counter = 0;
tracing_file.trace_enabled = 1;
return 0;
}
int sys_getcount(void){
if (tracing_file.trace_enabled == 1)
return tracing_file.open_counter;
else
return 0;
}
最后修改 sys_open
使得每次打开都会增加计数。
int
sys_open(void)
{
char *path;
int fd, omode;
struct file *f;
struct inode *ip;
// check if trace enabled and add counter
int length = argstr(0, &path);
if(length < 0 || argint(1, &omode) < 0)
return -1;
char *ppath = path;
char *ptf = tracing_file.filename;
if (strncmp(ppath, ptf, length) == 0)
if (tracing_file.trace_enabled)
tracing_file.open_counter += 1;
begin_op();
if(omode & O_CREATE){
ip = create(path, T_FILE, 0, 0);
if(ip == 0){
end_op();
return -1;
}
} else {
if((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op();
return -1;
}
}
if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
if(f)
fileclose(f);
iunlockput(ip);
end_op();
return -1;
}
iunlock(ip);
end_op();
f->type = FD_INODE;
f->ip = ip;
f->off = 0;
f->readable = !(omode & O_WRONLY);
f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
return fd;
}