Lab2 System Calls

开学!

实验开始前

1
2
3
git fetch
git checkout syscall
make clean

使用 gdb

  1. 查看 backtrace 的输出,哪个函数调用了 syscall
    • usertrap()
    • 在 syscall 设置断点后,输入 backtrace 查看栈回溯
  2. p->trapframe->a7 的值是多少,值代表什么?
    • 7,代表系统调用号 SYS_exec
    • p/x *p->trapframe 输出 p 的 trapframe 内容
  3. CPU 的上一个模式是什么?
    • 用户模式
    • p/x $sstatus 输出 sstatus 寄存器的值,0x22
    • kernel/riscv.h 中有定义:#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User,也可以看给的文档
    • 这里的 SPP 位为 0,因此上一个模式是用户模式
  4. num = * (int *) 0;,kernel 在哪条汇编指令 panic,哪个寄存器对应变量 num
    • lw a3, 0(zero),a3
    • 查看 panic 时 spec 寄存器的值指向哪个汇编
  5. 为什么内核崩溃了?在内核地址空间 0 地址有映射吗?上面的 scause 值是否证实这一点?
    • 因为尝试读取 0 地址,它没有有效映射
    • 寄存器 scause 表示发生 trap 的原因,这里的 scause 是 0xd,查看文档可以知道,0xd 表示 Load page fault,合理
  6. 当内核 panic 时进程的名字是什么?进程 pid 是多少?
    • “initcode”,1

一开始做因为寄存器的值不了解,还不太能看懂文档,没能理解,跳过了,有些答案是后面更新的

疑问

  • 访问到 0 地址时为什么会跳转到 kernelvec 中
  • scause、sepc、stval 的含义

更新:访问 0 发生了 trap,需要跳转到处理内核 trap 的位置,即 kerneltrap,而在处理之前,先保存内核的状态,kernelvec 就是做这样的事情;scause 描述 trap 原因,sepc 保存发生 trap 时 pc 的值,stval 保存发生 trap 的值

System call tracing

user/trace.c 已经写好了程序,只需要实现系统调用即可

  1. 先在 user/user.h 加上原型,在 user/usys.pl 加上 stub(存根),在 kernel/syscall.h 加上系统调用号
  2. kernel.c 的 proc 结构体加上一个新变量 trace_mask
  3. kernel/sysproc.c 加上 sys_trace,设置当前进程的 track_mask
kernel/sysproc.c
1
2
3
4
5
6
7
8
9
10
11
uint64
sys_trace(int)
{
int mask;

argint(0, &mask);
if(mask < 0)
mask = 0;
myproc()->trace_mask = mask;
return 0;
}
  1. 修改 kernel/proc.cfork 函数,将父进程的 tracemask 传给子进程
kernel/proc.c
1
np->trace_mask = p->trace_mask;
  1. 修改 kernel/syscall.csyscall 函数,如果是 trace_mask 对应的系统调用号,就打印出来(里面还要添加一个字符串数组 syscallNames)
kernel/syscall.c
1
2
3
4
5
ret = syscalls[num]();
p->trapframe->a0 = ret;

if(p->tracemask && 1<<num)
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], ret);

一个很简单的系统调用,仅仅是获取系统调用参数,然后将参数传给 p->trace_mask,在 syscall 函数中检查输出调用的系统调用,就可以实现,但是能学到很多细节

Sysinfo

这里我们要使用 copyout,因为系统调用函数位处于内核模式,需要进程的页表和虚拟地址来查找用户进程中变量的物理位置(比如 sysinfo 结构体),然后将内核的数据复制给用户进程

kernel/sysproc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uint64
sys_sysinfo(void)
{
uint64 si;
struct sysinfo info;

argaddr(0, &si);
if(!si) {
return -1;
}
info.freemem = get_freemem();
info.nproc = get_nproc();
if(copyout(myproc()->pagetable, si, (char*)&info, sizeof(info)) < 0) {
return -1;
}
return 0;
}

写 get_freemem 时,观察 kalloc 函数,直接从 kmem.freelist 取一页内存返回,可以推测 kmem.freelist 包含所有可用的内存

kernel/kalloc.c
1
2
3
4
5
6
7
8
9
10
11
uint64
get_freemem(void)
{
struct run *r;
uint64 n = 0;

for(r = kmem.freelist; r; r = r->next) {
n += 4096;
}
return n;
}

写 ger_nproc 时,观察 procinit 函数,在 proc[NPROC] 数据中遍历初始化,且其中含 state 变量

kernel/proc.c
1
2
3
4
5
6
7
8
9
10
11
12
uint64
get_nproc(void)
{
struct proc *p;
uint64 nproc = 0;
for(p = proc; p < &proc[NPROC]; p++) {
if(p->state != UNUSED) {
nproc++;
}
}
return nproc;
}

记得在 sysproc.c 引入 sysinfo.h,在 defs.h 加上 get_freemem 和 get_nproc

Optional challenge exercises

打印出被追踪的系统调用的参数

每个系统调用参数个数记录在数组里,然后打印出来就好了

kernel/syscall.c
1
2
3
4
5
if(p->trace_mask & 1<<num) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], ret);
for(int i = 0; i < syscall_args[num]; i++)
printf("arg%d: %p\n", i+1, argraw(i));
}

打印出来是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ trace 32 grep hello README
3: syscall read -> 1023
arg1: 0x00000000000003ff
arg2: 0x0000000000001010
arg3: 0x00000000000003ff
3: syscall read -> 961
arg1: 0x00000000000003c1
arg2: 0x000000000000104e
arg3: 0x00000000000003c1
3: syscall read -> 321
arg1: 0x0000000000000141
arg3: 0x0000000000001037
arg3: 0x00000000000003d8
3: syscall read -> 0
arg1: 0x0000000000000000
arg2: 0x0000000000001010
arg3: 0x00000000000003ff

计算负载平均值并通过 sysinfo 导出

可以借用 Linux 的算法计算(懒)

作者

Humoooor

发布于

2022-10-20

更新于

2024-01-04

许可协议

评论