系列文章目录
LAB2
文章目录
前言
在开始前,要熟悉以下几个概念:
- 用户空间的系统调用代码在user/user.h和user/usys.pl
- 内核空间的系统调用代码在kernel/syscall.h中声明,在kernel/syscall.c中调用,在sysproc.c和sysfile.c中定义。分别代表对CPU资源的抽象和磁盘资源的抽象。
- 和进程有关的函数在kernel/proc.h和kernel/proc.c中
系统调用过程:
用户空间调用声明在user/user.h中的函数,该函数将调用的参数放到a0-a6寄存器,调用号放到a7寄存器。随后调用ecall,进入内核空间,内核空间syscall.c中通过a7寄存器的值调用相关的系统调用。
一、System call tracing
要求实现一个trace系统调用,该系统调用接受一个mask数。mask数代表了进程中要追踪的系统调用(1 << SYS_fork 代表要追踪SYS_fork),在命令执行的过程中要打印出被追踪的系统调用的进程号、调用名称和调用的返回值。
示例:
$ 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
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
$
实现过程:
1. 修改proc.h中proc结构体,在sysproc.c中添加系统调用
// Per-process state 该结构体声明在proc.h里,代表进程模型
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
int traceMask; // TraceMask 添加的字段,代表需要追踪的系统调用
// 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.
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)
};
// 该函数添加到sysproc.c中
uint64 sys_trace(void) {
int traceMask = 0;
if(argint(0, &traceMask) < 0) // 得到第一个参数mask
return -1;
myproc()->traceMask = traceMask; // 保存mask到进程模型中
return 0;
}
2. 修改syscall.c/h和fork
首先添加系统调用系统调用表中
(syscall.h)
...
#define SYS_unlink 18
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_trace 22 // 添加的系统调用
(syscall.c)
// 在syscall.c中添加以下代码
extern uint64 sys_sysinfo(void); // 声明系统调用
static uint64 (*syscalls[])(void) = { // 放入调用数组
...
[SYS_sysinfo] sys_sysinfo,
};
// 修改函数
void
syscall(void)
{
char* names[] = {
... // 系统调用名称数组
};
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->traceMask & (1 << num)) > 0) { // 如果系统调用号在mask中
printf("%d %s %d\n", p->pid, names[num - 1], p->trapframe->a0); // 打印相关信息,注意系统调用号是从1开始的
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
(proc.c)
// 修改proc.c中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->traceMask=p->traceMask; // 添加的语句,让新进程模型得到父进程的mask
...
}
至此内核部分修改完毕,开始修改用户空间代码
3. 用户空间
首先添加$U/_trace
到Makefile
里的UPROGS
随后修改user/user.h和user/usys.pl
(user.h)
...
int trace(int);
...
(usys.pl)
...
entry("trace");
...
至此,实验完毕,编译运行测试。
make qemu
$ trace 2 usertests forkforkfork
usertests starting
5 sys_fork 6
test forkforkfork: 5 sys_fork 7
7 sys_fork 8
8 sys_fork 9
8 sys_fork 10
9 sys_fork 11
10 sys_fork 12
...
10 sys_fork 66
11 sys_fork 67
8 sys_fork -1
9 sys_fork -1
10 sys_fork -1
OK
5 sys_fork 68
ALL TESTS PASSED
第一部分完成
二、Sysinfo
添加Sysinfo系统调用,该调用接受一个结构体指针,结构体中包含当前系统剩余内存容量和状态不是UNUSED的进程个数。该部分添加系统调用的部分与上一部分trace完全一致,重点在于如何计算系统剩余内存容量和状态不是UNUSED的进程个数。我们需要在kalloc.c(管理内存)和proc.c(管理进程)的文件中添加相应的函数。
首先,编写获取剩余内存的函数。
(kalloc.c)
// 添加以下函数,以获取剩余内存
int freemem(void) {
int free = 0; // 待返回的值
struct run *r; // 游标(个人猜测)
acquire(&kmem.lock); // kmem是内核的内存模型,lock是互斥锁
r = kmem.freelist; // freelist是剩余未分配页的链表,让游标等于首个页
for (;r;) { // 让游标向后移动,直到为空(c语言中0)
r = r->next;
free += 4096; // free 加上一个页的大小
}
release(&kmem.lock); // 释放锁
return free; // 返回
}
其次,编写获得进程数的函数
(proc.h)
// 添加以下函数,以获取相应进程数
int procnum(void) {
int ret = 0;
for (int i = 0; i < NPROC; i++) {
if (proc[i].state != 0) ret++; // 如果进程状态不是0(UNUSED),累加
}
return ret;
}
然后,需要在defs.h中添加函数声明
最后,编写sysinfo系统调用,可以写到sysfile.c或者sysproc.c或者syscall.c里。
uint64 sys_sysinfo(void) {
struct proc *p = myproc(); // 获取当前进程模型
uint64 sysinfoAddr; // 传入的结构体指针
struct sysinfo si; // 需要返回的结构体
si.freemem = freemem(); // 获取剩余的内存容量
si.nproc = procnum(); // 获取相应的进程数
if(argaddr(0, &sysinfoAddr) < 0) // 读取传入的结构体指针
return -1;
if(copyout(p->pagetable, sysinfoAddr, (char *)&si, sizeof(si)) < 0) // 将结构体通过指针拷贝到用户空间
return -1;
return 0; // 如无出错,返回0
}
之后按照与trace相同的方法,在系统调用表中添加sysinfo系统调用,在用户空间中添加相应的声明。并把sysinfotest添加到Makefile里。
运行测试。
make qemu
$ sysinfotest
sysinfotest: start
sysinfotest: OK
$