文章目录
实验链接
https://pdos.csail.mit.edu/6.S081/2020/labs/pgtbl.html
实验
RISC-V assembly
-
a0-a7,13保存在a2里
-
函数f和g直接被内联优化了,即printf的第2个参数直接是12(f(8)+1的结果).
-
0x640
-
ra保存的是函数调用后返回的地址,由下图可以看出为0x38
-
He110 World(本人的机器是小端的,所以是这个输入,如果是大端的,输出则不一样)
unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i);
-
y的输出结果为0,保存的是a2寄存器中的值。
printf("x=%d y=%d", 3);
Backtrace
该题的主要目的是打印当前进程的调用链,为实现该函数,我们首先需要了解栈的分布情况,如下图所示:
stack
地址从高到低进行增长;sp
: 指向栈的最底端;fp
: 当前栈帧的顶端;return address
是地址是fp-8
prev frame fp
保存地址是fp-16
按照实验提示进行代码更改:
-
kernel/defs.h,添加头文件。
// printf.c void printf(char*, ...); void panic(char*) __attribute__((noreturn)); void printfinit(void); // begin++++ void backtrace(void); // end------
-
kernel/syspro.c,在函数sys_sleep中添加调用
uint64 sys_sleep(void) { int n; uint ticks0; // begin+++++ backtrace(); // end------- if(argint(0, &n) < 0) return -1; acquire(&tickslock); ticks0 = ticks; while(ticks - ticks0 < n){ if(myproc()->killed){ release(&tickslock); return -1; } sleep(&ticks, &tickslock); } release(&tickslock); return 0; }
-
kernel/riscv.h文件中,添加如下函数,该函数的主要作用是从
s0
寄存器读取数值。(gcc编译器将当前正在执行函数的栈帧指针保存在s0寄存器中)static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x) ); return x; }
-
kernel/printf.c文件中,添加如下函数:
void backtrace(void) { // 获取栈帧首地址 uint64 fpaddr = r_fp(); // 获取当前进程在kernel中stack的最大地址 uint64 max = PGROUNDUP(fpaddr); // 因为栈是从高地址至低地址增长的,所以这里用小于号判断 while (fpaddr < max) { // return address是当前fp-8 printf("%p\n", *((uint64*)(fpaddr - 8))); // 调用该函数的栈帧便宜是fp - 16 fpaddr = *((uint64*)(fpaddr - 16)); } }
-
kernel/printf.c文件中,在函数panic中添加对backtrace的调用:
void panic(char *s) { // begin+++++ backtrace(); // end+++++++ pr.locking = 0; printf("panic: "); printf(s); printf("\n"); panicked = 1; // freeze uart output from other CPUs for(;;) ; }
-
运行结果:
Alarm
该题的主要目的是让我们增加一个系统调用,该系统调用会在指定时间间隔后调用用户态的函数,因此需要我们对内核态的相关状态进行保存。按照实验提示一步一步来即可。涉及修改的文件如何所示:
-
Makefile文件,增加如下代码:
+ $U/_alarmtest\
-
文件kernel/proc.h文件
struct proc { .... struct file *ofile[NOFILE]; // Open files struct inode *cwd; // Current directory char name[16]; // Process name // begin+++++++ int alarmticks; // ticks interval uint64 alarmhandler; // function to handler alarm int tickspass; struct trapframe * alarmtrapframe; int accessable; // end+++++++ }
-
文件Kernel/proc.c,在函数allproc中增加如下内容:
static struct proc* allocproc(void) { ...... // Allocate a trapframe page. // begin++++++ if((p->alarmtrapframe = (struct trapframe *)kalloc()) == 0){ release(&p->lock); return 0; } p->tickspass = 0; p->alarmticks = 0; p->alarmhandler = 0; p->accessable = 1; return p; // end++++++ }
-
文件Kernel/proc.c,在函数freeproc中增加如下内容:
// begin++++ if(p->alarmtrapframe) kfree((void*)p->alarmtrapframe); p->alarmtrapframe=0; // end+++++
-
在文件kernel/syscall.h中,增加如下内容
// begin++++ #define SYS_sigalarm 22 #define SYS_sigreturn 23 // end++++
-
在文件kernel/syscall.c中,增加如下内容
.... extern uint64 sys_wait(void); extern uint64 sys_write(void); extern uint64 sys_uptime(void); // begin++++ extern uint64 sys_sigalarm(void); extern uint64 sys_sigreturn(void); // end++++ ... static uint64 (*syscalls[])(void) = { .... //begin++++ [SYS_sigalarm] sys_sigalarm, [SYS_sigreturn] sys_sigreturn, } // end+++++
-
在文件kernel/syproc.c中,增加两个系统调用函数
uint64 sys_sigalarm(void) { int ticks = 0; uint64 alarmhandler = 0; if(argint(0, &ticks) < 0 || argaddr(1, &alarmhandler) < 0) return -1; struct proc *p = myproc(); p->alarmticks = ticks; p->alarmhandler = alarmhandler; p->tickspass = 0; p->accessable = 1; return 0; } uint64 sys_sigreturn(void) { struct proc *p = myproc(); // 恢复系统调用时用户的栈帧 if (0 == p->accessable) { memmove(p->trapframe, p->alarmtrapframe, sizeof(struct trapframe)); p->accessable = 1; } return 0; }
-
在kernel/trap.c文件的usertrap函数中,改为如下内容:
// give up the CPU if this is a timer interrupt. if(which_dev == 2) { p->tickspass++; if (p->alarmticks != 0 && p->tickspass == p->alarmticks) { // return to user space to call the alarm function p->tickspass = 0; if (p->accessable == 1) { memmove(p->alarmtrapframe, p->trapframe, sizeof(struct trapframe)); p->trapframe->epc = p->alarmhandler; p->accessable = 0; } } else { yield(); } }
-
在文件user/user.h中,添加如下内容:
int sigalarm(int ticks, void (*handler)()); int sigreturn(void);
-
在文件user/usys.pl中,添加如下内容:
entry("sigalarm"); entry("sigreturn");
-
查看结果
提交结果
$ git commit -m "lab page tabls"
$ make handin
查看结果
登录网站https://6828.scripts.mit.edu/2020/handin.py/student
,可以看到提交的结果。
课程涉及知识汇总
寄存器
- RISC-V有32个用户寄存器,用户可以使用全部的寄存器。
- 通过系统调用从用户态转到内核态时,相关系统调用的参数,可以通过a0至a7寄存器进行传递。通过调试我们发现:
- a7一般用来保存系统调用函数的编号;
- a0-a6可用来保存用户传递给系统调用的参数;
- a0也用来保存系统调用之后的返回结果;
- STVEC:在内核态的时候,指向的是内核的trap代码位置,在用户态的时候,应该指向用户的trap代码位置;
- SEPC: 内核的程序计算器
- 任何需要进行编译的语言(例如C语言),都不能修改用户寄存器。因此,用户寄存器必须在汇编代码中保存。
- SSTATUS:控制寄存器,本次课程中用到了其中的两个bit
- SPP:代表sret指令的行为,为0表示我们要进入用户态,为1表示我们要进入特权态();
- SPIE:代表是否打开中断,为0表示关闭,为1表示打开
page table
- trapframe page和trampoline page这两个页的
PTE_U
都为0,表示用户不能访问,只有进入supervisor mode才能访问;
指令
- ecall:该命令会将控制权由用户转到内核。执行该指令时,并不会切换page table
GDB
tui enable
可以打开对应的C语言代码;
Trapline
- 由内核映射在各个进程和内核空间的的代码;
- 该代码涉及到的功能有
- 将用户寄存器的值保存至trapframe对应字段;
Trapframe
- 保存用户态和内核态切换时的上下文信息,主要包含相关寄存器的值以及内核
page table
,内核stack
的顶部指针,usertrap
代码位置,程序计数器等相关信息,具体可参考文件的kernel/proc.h
中的trapframe
结构体。
常用命令
ctrl + a c
,进入qemu的console页面;- 在console页面,输入
info mem
,可以打印当前的page table。
- 在console页面,输入
参考链接
Github
https://github.com/aerfalwl/mit-xv6-labs-2020.git