xv6-Lab2
Last Update:
Word Count:
Read Time:
Lab 2
using gdb
Q1:Looking at the backtrace output,which function called syscall?
执行backtrace后结果如下:

由图可知是函数usertrap()调用了syscall()函数.
Q2:What is the value of
p->trapframe->a7and what does that value represent?(Hint:lookuser/initcode.S,the first user program xv6 starts.)
首先输入两次n之后执行结果如下:

语句struct proc *p = myproc()执行完毕,接着执行p/x *p看p的内容如下:

此时查看p->trapframe->a7的值:

得到a7的值为7。
根据user/initcode.S以及参考书第二章内容可知寄存器a7保存了系统将要执行的系统调用号,这里的系统调用号为7,由kernel/syscall.h的内容(下图)可知系统调用为SYS_exec。

Q3:What was the previous mode that the CPU was in?
在gdb中输入p /x $sstatus得到如下结果:

其值转换为二进制为:0b100010,在参考书RISC-V privileged instructions中找到sstatus的值定义如下:

同时对于其SPP位有描述如下:
The SPP bit indicates the privilege level at which a hart was executing before entering supervisor mode. When a trap is taken, SPP is set to 0 if the trap originated from user mode, or 1 otherwise. When an SRET instruction (see Section 3.3.2) is executed to return from the trap handler, the privilege level is set to user mode if the SPP bit is 0, or supervisor mode if the SPP bit is 1; SPP is then set to 0.
SPP 位指示进入管理员模式之前 hart 执行的特权级别。 当采取陷阱时,如果陷阱源自用户模式,则 SPP 设置为 0,否则设置为 1。 当执行 SRET 指令(见第 3.3.2 节)从陷阱处理程序返回时,如果 SPP 位为 0,则特权级别设置为用户模式,如果 SPP 位为 1,则设置为超级用户模式; 然后将 SPP 设置为 0。
因此,因为此时SPP位为0,所以在syscall之前系统处于用户模式(User mode)。
Q4:Write down the assembly instruction the kernel is panicing at.Which register corresponds to the varialable num?
首先按照指导将kernel/syscall.c中syscall函数中的num = p->trapframe->a7改为num = * (int *) 0,接着执行make qemu得到如下输出:

在kernel/kernel.asm中查找上图中的sepc值,得到结果如下图:

对应的汇编指令为lw a3,0(zero),由参考书RISC-V Assembly Language,

这条汇编代码代表:将内存中地址从0开始的一个字word(2bytes)大小的数据加载到寄存器a3中。
Q5:Why does the kernel crash?Hint:look at figure 3-3 in the text;is address 0 mapped in the kernel address space?Is that confirmed by the value in scause above?(See
description of scause in RISC-V privileged instructions)
首先按照实验要求在上文panic的代码处打上断点并继续执行,结果如下:

此时再次输入n并执行会引起内核panic,如下:


使用Ctrl+C来退出当前线程并打印scause的值如下:

接着开始分析:首先根据参考书book-riscv-rev3中的Figure 3.3(如下图),

内核地址空间基地址为0x80000000,因此代码中的数据地址0不映射到内核地址空间中,因此内核会崩溃。
而scause的值为13,在参考书RISC-V Assembly Language的Table 8.6(如下图)中可以查到代码13对应Load page fault,验证了结论。

Q6:What is the name of the binary that was running when the kernel paniced? What is its process id (pid)?
首先重启qemu和gdb执行如下命令:

为了获得进程pid,执行如下命令:

