Lab4:Traps
本实验探索如何使用陷阱实现系统调用。您将首先使用栈做一个热身练习,然后实现一个用户级陷阱处理的示例。
**Attention:**开始编码之前,请阅读xv6手册的第4章和相关源文件:
- kernel/trampoline.S:涉及从用户空间到内核空间再到内核空间的转换的程序集
- kernel/trap.c:处理所有中断的代码
要启动实验,请切换到traps
分支:
$ git fetch
$ git checkout traps
$ make clean
RISC-V assembly (easy)
理解一点RISC-V汇编是很重要的,你应该在6.004中接触过。xv6仓库中有一个文件user/call.c。执行make fs.img
编译它,并在user/call.asm中生成可读的汇编版本。
阅读call.asm中函数g
、f
和main
的代码。RISC-V的使用手册在参考页上。以下是您应该回答的一些问题(将答案存储在answers-traps.txt文件中):
- 哪些寄存器保存函数的参数?例如,在
main
对printf
的调用中,哪个寄存器保存13? main
的汇编代码中对函数f
的调用在哪里?对g
的调用在哪里(提示:编译器可能会将函数内联)printf
函数位于哪个地址?- 在
main
中printf
的jalr
之后的寄存器ra
中有什么值? - 运行以下代码。
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
程序的输出是什么?这是将字节映射到字符的ASCII码表。
输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i
设置成什么?是否需要将57616
更改为其他值?
- 在下面的代码中,“
y=
”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?
printf("x=%d y=%d", 3);
Backtrace (moderate)
回溯(Backtrace)通常对于调试很有用:它是一个存放于栈上用于指示错误发生位置的函数调用列表。
在kernel/printf.c中实现名为backtrace()
的函数。在sys_sleep
中插入一个对此函数的调用,然后运行bttest
,它将会调用sys_sleep
。你的输出应该如下所示:
backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898
在bttest
退出qemu后。在你的终端:地址或许会稍有不同,但如果你运行addr2line -e kernel/kernel
(或riscv64-unknown-elf-addr2line -e kernel/kernel
),并将上面的地址剪切粘贴如下:
$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D
你应该看到类似下面的输出:
kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85
编译器向每一个栈帧中放置一个帧指针(frame pointer)保存调用者帧指针的地址。你的backtrace
应当使用这些帧指针来遍历栈,并在每个栈帧中打印保存的返回地址。
提示:
- 在kernel/defs.h中添加
backtrace
的原型,那样你就能在sys_sleep
中引用backtrace
- GCC编译器将当前正在执行的函数的帧指针保存在
s0
寄存器,将下面的函数添加到kernel/riscv.h
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
并在backtrace
中调用此函数来读取当前的帧指针。这个函数使用内联汇编来读取s0
-
这个课堂笔记中有张栈帧布局图。注意返回地址位于栈帧帧指针的固定偏移(-8)位置,并且保存的帧指针位于帧指针的固定偏移(-16)位置
-
XV6在内核中以页面对齐的地址为每个栈分配一个页面。你可以通过
PGROUNDDOWN(fp)
和PGROUNDUP(fp)
(参见kernel/riscv.h)来计算栈页面的顶部和底部地址。这些数字对于backtrace
终止循环是有帮助的。
一旦你的backtrace
能够运行,就在kernel/printf.c的panic
中调用它,那样你就可以在panic
发生时看到内核的backtrace
。
Alarm (hard)
YOUR JOB
在这个练习中你将向XV6添加一个特性,在进程使用CPU的时间内,XV6定期向进程发出警报。这对于那些希望限制CPU时间消耗的受计算限制的进程,或者对于那些计算的同时执行某些周期性操作的进程可能很有用。更普遍的来说,你将实现用户级中断/故障处理程序的一种初级形式。例如,你可以在应用程序中使用类似的一些东西处理页面故障。如果你的解决方案通过了
alarmtest
和usertests
就是正确的。
你应当添加一个新的sigalarm(interval, handler)
系统调用,如果一个程序调用了sigalarm(n, fn)
,那么每当程序消耗了CPU时间达到n个“滴答”,内核应当使应用程序函数fn
被调用。当fn
返回时,应用应当在它离开的地方恢复执行。在XV6中,一个滴答是一段相当任意的时间单元,取决于硬件计时器生成中断的频率。如果一个程序调用了sigalarm(0, 0)
,系统应当停止生成周期性的报警调用。
你将在XV6的存储库中找到名为user/alarmtest.c的文件。将其添加到Makefile。注意:你必须添加了sigalarm
和sigreturn
系统调用后才能正确编译(往下看)。
alarmtest
在test0
中调用了sigalarm(2, periodic)
来要求内核每隔两个滴答强制调用periodic()
,然后旋转一段时间。你可以在user/alarmtest.asm中看到alarmtest
的汇编代码,这或许会便于调试。当alarmtest
产生如下输出并且usertests
也能正常运行时,你的方案就是正确的:
$ alarmtest
test0 start
........alarm!
test0 passed
test1 start
...alarm!
..alarm!
...alarm!
..alarm!
...alarm!
..alarm!
...alarm!
..alarm!
...alarm!
..alarm!
test1 passed
test2 start
................alarm!
test2 passed
$ usertests
...
ALL TESTS PASSED
$
当你完成后,你的方案也许仅有几行代码,但如何正确运行是一个棘手的问题。我们将使用原始存储库中的alarmtest.c版本测试您的代码。你可以修改alarmtest.c来帮助调试,但是要确保原来的alarmtest
显示所有的测试都通过了。
test0: invoke handler (调用处理程序)
首先修改内核以跳转到用户空间中的报警处理程序,这将导致test0
打印“alarm!”。不用担心输出“alarm!”之后会发生什么;如果您的程序在打印“alarm!”后崩溃,对于目前来说也是正常的。以下是一些提示:
- 您需要修改Makefile以使alarmtest.c被编译为xv6用户程序。
- 放入user/user.h的正确声明是:
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
- 更新user/usys.pl(此文件生成user/usys.S)、kernel/syscall.h和kernel/syscall.c以允许
alarmtest
调用sigalarm
和sigreturn
系统调用。 - 目前来说,你的
sys_sigreturn
系统调用返回应该是零。 - 你的
sys_sigalarm()
应该将报警间隔和指向处理程序函数的指针存储在struct proc
的新字段中(位于kernel/proc.h)。 - 你也需要在
struct proc
新增一个新字段。用于跟踪自上一次调用(或直到下一次调用)到进程的报警处理程序间经历了多少滴答;您可以在proc.c的allocproc()
中初始化proc
字段。 - 每一个滴答声,硬件时钟就会强制一个中断,这个中断在kernel/trap.c中的
usertrap()
中处理。 - 如果产生了计时器中断,您只想操纵进程的报警滴答;你需要写类似下面的代码
if(which_dev == 2) ...
- 仅当进程有未完成的计时器时才调用报警函数。请注意,用户报警函数的地址可能是0(例如,在user/alarmtest.asm中,
periodic
位于地址0)。 - 您需要修改
usertrap()
,以便当进程的报警间隔期满时,用户进程执行处理程序函数。当RISC-V上的陷阱返回到用户空间时,什么决定了用户空间代码恢复执行的指令地址? - 如果您告诉qemu只使用一个CPU,那么使用gdb查看陷阱会更容易,这可以通过运行
make CPUS=1 qemu-gdb
- 如果
alarmtest
打印“alarm!”,则您已成功。
test1/2: resume interrupted code(恢复被中断的代码)
alarmtest
打印“alarm!”后,很可能会在test0
或test1
中崩溃,或者alarmtest
(最后)打印“test1 failed”,或者alarmtest
未打印“test1 passed”就退出。要解决此问题,必须确保完成报警处理程序后返回到用户程序最初被计时器中断的指令执行。必须确保寄存器内容恢复到中断时的值,以便用户程序在报警后可以不受干扰地继续运行。最后,您应该在每次报警计数器关闭后“重新配置”它,以便周期性地调用处理程序。
作为一个起始点,我们为您做了一个设计决策:用户报警处理程序需要在完成后调用sigreturn
系统调用。请查看alarmtest.c中的periodic
作为示例。这意味着您可以将代码添加到usertrap
和sys_sigreturn
中,这两个代码协同工作,以使用户进程在处理完警报后正确恢复。
提示:
- 您的解决方案将要求您保存和恢复寄存器——您需要保存和恢复哪些寄存器才能正确恢复中断的代码?(提示:会有很多)
- 当计时器关闭时,让
usertrap
在struct proc
中保存足够的状态,以使sigreturn
可以正确返回中断的用户代码。 - 防止对处理程序的重复调用——如果处理程序还没有返回,内核就不应该再次调用它。
test2
测试这个。 - 一旦通过
test0
、test1
和test2
,就运行usertests
以确保没有破坏内核的任何其他部分。
可选的挑战部分
- 在
backtrace()
中打印函数的名称和行号,而不仅仅是数字化的地址。(hard)
My Record
1. RISC-V assembly
相关问题
这里将 cat.asm
的相关部分放在下面
0000000000000000 <g>:
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int g(int x) {
0: 1141 addi sp,sp,-16
2: e422 sd s0,8(sp)
4: 0800 addi s0,sp,16
return x+3;
}
6: 250d addiw a0,a0,3
8: 6422 ld s0,8(sp)
a: 0141 addi sp,sp,16
c: 8082 ret
000000000000000e <f>:
int f(int x) {
e: 1141 addi sp,sp,-16
10: e422 sd s0,8(sp)
12: 0800 addi s0,sp,16
return g(x);
}
14: 250d addiw a0,a0,3
16: 6422 ld s0,8(sp)
18: 0141 addi sp,sp,16
1a: 8082 ret
000000000000001c <main>:
void main(void) {
1c: 1141 addi sp,sp,-16
1e: e406 sd ra,8(sp)
20: e022 sd s0,0(sp)
22: 0800 addi s0,sp,16
printf("%d %d\n", f(8)+1, 13);
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7b050513 addi a0,a0,1968 # 7d8 <malloc+0xea>
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 27e080e7 jalr 638(ra) # 2b8 <exit>
- 哪些寄存器包含函数参数?例如在
main
对printf
的调用中,哪个寄存器保存13?
a0-a7 包含函数参数 a2存储13
main
的汇编代码中对函数f
的调用在哪里?对g
的调用在哪里(提示:编译器可能会将函数内联)
在C代码中,main函数调用f,f调用g;而在生成的汇编代码中,main函数进行了内联优化处理,从代码li a1,12可以看出,main直接计算出了结果并存储
printf
函数位于哪个地址?
0x630
- 在
main
中prinf
的jalr
之后的寄存器ra
中有什么值?
auipc
(Add Upper Immediate to PC):auipc rd imm
,将高位立即数加到PC上,从下面的指令格式可以看出,该指令将20位的立即数左移12位之后(右侧补0)加上PC的值,将结果保存到dest位置,图中为rd
寄存器
下面来看jalr
(jump and link register):jalr rd, offset(rs1)
跳转并链接寄存器。jalr指令会将当前PC+4保存在rd中,然后跳转到指定的偏移地址offset(rs1)
。
来看XV6的代码:
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
第一行代码:00000097H=00...0 0000 1001 0111B
,对比指令格式,可见imm=0,dest=00001,opcode=0010111,对比汇编指令可知,auipc的操作码是0010111,ra寄存器代码是00001。这行代码将0x0左移12位(还是0x0)加到PC(当前为0x30)上并存入ra中,即ra中保存的是0x30
第2行代码:600080e7H=0110 0...0 1000 0000 1110 0111B
,可见imm=0110 0000 0000,rs1=00001,funct3=000,rd=00001,opcode=1100111,rs1和rd的知识码都是00001,即都为寄存器ra
。这对比jalr的标准格式有所不同,可能是此两处使用寄存器相同时,汇编中可以省略rd
部分。
ra中保存的是0x30,加上0x600后为0x630,即printf
的地址,执行此行代码后,将跳转到printf函数执行,并将PC+4=0X34+0X4=0X38保存到ra
中,供之后返回使用。
- 运行一下代码
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
程序的输出是什么?这是将字节映射到字符的ASCII码表。
输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i
设置成什么?是否需要将57616
更改为其他值?
57616=0xE110,0x00646c72小端存储为72-6c-64-00,对照ASCII码表
72:r
6c:l
64:d
00:充当字符串结尾标识
因此输出为:HE110 World
若为大端存储,i应改为0x726c6400,不需改变57616
- 在下面的代码中,“
y=
”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?
printf("x=%d y=%d", 3);
原本需要两个参数,却只传入了一个,因此y=后面打印的结果取决于之前a2中保存的数据
2. Backtrace
-
首先先在
kernel/defs.h
中添加定义// printf.c void printf(char*, ...); void panic(char*) __attribute__((noreturn)); void printfinit(void); void backtrace();
-
根据提示,在
kernel/riscv.h
中增加r_fp()
的实现,用于读取寄存器s0//r_fp()用来读取寄存器s0 static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x) ); return x; }
-
在
kernel/printf.c
中增加backtrace()
的实现void backtrace() { printf("backtrace:\n"); //读取当前帧指针 uint64 fp = r_fp(); while(PGROUNDUP(fp) - PGROUNDDOWN(fp) == PGSIZE) { //调用的函数的返回地址保存在固定偏移-8的位置 uint64 ret_addr = *(uint64*)(fp - 8); printf("%p\n",ret_addr); //前一个帧指针保存在固定偏移-16的位置 fp = *(uint64*)(fp - 16); } }
-
在
kernel/sysproc.c
的sys_sleep()
函数中调用backtrace()
uint64 sys_sleep(void) { int n; uint ticks0; 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); backtrace(); return 0; }
运行结果:
make qemu
启动xv6,然后执行bttest
,bttest
调用sleep
(system call),然后依次调用trap.c , syscall.c , sysproc.c
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ bttest
backtrace:
0x0000000080002d66
0x0000000080002bc8
0x00000000800028b2
- 退出qemu,执行
addr2line
(提示中给的代码)
root@shimo-rin-virtual-machine:/home/shimo_rin/桌面/xv6/xv6-lab/xv6-labs-2020# addr2line -e kernel/kernel
0x0000000080002d66
0x0000000080002bc8
0x00000000800028b2/home/shimo_rin/桌面/xv6/xv6-lab/xv6-labs-2020/kernel/sysproc.c:74
/home/shimo_rin/桌面/xv6/xv6-lab/xv6-labs-2020/kernel/syscall.c:140
/home/shimo_rin/桌面/xv6/xv6-lab/xv6-labs-2020/kernel/trap.c:76
3. Alarm
test0
先理解下述在过程中:进行函数调用时,PC(程序计数器)发生的变化
- 在
ecall
指令中,将PC
保存到SEPC
- 在
usertrap
中将SEPC
保存到p->trapframe->epc
p->trapframe->epc
+4
指向下一条指令- 执行系统调用
- 在
usertrapret
中将SEPC
改写为p->trapframe->epc
中的值 - 在
sret
中将PC
设置为SEPC
的值
可见执行系统调用后返回到用户空间,继续执行的指令的地址是由p->trapframe->epc
决定的,因此在usertrap
中主要就是完成p->trapframe->epc
的设置工作。
以下是实验步骤:
-
首先先修改Makefile,(因为我们需要用老师给的测试程序测试实验结果)
UPROGS=\ $U/_cat\ $U/_echo\ $U/_forktest\ $U/_grep\ $U/_init\ $U/_kill\ $U/_ln\ $U/_ls\ $U/_mkdir\ $U/_rm\ $U/_sh\ $U/_stressfs\ $U/_usertests\ $U/_grind\ $U/_wc\ $U/_zombie\ $U/_alarmtest\ //添加该行
-
修改
user/usys.pl
(用以实现系统调用)entry("fork"); entry("exit"); entry("wait"); entry("pipe"); entry("read"); entry("write"); entry("close"); entry("kill"); entry("exec"); entry("open"); entry("mknod"); entry("unlink"); entry("fstat"); entry("link"); entry("mkdir"); entry("chdir"); entry("dup"); entry("getpid"); entry("sbrk"); entry("sleep"); entry("uptime"); // 添加如下两行 entry("sigalarm"); entry("sigreturn");
-
修改
user/user.h
(都是Lab2中的添加新的系统调用的操作)// system calls ... int getpid(void); char* sbrk(int); int sleep(int); int uptime(void); // 添加如下2行 int sigalarm(int ticks, void (*handler)()); int sigreturn(void);
-
修改
kernel/syscall.h
// System call numbers ... #define SYS_unlink 18 #define SYS_link 19 #define SYS_mkdir 20 #define SYS_close 21 // 添加如下2行 #define SYS_sigalarm 22 #define SYS_sigreturn 23
-
修改
kernel/syscall.c
... extern uint64 sys_wait(void); extern uint64 sys_write(void); extern uint64 sys_uptime(void); // 声明在别处定义的两个系统调用函数 extern uint64 sys_sigalarm(void); extern uint64 sys_sigreturn(void); ... // 将新增的系统调用按顺序添加到系统调用数组中 static uint64 (*syscalls[])(void) = { [SYS_fork] sys_fork, [SYS_exit] sys_exit, [SYS_wait] sys_wait, [SYS_pipe] sys_pipe, [SYS_read] sys_read, [SYS_kill] sys_kill, [SYS_exec] sys_exec, [SYS_fstat] sys_fstat, [SYS_chdir] sys_chdir, [SYS_dup] sys_dup, [SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk, [SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, [SYS_open] sys_open, [SYS_write] sys_write, [SYS_mknod] sys_mknod, [SYS_unlink] sys_unlink, [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, //新增 [SYS_sigalarm] sys_sigalarm, [SYS_sigreturn] sys_sigreturn, };
-
在
kernel/proc.h
中在proc
结构体中增加新的字段,用以保存进程进行Alarm
操作的相关信息(提示中已给出)// Per-process state struct proc { struct spinlock lock; // p->lock must be held when using these: enum procstate state; // Process state struct proc *parent; // Parent process void *chan; // If non-zero, sleeping on chan int killed; // If non-zero, have been killed int xstate; // Exit status to be returned to parent's wait int pid; // Process ID // these are private to the process, so p->lock need not be held. uint64 kstack; // Virtual address of kernel stack uint64 sz; // Size of process memory (bytes) pagetable_t pagetable; // User page table struct trapframe *trapframe; // data page for trampoline.S struct context context; // swtch() here to run process struct file *ofile[NOFILE]; // Open files struct inode *cwd; // Current directory char name[16]; // Process name (debugging) int alarm_interval; // 报警间隔 void (*alarm_handler)(); // 指向报警处理函数的指针 int ticks_count; // 两次报警间的滴答计数 };
-
并对新增的两个字段进行相应的
alloc
和free
操作,在kernel/proc.c
的allocproc() freeproc()
中static struct proc* allocproc(void) { struct proc *p; for(p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if(p->state == UNUSED) { goto found; } else { release(&p->lock); } } return 0; found: p->pid = allocpid(); // Allocate a trapframe page. if((p->trapframe = (struct trapframe *)kalloc()) == 0){ release(&p->lock); return 0; } // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ freeproc(p); release(&p->lock); return 0; } // Set up new context to start executing at forkret, // which returns to user space. memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE; p->alarm_handler = 0; p->alarm_interval = 0; p->ticks_count = 0; return p; } static void freeproc(struct proc *p) { if(p->trapframe) kfree((void*)p->trapframe); p->trapframe = 0; if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); p->pagetable = 0; p->sz = 0; p->pid = 0; p->parent = 0; p->name[0] = 0; p->chan = 0; p->killed = 0; p->xstate = 0; p->state = UNUSED; p->alarm_handler = 0; p->alarm_interval = 0; p->ticks_count = 0; }
-
下面是对新增的两个系统调用的具体实现,在
kernel/sysproc.c
中// 根据给出的提示 uint64 sys_sigalarm(void) { // p->alarm_interval = p->trapframe->a0 // (uint64*)p->alarm_handler = p->trapframe->a1 if(argint(0,&myproc()->alarm_interval) < 0 || argaddr(1,(uint64*)&myproc()->alarm_handler) < 0) return -1; return 0; } uint64 sys_sigreturn(void) { return 0; }
-
完善
kernel/trap.c
// give up the CPU if this is a timer interrupt. if(which_dev == 2){ if(++p->ticks_count == p->alarm_interval){ //滴答计数达到设定的报警间隔 //滴答计数归0,设置陷阱帧中保留的程序计数器 p->ticks_count = 0; p->trapframe->epc = (uint64)p->alarm_handler; } yield(); } usertrapret();
-
测试效果
test0 passed
test1/2
需要解决的主要问题是 寄存器保存恢复和防止重复执行的问题。
没有alarm时运行的大致过程
- 进入kernel space,保存用户寄存器到
p->trapframe
- 陷阱处理过程
- 恢复用户寄存器,返回user space
在test0,添加alarm后,过程有些改变
- 进入kernel space, 保存用户寄存器到
p->trapframe
- 陷阱处理过程
- 恢复用户寄存器,返回user space。但是,此时返回的不是trap时的程序地址,而是处理函数
handler
的地址,而handler
有可能会改变用户寄存器。
因此,我们需要在usertrap
中再次保存用户寄存器,当handler
调用sigreturn
时再次恢复,并且要防止handler
执行过程中重复调用
实验过程如下
-
再在
kernel/proc.h
的struct proc
中新增两个字段int is_alarming; // 是否正在执行警告处理函数----handler函数 struct trapframe* alarm_trapframe; // 用来备份用户寄存器的警告陷阱帧
-
在
kernel/proc.c
中,修改allocproc()和freeproc()
,设定好相关分配和回收的代码static struct proc* allocproc(void) { struct proc *p; for(p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if(p->state == UNUSED) { goto found; } else { release(&p->lock); } } return 0; found: p->pid = allocpid(); // Allocate a trapframe page. if((p->trapframe = (struct trapframe *)kalloc()) == 0){ release(&p->lock); return 0; } // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ freeproc(p); release(&p->lock); return 0; } // Set up new context to start executing at forkret, // which returns to user space. memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE; // 初始化警告字段 if((p->alarm_trapframe = (struct trapframe*)kalloc()) == 0){ freeproc(p); release(&p->lock); return 0; } p->alarm_handler = 0; p->alarm_interval = 0; p->ticks_count = 0; p->is_alarming = 0; return p; } static void freeproc(struct proc *p) { if(p->trapframe) kfree((void*)p->trapframe); p->trapframe = 0; if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); if(p->alarm_trapframe) //释放备份的警告陷阱帧 kfree((void*)p->alarm_trapframe); p->pagetable = 0; p->sz = 0; p->pid = 0; p->parent = 0; p->name[0] = 0; p->chan = 0; p->killed = 0; p->xstate = 0; p->state = UNUSED; // 回收警告字段 p->is_alarming = 0; p->alarm_trapframe = 0; p->alarm_handler = 0; p->alarm_interval = 0; p->ticks_count = 0; }
-
修改
kernel/trap.c 中的 usertrap()
... // give up the CPU if this is a timer interrupt. if(which_dev == 2){ if(++p->ticks_count == p->alarm_interval && p->alarm_interval != 0 && p->is_alarming == 0){ // 保存用户寄存器的内容 memmove(p->alarm_trapframe, p->trapframe, sizeof(struct trapframe)); //滴答计数归0,设置陷阱帧中保留的程序计数器 p->ticks_count = 0; p->trapframe->epc = (uint64)p->alarm_handler; p->is_alarming = 1; //标记正在执行handler函数 } yield(); } ...
-
修改
kernel/sysproc.c
中的sys_sigreturn
uint64 sys_sigreturn(void) { // 恢复陷阱帧 memmove(myproc()->trapframe, myproc()->alarm_trapframe, sizeof(struct trapframe)); myproc()->is_alarming = 0; return 0; }
测试结果:passed。