相关知识
进程最重要的内核状态:1. 页表 p->pagetable 2. 内核堆栈p->kstack 3. 运行状态p->state,显示进程是否已经被分配、准备运行/正在运行/等待IO或退出
每个进程中都有线程(thread),是执行进程命令的最小单元,可以被暂停和继续
每个进程有两个堆栈:用户堆栈(user stack)和内核堆栈(kernel stack)。当进程在user space中进行时只使用用户堆栈,当进程进入了内核(比如进行了system call)使用内核堆栈
在_entry中设置了一个初始stack,stack0来让xv6执行kernel/start.c。在start函数先在machine模式下做一些配置,然后通过RISC-V提供的mret指令切换到supervisor mode,使program counter切换到kernel/main.c
main先对一些设备和子系统进行初始化,然后调用kernel/proc.c中定义的userinit来创建第一个用户进程。这个进程执行了一个initcode.S的汇编程序,这个汇编程序调用了exec这个system call来执行/init,重新进入kernel。exec将当前进程的内存和寄存器替换为一个新的程序(/init),当kernel执行完毕exec指定的程序后,回到/init进程。/init(user/init.c)创建了一个新的console device以文件描述符0,1,2打开,然后在console device中开启了一个shell进程,至此整个系统启动了
trace函数
添加系统调用函数
要求:trace [tracing_mask] [command] 要求当调用了给定的tracing mask所对应的system call时,打印输出调用该system call的进程PID、system call的名称、系统调用号。已经给出了user space下的user/trace.c,需要注册并实现trace这一system call
user/user.h: 用户态程序调用跳板函数 trace()
user/usys.S: 由user/usys.pl编译而来。跳板函数 trace() 使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c 到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c syscall() 根据跳板传进来的系统调用编号,查询 syscalls[] 表,找到对应的内核函数并调用。
kernel/sysproc.c 到达 sys_trace() 函数,执行具体内核操作
第一步:用户空间配置
user.h中声明函数。
用户态文件夹下实现用户态的trace函数入口
user.pl中ecall指令,进入syscall
第二步:内核空间配置
/*,所有的系统调用到达内核态后,都会进入到 syscall() 这个函数进行处理,
所以要跟踪所有的内核函数,只需要在 syscall() 函数里埋点就行了。*/
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](); //a0中是系统调用地址
if((p->syscall_trace >> num) & 1) {
printf("%d: syscall %s -> %d\n",p->pid, syscall_names[num], p->trapframe->a0); // syscall_names[num]: 从 syscall 编号到 syscall 名的映射表
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
该函数首先需要知道是发生了哪个syscall,所以会取a7查看对应的系统调用号。最后,为了能够按照题目要求实现打印输出,需要判断相关逻辑,系统调用名字存放在syscall_names变量中
由于内核与用户进程的页表不同,寄存器也不互通,所以参数无法直接通过 C 语言参数的形式传过来,而是需要使用 argaddr、argint、argstr 等系列函数,从进程的 trapframe 中读取用户进程寄存器中的参数。
同时由于页表不同,指针也不能直接互通访问(也就是内核不能直接对用户态传进来的指针进行解引用),而是需要使用 copyin、copyout (类似linux中copy_from_usr)方法结合进程的页表,才能顺利找到用户态指针(逻辑地址)对应的物理内存地址。(在本 lab 第二个实验会用到)
函数实现逻辑为:首先需要获取系统调用的参数,也就是从用户态传入的参数mask,然后设置当前进程的syscall_trace为该参数(需要在进程结构体中添加该项),同时为了能够保证真正地trace指定的系统调用,那么就不仅仅在该进程,同时也需要相关所有的子进程也传递该参数,就需要在fork.c函数中增加一行赋值语句
Sysinfo
添加一个系统调用,返回空闲的内存、以及已创建的进程数量。