Syetem calls
这篇思路很乱,所以请确保你阅读过实验 HINT 以及如下源码:user/user.h, user.usys.pl, kernel/syscall.h, kernel/syscall.c, kernel/proc.h, kernel/proc.c, kernel/kalloc.c, kernel/vm.c。
System call tracing
本次实验和前次不同,我们并不从头写一个 c 文件,实现系统调用功能,而是在系统的架构中添加代码,来实现我们的需求。
按照 hint 一步步来。可以找到课程提供的trace
程序user/trace.c
。我们先在Makefile
里加入$U/_trace\
。此时执行sudo make qemu
会发生错误。
because the user-space stubs for the system call don't exist yet,这句话大意是:系统调用的系统空间桩代码尚不存在。stubs(桩代码)指的是为了让程序正常运行而临时占坑的、尚未编辑的代码。如果你使用python
写过代码,你应该很熟悉pass
语句,它和stubs的用途是一样的。检查 lab1 的实验代码,可以发现我们编写的一系列系统调用程序,已经填充了stubs。
因此按照hint
,我们需要在系统中添加stubs。(详见配置部分)
添加stubs之后,我们执行trace 32 grep hello README
,依然失败。这是由于我们未在内核中实现这项系统调用。
在kernel/sysproc.c
中添加sys_trace()
作为实现,并在struct proc
为trace
的实现添加一个新变量。(具体实现见源码)参考kernel/sysproc.c
里其他函数的实现,调用kernel/syscall.c
里的从用户空间检索系统调用的参数。
修改kernel/proc.c
中的fork()
函数,使得子进程在复制的时候一并复制父进程的 mask 变量。
修改kernel/syscall.c
中的syscall()
,使得输出符合需求。创建一个数组存储进系统调用名。
kernel/syscall.c
里获取系统调用参数的函数参见这个文章https://blog.csdn.net/NP_hard/article/details/121325896
源码
// kernel/proc.h struct proc
// Per-process state
struct proc {
// ......
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
int mask; // trace mask new
};
// kernel/sysproc.c sys_trace()
uint64 sys_trace(void) {
int mask;
if (argint(0, &mask) < 0)// 获取第0个寄存器的信息,并用一个int型指针指向这个寄存器。
// 类似把第0个参数赋值给int变量mask。
// 参考其他函数的写法获取第0个参数并存入 mask
return -1;
myproc()->mask = mask;
// printf("mask: %d\n", mask);
return 0;
}
// kernel/proc.c fork()
int
fork(void)
{
// ......
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
// trace mask
// 将父进程的掩码 赋给 子进程的掩码
np->mask = p->mask;
// increment reference counts on open file descriptors.
// ......
}
// kernel/syscall.c syscall()
// ......
extern uint64 sys_sysinfo(void); // new
static uint64 (*syscalls[])(void) = {
// ......
[SYS_trace] sys_trace, // new
}
// 和系统调用号对应的系统调用名
const char* syscallName[] = {// new
0, "fork", "exit", "wait",
"pipe", "read", "kill", "exec",
"fstat", "chdir", "dup", "getpid",
"sbrk", "sleep", "uptime", "open",
"write", "mknod", "unlink", "link",
"mkdir", "close", "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((p->mask >> num) & 1){// new
printf("%d: syscall %s -> %d\n", p->pid, syscallName[num], p->trapframe->a0);// new
}// new
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
配置
首先在user/user.h
中添加函数原型int trace(int)
。接下来在user/usys.pl
中添加stubentry("trace");
,并在kernel/syscall.h
为它添加一个系统调用号 22,#define SYS_trace 22
。
运行结果
sudo make qemu
...
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$ grep hello README
$ trace 2 usertests forkforkfork
usertests starting
6: syscall fork -> 7
test forkforkfork: 6: syscall fork -> 8
8: syscall fork -> 9
9: syscall fork -> 10
10: syscall fork -> 11
...
9: syscall fork -> -1
OK
6: syscall fork -> 69
ALL TESTS PASSED
判分程序结果
./grade-lab-syscall trace
make: 'kernel/kernel' is up to date.
== Test trace 32 grep == trace 32 grep: OK (1.5s)
== Test trace all grep == trace all grep: OK (0.9s)
== Test trace nothing == trace nothing: OK (1.0s)
== Test trace children == trace children: OK (9.0s)
Sysinfo
本实验要求实现函数sysinfo()
。函数要求有以下功能:获取空闲内存大小、获取非 UNUSED 的进程以及复制内核中的信息。
源码
// kernel/syscall.c
extern uint64 sys_sysinfo(void);
static uint64 (*syscalls[])(void) = {
// ...
[SYS_close] sys_close,
}
const char* syscallName[] = {
// ...
"sysinfo",
}
// kernel/sysproc.c
uint64
sys_sysinfo(void){
struct sysinfo info;
struct proc *p = myproc();
uint64 addr;
info.freemem = kfreemem();// 获取空闲内存(单位bytes )
info.nproc = cntproc();// 获取非UNUSED的进程
if(argaddr(0, &addr) < 0)// 这里为了统一,使用了argaddr获取参数,有兴趣的话可以试一下传参
return -1;
// 将内存地址addr的内容,
// copyout在 kernel/vm.c 中找到
// int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
// 根据注释可知,从内核src复制到用户dstva
if(copyout(p->pagetable, addr, (char*)&info, sizeof(info)) < 0)
return -1;
return 0;
}
// kalloc.c kfreemem()
// 观察函数kalloc和kfree,以及结构体kmem
// 可以得知内存的组织结构类似链表
// 链表的头结点为keme.freelist
// 每次分配/回收空间都对freelist操作
// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc(). (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(void *pa)
{
struct run *r;
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree");
// Fill with junk to catch dangling refs.
memset(pa, 1, PGSIZE);
r = (struct run*)pa;
acquire(&kmem.lock);
r->next = kmem.freelist;
kmem.freelist = r;
release(&kmem.lock);
}
// 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;
if(r)
kmem.freelist = r->next;
release(&kmem.lock);
if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}
// new add
// get free memory
// 因此,我们遍历有多少个结点,就可以得到空余空间大小了
// 每个节点可以提供1个PGSIZE大小的空间
uint64
kfreemem(void){
struct run *r;
uint64 cnt = 0;
acquire(&kmem.lock);
r = kmem.freelist;
while(r){
r = r->next;
cnt += PGSIZE;
}
//printf("%d\n", cnt);
release(&kmem.lock);
return cnt;
}
// proc.c cntproc()
// 观察函数allocproc和结构体proc
// 可以得知,进程以数组的形式存储
// 仿照allocproc的遍历方式,得到函数cntproc
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
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;
return p;
}
// new add
// count the number of processes
// 统计不为UNUSED的进程,遍历即可
uint64
cntproc(void){
struct proc *p;
uint64 cnt = 0;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state != UNUSED) {
cnt++;
}
release(&p->lock);
}
return cnt;
}
配置
与trace()
类似,首先在user/user.h
中添加结构体声明struct sysinfo
以及函数原型int sysinfo(struct sysinfo *)
。接下来在user/usys.pl
中添加stubentry("sysinfo");
,并在kernel/syscall.h
为它添加一个系统调用号 23。
运行结果
sudo make qemu
...
xv6 kernel is booting
hart 2 starting
hart 1 starting
init: starting sh
$ sysinfotest
sysinfotest: start
sysinfotest: OK
判分程序结果
./grade-lab-syscall sysinfotest
make: 'kernel/kernel' is up to date.
== Test sysinfotest == sysinfotest: OK (2.0s)
(Old xv6.out.sysinfotest failure log removed)