6.S081 lab2
Preparation
xv6 book 部分
这一部分最核心的我觉得是x6机器从通电到创建第一个进程的过程:
1.通电,初始化然后加载bootloader,boot loader 把 kernel的代码加载到内存。
2.kernel代码从一个叫entry.S的汇编代码开始执行,它初始化了一个栈,可以让xv6运行c语言代码。然后他就call一个叫stat的c函数处。
3. start函数首先执行一些只有在机器模式才能做的配置,然后切换到监督模式(supervisor mode),并跳转到了main函数。
4. main函数初始几个设备和子系统,然后调用 userinit创建第一个进程。userinit通过调用 initcode.S,然后initcode.S又通过exec这个系统调用进入内核。exec执行完成后,返回到init进程,他会创建shell。
源码阅读
在读这个lab要求的源码的时候,在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,
[SYS_sysinfo] sys_sysinfo,
};
这是定义了一个函数指针数组变量,然后初始化,[ ]是在指定下标赋值。
System call tracing
目标是要我们写一个系统调用,来参数mask指定的系统调用,并输出 进程号,系统调用名称以及该调用的返回值。
首先要知道内核是通过查看 a7中的数据来确定是调用哪个系统调用的,然后执行该系统调用并把值存到a0中。
我们首先要修改 kernel/proc.c 中的proc结构,增加一个参数来保存mask,这个参数可以是数组也可以是其他东西,我这里用的是int
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; //trace的参数
};
然后在kernel/sysproc.c中增加一个系统调用函数,到时候syscall.c会调用这个函数。
uint64
sys_trace(void)
{
struct proc *p = myproc();
int n;
if(argint(0, &n) < 0)
return -1;
p->mask=n;
return 0;
}
这个函数很简单,就是把读取mask参数并保存到proc里。
然后要修改fork()和syscall().
fork()是要让他在fork的时候也要复制上一个进程的mask信息
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->parent = p;
np->mask=p->mask;
// 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;
}
syscall( )是要在系统调用的时候根据mask判断要不要printf。
当然为了方便输出,用一个数组保存了那些系统调用名
static char* syscall_names[24]={"","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;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();
if(((p->mask)&(1<<(p->trapframe->a7)))!=0){
printf("%d: syscall %s -> %d\n",p->pid,syscall_names[p->trapframe->a7],p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
Sysinfo
在 kernel/kalloc.c中增加一个函数,用来计算剩余的内存大小。这个直接遍历页表freelist的数量,然后乘上页大小4096就好了。
int
freemem(void){
int cnt=0;
struct run *r;
r = kmem.freelist;
while(r){
cnt+=4096;
r=r->next;
}
//printf("freemem: %d\n",cnt);
return cnt;
}
在kernel/proc.c加一个函数,计算题意要求的进程数目。
int
nproc(void){
int cnt=0;
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++){
if(p->state!=UNUSED){
cnt+=1;
}
}
//printf("nproc: %d\n",cnt);
return cnt;
}
最后在kernel/sysproc.c中增加sysinfo( )函数。
uint64
sys_sysinfo(void){
//printf("sys_sysinfo\n");
struct proc *p = myproc();
uint64 addr;
if(argaddr(0, &addr) < 0)
return -1;
/*
struct sysinfo info;
info.freemem=freemem();
info.nproc=nproc();
printf("free:%d nproc:%d\n",info.freemem,info.nproc);
if((copyout(p->pagetable, addr, (char *)&info, sizeof(info))<0))
return -1;
*/
uint64 info[2]={freemem(),nproc()};
printf("free:%d nproc:%d\n",info[0],info[1]);
if((copyout(p->pagetable, addr, (char *)info, sizeof(info))<0))
return -1;
return 0;
}
注意 struct sysinfo结构体是两个unit64的数,所以用数组存的话注意数组也要用unit64。
总结
这个lab一开始写的时候比较艰难,主要是自己没弄清系统调用的流程,弄清之后就比较容易了。
这篇博客详细介绍了xv6操作系统中从启动到创建第一个进程的过程,以及如何实现系统调用追踪。作者通过分析源码,展示了如何定义和初始化系统调用函数指针数组,并在`sys_trace`系统调用中设置和读取进程的mask参数。在`fork`函数中,确保mask信息在子进程中得以复制。在`syscall`函数中,根据mask决定是否打印系统调用信息。此外,还新增了`sys_sysinfo`系统调用来获取系统剩余内存和活跃进程数量。通过这个实验,作者对系统调用的流程有了更深入的理解。
1547

被折叠的 条评论
为什么被折叠?



