6.S081-Lab 2 system calls实验笔记

trace

在 usys.pl 中,有用户态到内核态的跳板函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# user/usys.pl

entry("fork");
entry("exit");
entry("wait");
entry("pipe");
entry("read");
entry("write");
entry("close");
entry("kill");
entry("exec");
entry("open");
entry("mknod");
entry("unlink");
entry("fstat");
entry("link");
entry("mkdir");
entry("chdir");
entry("dup");
entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("trace"); # HERE

这个脚本在运行后会生成 usys.S 汇编文件,里面定义了每个 system call 的用户态跳板函数:

1
2
3
4
trace:		# 定义用户态跳板函数
li a7, SYS_trace # 将系统调用 id 存入 a7 寄存器
ecall # ecall,调用 system call ,跳到内核态的统一系统调用处理函数 syscall() (syscall.c)
ret

项目代码中系统调用流程:

1
2
3
4
5
user/user.h:		用户态程序调用跳板函数 trace()
user/usys.S: 跳板函数 trace() 使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c 到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c syscall() 根据跳板传进来的系统调用编号,查询 syscalls[] 表,找到对应的内核函数并调用。
kernel/sysproc.c 到达 sys_trace() 函数,执行具体内核操作

系统调用实现函数都是不带参数的,实际上系统调用传入的参数会被放在当前的寄存器中,通过kernel/syscall.c文件中的argint,argaddr,argstr等函数能够获取到

bug2: 测试trace 2147483647 grep hello README的时候不显示trace的系统调用

主要的问题出在,调用trace的时候执行到打印的时候可以看出mask没有传进来比较滞后还是0。

继续dbug发现其实trace是执行了的也就是说应该是改了mask以后才执行到系统调用这段来:

然后发现:

p->trapframe->a0 = syscalls[num]();这句话一定要放在uint64 trace_mask= p->trace_mask;的前面就可以修复

主要原因:每一个系统调用有一个返回值,这个返回值保存在trapframe->a0中,如果系统调用号未知,就保存-1

最后systemcall:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void
syscall(void)
{
int num;
struct proc *p = myproc();

num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();
uint64 trace_mask= p->trace_mask;
if (1 & (trace_mask>>num)) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
}

} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}

Sysinfo

实现功能:添加 sysinfo系统调用, 用于收集系统运行时候的信息.系统调用的入参是一个结构体指针: struct sysinfo (see kernel/sysinfo.h).

1
2
3
4
5
struct sysinfo {
uint64 freemem; // amount of free memory (bytes)
uint64 nproc; // number of process
};

内核程序负责填写这个结构体: freemem 字段被设置成内存空闲的比特数, nproc字段被设置成 the number of processes whose state is not UNUSED

首先先在内核实现这个系统中断(流程和上一个trace实现过程一致)

sysinfo需要负责将struct sysinfo结构体复制回用户空间; 这一过程的实现可以参考sys_fstat() (kernel/sysfile.c) 和filestat() (kernel/file.c) 中的实现过程,主要通过调用 copyout()实现。如下所示

1
2
3
4
5
6
7
8
9
10
uint64
sys_fstat(void)
{
struct file *f;
uint64 st; // user pointer to struct stat

if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
return -1;
return filestat(f, st);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
int
filestat(struct file *f, uint64 addr)
{
struct proc *p = myproc();
struct stat st;

if(f->type == FD_INODE || f->type == FD_DEVICE){
ilock(f->ip);
stati(f->ip, &st);
iunlock(f->ip);
if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)
return -1;
return 0;
}
return -1;
}

首先模仿上面接收传入的结构体指针argaddr(1, &st).

首先获取用户传递给系统调用的参数放入addr中(用户地址空间地址),然后在内核空间中申请一个struct sysinfo,分别调用getfreemem()getnproc()函数填充该结构体相应字段之后,调用copyout()内核空间的内存复制到用户空间的地址

注意包含头文件:#include "sysinfo.h"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint64
sys_sysinfo(void) {
uint64 addr;
if (argaddr(0, &addr) < 0)
return -1;
struct sysinfo info;
info.freemem = getfreemem(); // 获取系统空闲内存(在kernel/kalloc.c中实现)
info.nproc = getnproc(); // 获取系统当前的进程数量(在kernel/proc.c中实现)
struct proc *p = myproc();

if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
  • To collect the amount of free memory, add a function to kernel/kalloc.c

我们看一下kalloc.c的代码

空闲内存是使用一个链表来维护的:

1
2
3
4
5
6
7
8
struct run {
struct run *next;
};

struct {
struct spinlock lock;
struct run *freelist;
} kmem;

为了获取系统空闲内存字节数,需要遍历空闲内存链表,参考kalloc函数:我们可以看出memset((char*)r, 5, PGSIZE);中,一个链表指针指向的空闲内存的单位是PGSIZE:

所以我们想得到空闲内存的比特数要乘4096。每遍历一个节点就增加一个PGSIZE字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void *
kalloc(void)
{
struct run *r;

acquire(&kmem.lock);
r = kmem.freelist;
if(r)
kmem.freelist = r->next;
release(&kmem.lock);

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

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
uint64 getfreemem(){
struct run *r;
uint64 freeret=0;
acquire(&kmem.lock);
r = kmem.freelist;
while(r){
freeret++;
r=r->next;
}
release(&kmem.lock);
freeret*=PGSIZE;//memset((char*)r, 5, PGSIZE); // fill with junk
return freeret;
}
  • To collect the number of processes, add a function to kernel/proc.c

实现getnproc()可以参考 kernel/proc.c

参考这个函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
allocproc(void)
{
struct proc *p;

for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == UNUSED) {
goto found;
} else {
release(&p->lock);
}
}
return 0;
...
}

可以看出,遍历进程池,通过p->state == UNUSED来判断进程是否空闲

程序实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
uint64
getnproc(){
struct proc *p;
int cnt=0;
for(p=proc;p<&proc[NPROC];p++){
acquire(&p->lock);
if(p->state!=UNUSED){
cnt++;
}
release(&p->lock);
}
return cnt;
}