这一个实验主要是加强你对系统调用,陷入内核的一些处理,突然发现lecture5的内容还是挺重要的,得重新回去看一下了
backtrace
做这个实验要知道一些前置知识
- 这个课堂笔记中有张栈帧布局图。注意返回地址位于栈帧帧指针的固定偏移(-8)位置,并且保存的帧指针位于帧指针的固定偏移(-16)位置,一个栈分配一个
PGSIZE
kernel/printf.c
void backtrace() {
uint64 fp = r_fp();
while(fp != PGROUNDUP(fp)) { // 如果已经到达栈底
uint64 ra = *(uint64*)(fp - 8); // return address
printf("%p\n", ra);
fp = *(uint64*)(fp - 16); // previous fp
}
}
alarm
根据hint,我们要完成一个sigalram,定时器每隔一些间隔执行一个处理函数,完成执行后执行一次sigreturn
完成这个有几个点要考虑
- 如何完成在调用处理函数之前保存寄存器,以便处理完后继续顺利执行
- 如何防止在一个处理函数未返回前,内核重复调用处理函数
完成第一个点
由lecture6可知进程的寄存器被保存在struct trapframe中,由此可知我们只需要在proc结构体中再创建一个struct trapframe用于保存在调用前的trapfrme即可
完成第二个点
在proc结构体中加一个标志,正在执行时设置为 1 ,结束时设置为 0 即可
由以上我们写出proc 结构体
// proc.h
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
uint64 handler;
int ticks;
int num;
int in_handler; // Is in process
struct trapframe *clock_trapframe;
// 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)
};
由hint写出 sigalarm 和 sigreturn
uint64
sys_sigalarm(void){
int ticks;
argint(0, &ticks);
uint64 handler;
argaddr(1, &handler);
struct proc* p =myproc();
p->ticks = ticks;
p->handler = handler;
return 0;
}
uint64
sys_sigreturn(void){
struct proc* p =myproc();
*p->trapframe = *p->clock_trapframe;
p->in_handler = 0;
return 0;
}
由hint写出计时和调用
kernel/trap.c
if(which_dev == 2){
if(p->ticks != 0){
p->num++;
if(p->num == p->ticks && p->in_handler == 0){
p->num = 0;
*p->clock_trapframe = *p->trapframe;
p->trapframe->epc = p->handler;
p->in_handler = 1;
}
}
yield();
}
在进程初始化和释放进程中对新添加的字段进行处理
kernel/proc.c
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;
}
if((p->clock_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->num = 0;
p->in_handler = 0;
return p;
}
// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
if(p->clock_trapframe)
kfree((void*)p->clock_trapframe);
p->clock_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;
}