Lab 2: system calls
- Lab Guide: Lab: system calls
- Lab Code: https://github.com/peakcrosser7/xv6-labs-2020/tree/syscall
System call tracing (moderate)
要点
int trace(int mask)系统调用- 如果在掩码
mask中设置了系统调用的编号, 则必须修改xv6内核, 以便在每个系统调用即将返回时打印出一行, 包含进程id、系统调用的名称和返回值; 不需要系统调用参数.
步骤
- 定义 trace 系统调用的原型, entry 和系统调用号
- 在
user/user.h中添加 trace 系统调用原型, 函数原型参考user/trace.c中调用的形式.

- 在
user/usys.pl脚本中添加 trace 对应的 entry

- 在
kernel/syscall.h中添加 trace 的系统调用号

- 在
- 编写 trace 的系统调用函数
在kernel/sysproc.c中编写 trace 系统调用函数sys_trace().
这里按照实验指导书中提示, 是将 trace 的参数mask作为一个变量存到结构体struct proc中.
uint64 sys_trace(void) {
int mask;
// 获取整数类型的系统调用参数
if (argint(0, &mask) < 0) {
return -1;
}
// 存入proc 结构体的 mask 变量中
myproc()->mask = mask;
return 0;
}
结构体 struct proc 的定义在 kernel/proc.h 中, 可以看到该结构体记录着进程的转态. 具体内容如下. 其中的成员变量主要分为上面的公有变量和下面的私有变量, 其中公有变量访问是需要加锁的. 此处需要为 trace 系统调用添加一个变量 mask 来记录其参数. 考虑到 trace 之会在其本进程发挥作用, 因此 mask 应该作为进程的私有变量.
// 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; // trace mask - lab2-1
};
- 修改
fork()函数.
由于进程 fork 时需要复制进程的状态, 即结构体proc中相关的成员变量, 因此需要修改kernel/proc.c中的fork()函数.
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
// ...
np->sz = p->sz;
np->mask = p->mask; //cp trace mask from parent to child
np->parent = p;
// ...
}
- 修改系统调用函数
syscall()
最后是在每次系统调用执行时, 即调用syscall()时, 根据掩码输出调用信息. 这里掩码mask作为一个 64 位的变量, 从低位开始每一比特位对应一个系统调用, 因此通过移位和按位与操作即可判断当前系统调用是否被 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]();
// trace output - lab2-1
if ((1 << num) & p->mask) { // 判断掩码是否匹配
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;
}
}
对于需要输出的进程 PID 以及系统调用的返回值, 都在 proc 结构体的相应成员中有记录, 而系统调用名则需要自行构建一个系统调用名称的数组, 顺序与在 kernel/syscall.h 中定义的系统调用号是对应的, 而由于调用号从 1 开始, 因此数组第一个为空字符串来占位.
static char *syscalls_name[] = {
"",
"fork",
"exit",
"wait",
"pipe",
"read",
"kill",
"exec",
"fstat",
"chdir",
"dup",
"getpid",
"sbrk",
"sleep",
"uptime",
"open",
"write",
"mknod",
"unlink",
"link",
"mkdir",
"close",
"trace"
};
- 在
kernel/syscall.c中添加 trace 调用
添加sys_trace()的外部声明

添加syscalls函数指针的对应关系

- 在
Makefile中添加$U/_trace到UPROGS中
测试
- xv6 中执行相关测试:

./grade-lab-syscall trace单项测试:

Sysinfo (moderate)
要点
实现 int sysinfo(struct sysinfo*) 系统调用
步骤
- 定义 sysinfo 系统调用的原型, entry 和系统调用号, 同上一实验
user/user.h

user/usys.pl

kernel/syscall.h

- 计算
freemem和nproc
在编写系统调用sys_info()时需要将内核态的struct sysinfo结构体拷贝到用户态, 而对于该结构体, 在kernel/sysinfo.h有定义, 其中有freemem和nproc两个成员变量, 分别对应着未使用的内存字节数和非 UNUSED 状态的进程数. 因此首先就是要计算这两个成员变量的值.- 计算
freemem
计算该值参考kernel/kalloc.c文件中的kalloc()和kfree()等几个函数. 可以看到内核维护未使用的内存是通过kmem.freelist的一个链表, 链表的每个结点对应一个页表大小(PGSIZE). 分配内存时从链表头部取走一个页表大小, 释放内存时会使用头插法插入到该链表. 因此计算未使用内存的字节数freemem只需要遍历该链表得到链表结点数, 在与页表大小相乘即可得到未使用内存的字节数.
// get the number of bytes of free memory - lab2-2 uint64 getfreemem(void) { uint64 n; struct run *r; // 遍历kmem.freelist链表 for (n = 0, r = kmem.freelist; r; r = r->next) { ++n; } return n * PGSIZE; }- 计算
nproc
计算该值参考kernel/proc.c文件中的allocproc()和freeproc()等函数. 可以看到对应进程, 内核是使用数组proc[NPROC]进行维护. 分配时直接遍历数组找到一个未使用(UNUSED)状态的进程即可(allocproc()函数在分配时锁p->lock的释放存在疑问,goto跳转后好像并没有释放锁?), 释放时则直接将进程信息清空. 因此计算非 UNUSED 的进程数nproc则同样遍历一遍proc数组计数即可.
// get the number of processes whose state is not UNUSED - lab2-2 uint64 getnproc(void) { uint64 n; struct proc *p; // 遍历proc数组, 找非UNUSED状态进程 for(n=0, p = proc; p < &proc[NPROC]; ++p) { if(p->state != UNUSED) { ++n; } } return n; }- 注: 上述两者计算时对
kmem.freelist和proc[NPROC]访问时没有加锁, 因为此处考虑的是只对值进行了读取而未更改.
编写完上述两个函数后, 需要在kernel/defs.h中添加函数原型.


- 计算
- 编写
sys_sysinfo系统调用
在kernel/sysproc.c中添加系统调用函数sys_sysinfo(). 此处需要注意的是将struct sysinfo由内核态拷贝至用户态, 需要调用copyout()函数, 该函数在kernel/vm.c中定义.
uint64 sys_sysinfo(void) {
uint64 info_addr;
struct sysinfo info;
if (argaddr(0, &info_addr) < 0) {
return -1;
}
// 计算freemem和nproc
info.freemem = getfreemem();
info.nproc = getnproc();
// 将结构体由内核态拷贝至用户态
if (copyout(myproc()->pagetable, info_addr,
(char *) &info, sizeof(info)) < 0) {
return -1;
}
return 0;
}
- 在
kernel/syscall.c中添加 sysinfo 调用
添加sys_sysinfo()的外部声明

添加syscalls函数指针的对应关系

添加系统调用名称数组syscalls_name中对应的字符串

- 在
Makefile中添加$U/_sysinfotest到UPROGS中
测试
- xv6 中执行
sysinfotest测试:

./grade-lab-syscall sysinfotest单项测试:

本文档详细介绍了在XV6操作系统中实现系统调用`trace`和`sysinfo`的过程。`trace`系统调用允许用户设定掩码,根据掩码追踪特定系统调用的执行情况,包括返回值和进程ID。`sysinfo`系统调用则用于获取系统的内存使用情况(freemem)和活跃进程数量(nproc)。文档涵盖了从系统调用原型定义、内核函数实现到测试的完整步骤,并提供了关键代码片段和计算内存及进程数的辅助函数。
4536

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



