- 需要切换到
syscall
分支
一、use gdb
学习使用 gdb
调试
- make qemu-gdb
- 打开一个新的终端: gdb-multiarch -x .gdbinit
qemu 界面:
gdb 界面:
- gdb 界面: b syscall
- gdb 界面:c
- qemu 界面输入:ls(随便一个会发起系统调用的命令)
- gdb界面:可以发现已经触发了断点,可以单步执行了
- gdb界面:layout src,显示源码
- gdb界面:bt,打印调用栈
- 第一个问题询问什么函数触发了 syscall,从调用栈可知:usertrap,表示在用户态调用了系统调用API
- 单步执行
- 打印当前进程 a7 寄存器的值:
- 由于a7寄存器用于保存系统调用号,可知用户态调用的是
SYS_read
- gdb界面:p/x $sstatus 寄存器(值为0x22),表示之前特权级别为用户态(问题二)
二、syscall:trace
注意:
- Makefile添加 $U/_trace
- user/user.h 添加 trace 函数原型、
- user/usys.pl 中添加系统调用入口
- kernel/syscall.h 添加系统调用号
- makefile 将执行 user/usys.pl,生成user/usys.S,里面存放的是系统调用的实际入口代码
- kernel/sysproc.c 添加 sys_trace() 函数用于实现系统调用 trace的逻辑(函数逻辑:proc 中添加一个新的函数用于)
- 修改 kernel/proc.c:fork(),拷贝父进程的 trace mask 到子进程
- 修改 kernel/syscall.c:syscall() 用于打印
- 准备一个 syscall 的名字的数组
- 该系统调用逻辑其实比较简单,即 trace 命令本身是一个进程,接收的参数为另外一个命令,然后调用trace系统调用设置当前进程的 trace mask,然后 fork 子进程去执行参数中的命令,然后如果当前进程调用的所有系统调用在返回时,和掩码判断,如果为true,则打印
- proc中需要添加一个 trace mask
- 所有的系统调用返回需要增加一段和 trace mask 进行判断并打印的逻辑
- 添加一个 trace 系统调用,用于设置当前进程的 trace mask,如果不设置,默认为0,即不 trace
- fork的时候,需要把父进程的 trace mask 拷贝给子进程
实验代码:
- 添加 trace nask:
- kernel/proc.h
struct proc {
// ....
char name[16]; // Process name (debugging)
uint64 trace_mask; // 新增的记录当前进程的 trace 掩码
};
- kernel/proc.c
int
fork(void)
{
// ...其他代码
np->sz = p->sz;
np->trace_mask = p->trace_mask; // 子进程创建的时候拷贝父进程的trace mask
}
- 添加 trace 系统调用:
- makefile
UPROGS=\
//其他代码
$U/_trace\
- user/user.h
struct stat;
// system calls
//其他代码
int trace(int);
//...
- user/usys.pl
#!/usr/bin/perl -w
// 其他代码
entry("trace");
- kernel/syscall.h
// ...
#define SYS_trace 22
- kernel/syscall.c
// ...
// sys call name
static
char* syscalls_name[] = {
[SYS_fork] = "syscall fork",
[SYS_exit] = "syscall exit",
[SYS_wait] = "syscall wait",
[SYS_pipe] = "syscall pipe",
[SYS_read] = "syscall read",
[SYS_kill] = "syscall kill",
[SYS_exec] = "syscall exec",
[SYS_fstat] = "syscall fstat",
[SYS_chdir] = "syscall chdir",
[SYS_dup] = "syscall dup",
[SYS_getpid] = "syscall getpid",
[SYS_sbrk] = "syscall sbrk",
[SYS_sleep] = "syscall sleep",
[SYS_uptime] = "syscall uptime",
[SYS_open] = "syscall open",
[SYS_write] = "syscall write",
[SYS_mknod] = "syscall mknod",
[SYS_unlink] = "syscall unlink",
[SYS_link] = "syscall link",
[SYS_mkdir] = "syscall mkdir",
[SYS_close] = "syscall close",
[SYS_trace] = "syscall trace",
};
// ...
// 定义系统调用处理函数
extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
// ....
// 定义系统调用实际处理函数
[SYS_trace] sys_trace,
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
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->trace_mask & num){
printf("xxx:%s\n", syscalls_name[num]);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
- kernel/sysproc.c
//实际系统调用函数实现,逻辑很简单,设置当前进程的trace mask
uint64
sys_trace(void){
//参考上面的系统调用函数实现,获取参数
int mask;
argint(0, &mask);
myproc()->trace_mask = mask;
return 0;
}
实验结果:
- make grade
三、sysinfo
tips:
- 添加 $U/_sysinfotest 到makefile
- 添加 user/sysinfotest.c 文件
- 实现 sys_info 系统调用,步骤同 sys_trace
- 注意在 user/user.h 声明
int sysinfo(struct sysinfo *)
函数之前,先预先声明结构体:struct sysinfo;
(struct sysinfo
在kernel/sysinfo.h
中定义) - 参考
sys_fstat()
(kernel/sysfile.c
) 和filestat()
(kernel/file.c
) 学习从内核态将struct sysinfo
拷贝到用户态 (用户态传入一个地址,内核态通过copyout
函数拷贝到用户态地址) - 统计内存使用量:
kernel/kalloc.c
中添加一个统计free内存使用量的函数 - 统计进程数量:
kernel/proc.c
中添加一个统计进程数目的函数 - 因此,我们实现的内核态的系统调用函数
sys_sysinfo
接收一个地址参数,然后调用统计内存的函数和统计进程数量的函数,最后使用 copy_out 拷贝到用户态
实验代码
- makefile
$U/_sysinfotest\
- user/user.h
struct stat;
struct sysinfo; //添加sysinfo的预声明
// system calls
// ...
int trace(int);
int uptime(void);
int sysinfo(struct sysinfo *); //声明用户态系统调用API
- user/usys.pl
entry("sysinfo");
- kernel/syscall.h
#define SYS_sysinfo 23
- kernel/syscall.c
static
char* syscalls_name[] = {
// ....
[SYS_trace] = "syscall trace",
[SYS_sysinfo] = "syscall sysinfo",
};
// ....
extern uint64 sys_sysinfo(void);
static uint64 (*syscalls[])(void) = {
// ...
[SYS_trace] sys_trace,
[SYS_sysinfo] sys_sysinfo,
};
- kernel/sysproc.c
#include "sysinfo.h"
// ....其他代码
uint64
sys_sysinfo(void){
uint64 addr; // user pointer to struct sysinfo
struct sysinfo sysinfo;
argaddr(0, &addr);
sysinfo.freemem = kfree_mem_get();
sysinfo.nproc = get_proc_num();
if(copyout(myproc()->pagetable, addr, (char *)&sysinfo, sizeof(sysinfo)) < 0){
return -1;
}
return 0;
}
- kernel/def.h
// proc.c
//....proc.c 中其他函数
uint64 get_proc_num(void); //获取进程数目
// kalloc.c
void* kalloc(void);
void kfree(void *);
void kinit(void);
uint64 kfree_mem_get(void); //获取空闲内存
- kernel/proc.c
uint64 get_proc_num(void){
uint64 num = 0;
struct proc* p;
for(p = proc; p < &proc[NPROC]; p++) {
if(p->state == UNUSED) {
continue;
} else {
num++;
}
}
return num;
}
- kernel/kalloc.c
uint64 kfree_mem_get(void){
uint64 free = 0;
struct run *r = kmem.freelist;
acquire(&kmem.lock);
while(r){
free += PGSIZE;
r = r->next;
}
release(&kmem.lock);
return free;
}
实验结果