mit 6.S081 Lab2 System Call

做这个实验之前需要把 xvb book 第二章和第四章的 4.3 4.4看了, 有助于理解。

System call tracing

实验大意

实现trace指令, trace接受mask以及调用的程序和它的参数。mask代表要追踪哪些系统调用,这些都可以在kernel/syscall.h中看到

过程

大致思路是通过trace函数将进程中新添加的trace_mask设置好,当syscall调用时,通过trace_mask和被调用的系统调用的mask进行相与,如果大于零,就打印该系统调用的信息。

这个lab已经提供了user/trace.c这个函数,所以不需要自己写,但是需要在user/user.h,user/usys.pl,以及kernel/syscall.h添加声明,还需要在kernel/syscall.c中的函数指针数组中,添加后面需要我们自己在 kernel/sysproc.c添加的sys_trace。

1
2
// user/user.h
int trace(int);
1
2
// user/usys.pl
entry("trace");
1
2
// kernel/syscall.h
#define SYS_trace 22
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
//kernel/syscall.c
static uint64 (*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,//新添加的函数指针,指向sysproc.c中的sys_trace
}

然后向kernel/proc.h 中的 struct proc 添加 int trace_mask

1
2
3
4
struct proc {
......
int trace_mask;
};

user调用trace时,实际上调用的是sys_trace,所以我们需要添加在 kernei/sysproc.c的sys_trace函数。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// kernel/sysproc.c
//这里我将主要的函数写在了 kernel/proc.c中
uint64
sys_trace(void)
{
int trace_mask = 0;
if(argint(0, &trace_mask) < 0)// 通过argint读取调用trace时所传入的参数
return -1;
return trace(trace_mask);
}

//kernel/proc.c
int trace(int mask)
{
struct proc *p = myproc();//获取当前进程
p->trace_mask = mask;// 将当前进程的trace_mask设置为 mask
return 0;
}
//kernel/defs.h
//在proc定义的函数,需要在 defs.h中添加声明,这样sysporc.c中才可以使用

//proc.h
.......
int trace(int);

我们还需要在调用fork生成子进程的时候,将父进程的trace_mask复制给子进程

1
np->trace_mask = p->trace_mask;

最后需要在syscall中,判断是否被调用的函数包含在mask中,如果包含在mask中,则输出信息,因为输出信息需要输出系统调用的函数名,所以还需要添加一个字符串数组。

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
35
36
37
38
39
40
41
42
43
44
45
kernel/syscall.h
char * fun_name[35]={
"null",//在函数的mask都是从1开始的,所以null作为占位符。
"fork",
"exit",
"wait",
"pipe",
"read",
"kill",
"exec",
"fstat",
"chdir",
"dup",
"getpid",
"sbrk",
"sleep",
"uptime",
"open",
"write",
"mknod",
"unlink",
"link",
"mkdir",
"close",
"trace",
"sysinfo"
};
void
syscall(void)
{
int num;
struct proc *p = myproc();

num = p->trapframe->a7;
int mask_value = 1 << num;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {

p->trapframe->a0 = syscalls[num]();
if((mask_value & (p->trace_mask)) != 0)//如果当前函数的mask在trace_mask中,就打印信息,a0寄存器中包含函数的返回值。
printf("%d: syscall %s -> %d\n", p->pid, fun_name[num], p->trapframe->a0);
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}

这样我们就完成了lab2的第一个实验。

Sysinfo

实验大意

实现sysinfo,获取系统中有多少非UNUSED的进程和空闲内存字节数。

过程

和上一个system call 实验一样,需要将sysinfo声明在各个文件中,实验介绍中已经给出来了。
然后就是获取当前不是UNUSED的进程,比较简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// kernel/proc.c
int count_proc(void)
{
int count = 0;
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++) //proc是全局变量,是进程的数组,NPROC是进程的个数
{
acquire(&p->lock);// 先给所查询的进程上锁
if(p->state != UNUSED)// 如果state不是 unused,则count++
count ++;
release(&p->lock);
}
return count;
}

然后就是获取空闲内存字节数, xv6是通过freelist管理的内存,每个页大小为PAGESIZE,相关代码可以参考 kernel/kalloc.c中的kalloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
struct run *r;

acquire(&kmem.lock);
r = kmem.freelist; // 获取freelist的头指针
if(r)
kmem.freelist = r->next; //获取一个freelist中的页
release(&kmem.lock);

if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}

通过阅读这个代码,就可以知道想计算空闲内存的大小,只需要计算freelist中有多少个页就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint64 get_freemem_num(void)
{
struct run *r;
uint64 num = 0;
acquire(&kmem.lock);// 上锁
r = kmem.freelist;
while(r)
{
num ++; // 如果当前指针指向的是一个页,num++
r = r->next;
}
release(&kmem.lock);
return num * PGSIZE; // 通过页的个数 乘上 页的大小来获取内存空闲大小
}

至此,我们需要的两个函数就写完了,剩下的就是完成kernel/sysproc.c 中的sysinfo函数了,需要注意的是,当程序调用系统函数的时候,进入的是 kernel mode 无法通过 user mode 中的虚拟地址直接将数据存入虚拟地址所指向的物理地址,因为目前xv6是有一个全局的kernel page table,其中没有每个进程的user table 的映射,所以需要使用copyout来获取指定page table中的物理地址,并将数据存入进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint64
sys_sysinfo(void)
{
uint64 st;
struct proc *p = myproc();
struct sysinfo info;
if(argaddr(0, &st) < 0)
{
printf("sys_sysinfo: get addr error\n");
return -1;
}
info.freemem = get_freemem_num();
info.nproc = count_proc();
if(copyout(p->pagetable, st, (char *)&info, sizeof info) < 0) // 获取物理地址,并将info存入其中。
{
printf("sys_sysinfo: copyout error\n");
return -1;
}
return 0;
}

在添加函数的时候,不要忘记取 defs.h中添加对应的声明。

我们lab2整个就完成了^_^

作者

xiaomaotou31

发布于

2021-10-01

更新于

2021-10-02

许可协议

评论