不同于lab1的几个实验,只需要在user下实现1个或几个功能函数即可,lab2开始涉及到xv6的内核,实现起来更为复杂。所以,一定要根据实验要求阅读完文档相关章节和源代码后再做实验。
System call tracing
添加系统调用sys_trace
该实验需要添加一个系统调用,首先根据提示,在user/user.h中添加一个原型,user/usys.pl中添加一个stub,kernel/syscall.h中增加trace的系统调用号。
#user/user.h
// system calls
...
int trace(int);//添加trace原型
#user/usys.pl
...
entry("trace");
#kernel/syscall.h
// System call numbers
...
#define SYS_trace 22
然后,在kernel/proc.h的struct proc中添加一个参数mask,并在kernel/sysproc.c中实现sys_trace(),获取mask值并给当前进程的mask赋值。阅读文档的4.3节可以得知,用户代码将exec需要的参数放在寄存器a0和a1中,而4.4节告诉我们可以通过argint来获取寄存器里的值。
#kernel/proc.h
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
struct proc *parent; // Parent process
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
// these are private to the process, so p->lock need not be held.
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)
int mask; // Tracing parameter
};
#kernel/sysproc.c
uint64
sys_trace(void)
{
int mask;
if(argint(0, &mask) < 0)
return -1;
myproc()->mask = mask;
return 0;
}
具体实现trace
因为还需要输出跟踪的信息,所以还需要对kernel/syscall.c进行修改。
...
extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
...
[SYS_trace] sys_trace,
};
char* syscalls_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",
[SYS_trace] "trace",
};
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]();
if((1 << num) & p->mask){//mask的二进制下为1的数位表示需要跟踪的进程
printf("%d: syscall %s -> %d\n",
p->pid, syscalls_name[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
之后,还需要对kernel/proc.c进行修改。
- 修改
fork(),让子进程继承父进程的mask。 - 此外,未调用
trace时mask应该为0,否则可能会在正常运行其他调用时输出跟踪信息,需要修改allocproc()。
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
np->mask = p->mask;
np->parent = p;
// copy saved user registers.
*(np->trapframe) = *(p->trapframe);
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd);
safestrcpy(np->name, p->name, sizeof(p->name));
pid = np->pid;
np->state = RUNNABLE;
release(&np->lock);
return pid;
}
static struct proc*
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;
found:
p->pid = allocpid();
// Allocate a trapframe page.
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
release(&p->lock);
return 0;
}
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
p->mask = 0;//给每个进程mask赋个初值即可。
return p;
}
运行结果
Sysinfo
实现sys_sysinfo
根据题意,仿照sys_fstat() (kernel/sysfile.c)和 filestat() (kernel/file.c)即可实现sys_sysinfo()的核心功能,把内核中的结构体sysinfo拷贝到用户区的指针中。
uint64
sys_sysinfo(void)
{
uint64 addr;
struct sysinfo sinfo;
if(argaddr(0, &addr) < 0)
return -1;
sinfo.freemem = getfreemem();
sinfo.nproc = getnproc();
if(copyout(myproc()->pagetable, addr, (char *)&sinfo, sizeof(sinfo)) < 0)
return -1;
return 0;
}
获取空闲内存和空闲进程
接下来,完善getfreemem()和getnproc()这两个函数。
getfreemem()
根据提示查看kernel/kalloc.c文件,从中可以看出,xv6系统维护着一个kmem.freelist链表来存储空闲页,遍历kmem.freelist即可计算出空闲内存。因为要遍历该链表,为防止冲突,需要先获取lock,计算完后再release。
uint64
getfreemem(void)
{
uint64 freemem = 0;
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
while(r){
freemem += PGSIZE;//若r存在,加上页的内存大小
r = r->next;
}
release(&kmem.lock);
return freemem;
}
getnproc()
根据提示查看kernel/proc.c,其内部定义了struct proc proc[NPROC],所以我们只要遍历这个数组,统计其中state != UNUSED的个数即可。
uint64
getnproc(void)
{
uint64 res = 0;
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++){
if(p->state != UNUSED) res++;
}
return res;
}
添加系统调用和函数声明
系统调用添加方式和trace一样,这里不再赘述。
因为在kernel/kalloc.c和kernel/proc.c增加了getfreemem()和getnproc()这两个函数,所以需要在defs.h中进行声明。
此外,kernel/sysproc.c原本没有#include "sysinfo.h",需要自行添加