MIT 6.S081 Lab: System calls
开始学习MIT的操作系统6.S081课程的第二个Lab。Lab内容为内核添加两个系统调用,比较简单。
增加系统调用的通用流程,
- 在 user/usys.pl 中增加 stub for syscalls 以便生成相应的 usys.S 代码。
- 在 user/user.h 中增加系统调用的函数声明,以便用户调用。
- 在 kernel/syscall.h 中增加系统调用号,其作为宏会易于使用。
- 在 kernel/syscall.c 中增加系统调用的内核实现函数(一般为 sys_xxx)的 extern 声明,增加函数指针数组syscalls的内容(系统调用号对应内核实现)。
- 编写系统调用的内核实现函数。
system call tracing
增加一个系统调用 trace 用于追踪系统调用的信息。系统调用 trace 接受一个指示追踪的系统调用号的参数 mask
,该参数通过位指示。例如, mask == (1 << SYS_fork);
代表需要追踪系统调用 fork 的信息。如果某个系统调用 c
在 mask
中,内核需要为系统调用 c
的每次调用打印一行,内容包括 PID,系统调用名与该次调用的返回值。系统调用 trace 需要追踪当前进程(caller)之后的系统调用,以及任何后代进程(通过fork)的系统调用。
难点在于所有通过fork得到的后代进程都需要追踪。所以需要在 kernel/proc.h 的进程结构体中增加一个字段 traceMask
,用于标记当前进程需要追踪的系统调用号。我这里添加到 process private 部分中。
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
int traceMask; // Trace mask bits
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
简单实现就是,在 kernel/sysproc.c 中添加 sys_trace 函数,获取参数 mask
后记录到当前进程的 traceMask
字段中。同时在 kernel/proc.c 的 fork 函数实现里,复制父进程的 traceMask
字段。
// in fork function kernel/proc.c
// copy parent's trac mask
np->traceMask = p->traceMask;
// in kernel/sysproc.c
uint64
sys_trace(void)
{
int mask;
if(argint(0, &mask) < 0)
return -1;
myproc()->traceMask = mask;
return 0;
}
实现内核打印的部分在 kernel/syscall.c 的函数 syscall 中。得到系统调用的返回值后,根据系统调用号是否在 traceMask
中,决定打印 PID,系统调用名(可以用一个数组,系统调用号映射到名字字符串)与返回值。需要注意 trace 这个系统调用也可能被追踪,而且判断 traceMask
是在调用 trace 之后的,所以不能提前用局部变量记录 traceMask
的值。这部分不放代码。
Sysinfo
添加一个系统调用 sysinfo 收集当前的系统信息。系统调用 sysinfo 接受一个指向 struct sysinfo(声明在 kernel/sysinfo.h 中) 的指针作为参数,将收集的系统信息写入该指针指向的内存中。内核实现需要填充 struct sysinfo 的freemem与nproc字段。字段 freemem 指示当前系统空闲内存的字节数,字段 nproc 指示状态不是UNUSED
的进程数目。
在 kernel/kallo.c 中增加 getfreemem 函数,实现上遍历 kmem.freelist
中的物理页数目,计算空闲内存字节数。在 kernel/proc.c 中增加 getnproc 函数,实现上遍历进程状态表 struct proc proc[NPROC];
,计数状态不是UNUSED
的进程。这两个实现比较简单,均为遍历,对于全局的数据结构小心加锁即可。这里仅放 kernel/sysfile.c 中 sys_sysinfo 函数实现代码。
uint64
sys_sysinfo(void)
{
uint64 upsi; // user pointer to struct sysinfo
struct sysinfo si;
struct proc *p = myproc();
if(argaddr(0, &upsi) < 0)
return -1;
si.freemem = getfreemem();
si.nproc = getnproc();
if(copyout(p->pagetable, upsi, (char *)&si, sizeof(struct sysinfo)) < 0) {
return -1;
}
return 0;
}
先通过 argaddr 获取参数指针的地址(用户空间),在内核空间获取系统信息后,需要注意的是写回用户空间。这里使用函数 copyout 从用户进程的页表p->pagetable
中找到虚拟地址upsi
指向的物理地址,然后写入内核空间里收集的系统信息struct sysinfo si;
内容。
Conclusion
Lab的内容很简单,添加系统调用的过程主要还是熟悉内核系统调用的调用流程,如何从用户空间获取数据(系统调用的参数在 trapframe 中),得到系统调用的返回值(亦在 trapframe),如何在内核空间向用户空间写数据(通过用户进程页表找到物理页),简单熟悉一些内核的数据结构如 struct proc
, kmem.freelist
与进程表。