因此
name = initcode\000\000\000\000\000\000\000;
pid = 1.
System call tracing
主要思路
- 根据题中所给的Hints来修改程序
Makefile:Add$U/_tracetoUPROGS.user/user.h: 增加trace函数的声明,如下:1
int trace(int);user/usys.pl:增加如下代码:1
entry("trace");kernel/proc.h:在struct proc中添加成员变量mask,用于存储trace函数的参数1 << SYS_call:1
2
3
4struct proc{
uint64 mask;
...
};kernel/sysproc.c:增加函数sys_trace如下:用于在调用1
2
3
4
5
6
7
8
9
10
11uint64
sys_trace(void)
{
int mask;
argint(0, &mask); // 从系统调用中获得参数mask
struct proc* p = myproc();
p->mask = mask; // 将当前进程的mask值设为获取的参数
return 0;
}trace时来接收参数mask.kernel/proc.c:在fork函数中增加如下代码:上述代码实现将掩码1
2
3
4
5
6
7
8
9
10
11fork(void){
...
np->sz = p->sz;
// 复制trace mask值到子进程
np->mask = p->mask;
// copy saved user registers.
*(np->trapframe) = *(p->trapframe);
...
}mask从父进程传到子进程.kernel/syscall.h:增加系统调用号SYS_trace:1
#define SYS_trace 22kernel/syscall.c:- 增加函数声明:
1
extern uint64 sys_trace(void); - 增加
syscalls的取值:1
2
3
4
5static uint64 (*syscalls[])(void) = {
...
[SYS_close] sys_close,
[SYS_trace] sys_trace,
}; - 增加系统调用名的字符串数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 用来在打印trace函数运行结果时输出系统调用名称
static char* syscall_names[] = {
"",
"syscall fork",
"syscall exit",
"syscall wait",
"syscall pipe",
"syscall read",
"syscall kill",
"syscall exec",
"syscall fstat",
"syscall chdir",
"syscall dup",
"syscall getpid",
"syscall sbrk",
"syscall sleep",
"syscall uptime",
"syscall open",
"syscall write",
"syscall mknod",
"syscall unlink",
"syscall link",
"syscall mkdir",
"syscall close",
"syscall trace",
}; - 修改
syscall函数:上述改动中内层条件判断的语句就是将1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void
syscall(void)
{
...
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
if((p->mask >> num) & 0b1){ // 如果mask的值译码后等于该系统调用的系统调用号,则打印相关信息
printf("%d: %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}mask进行解码,然后与1作比较,若相等则输出该系统调用.
- 增加函数声明:
经过以上对内核的修改,成功实现了系统调用trace.
sysinfo
主要思路
- 和上个实验一样按照
Hints来逐步完成,本实验的主要目的是完成系统调用sysinfo来统计系统非空闲进程数量和空闲空间字节数.- 首先是将系统调用添加到内核中,参考
System call tracing,进行如下操作:- Add
$U/_sysinfotesttoUPROGSinMakefile - 在
user/user.h中进行系统调用函数的声明:1
2
3
4struct sysinfo;
...
int sysinfo(struct sysinfo *);
... - 在
user/usys.pl中添加如下代码:1
2
3...
entry("trace");
entry("sysinfo"); - 在
kernel/syscall.h中添加系统调用号:1
#define SYS_sysinfo 23 - 在
kernel/syscall.c中添加如下改动:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16...
extern uint64 sys_info(void);
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
...
[SYS_sysinfo] sys_info,
};
// 用来在打印trace函数运行结果时输出系统调用名称
static char* syscall_names[] = {
...
"syscall sysinfo"
};
...
- Add
- 接着就是实现该系统调用,该系统调用的功能是统计空闲内存和非空闲进程
- 统计空闲内存:在
kernel/kalloc.c中添加如下函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 用于统计空闲空间的大小,free_num计算得到的是页数,每页的大小为 4096 bytes
uint64
free_mem_num(void)
{
struct run *page;
uint64 free_num = 0;
acquire(&kmem.lock); // 访问上锁
page = kmem.freelist; // 空闲页面的链表头
while (page) {
free_num++;
page = page->next;
}
release(&kmem.lock); // 访问结束解锁
return free_num * 4096;
} - 统计非空闲进程:在
kernel/proc.c中添加如下函数1
2
3
4
5
6
7
8
9
10
11// 用于统计当前非空闲进程数量
uint64
proc_used_num(void)
{
uint64 nproc = 0; // 计数
for (struct proc *p = proc; p < &proc[NPROC]; p++) {
if (p->state != UNUSED)
nproc++;
}
return nproc;
} - 在实现上述函数后要将二者的声明加入到
kernel/defs.h中. - 在
kernel/sysproc.c中实现系统调用uint64 sys_info(void):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18uint64
sys_info(void)
{
// 用户空间指向sysinfo的指针
uint64 u_addr;
argaddr(0, &u_addr);
struct sysinfo info;
info.freemem = free_mem_num(); // 获取空闲内存
info.nproc = proc_used_num(); // 获取非空闲进程
struct proc *p = myproc();
// 根据Hints参考kernel/file.c/filestate()使用copy函数将info复制到用户空间
if(copyout(p->pagetable, u_addr, (char*)(&info), sizeof(info)) < 0)
return -1;
return 0;
}
- 统计空闲内存:在
- 首先是将系统调用添加到内核中,参考
