xv6 Lab Traps Alarm

158 阅读3分钟

实验要求:详见这里

要点:

  1. 实现系统调用 sigalarm(n, fn),让函数 fn 每n个tick被调用一次。这里需要注意的是,传入的函数 fn 的地址是用户态函数的虚拟地址。时钟中断发生后,内核如何让用户态的代码执行流切到函数 fn 执行,这个可以通过设置 sepc 寄存器,返回用户态时,返回到函数 fn 执行,而不是返回原来的用户态进程被打断的地方继续执行。
  2. fn 函数执行完成后,要返回原来的代码执行流继续执行。在函数 fn 的最后,调用另一个系统调用 sigreturn,返回用户空间原来的执行流。这就需要内核在返回 fn 函数执行之前,保存用户态的现场。sigreturn 时恢复。
  3. fn 函数在没有执行完毕之前,应该禁止再次调用。也就是内核需要知道当进程的用户态代码是否是处在函数 fn 的执行流里。
  4. 要注意到,sigreturn 是一个系统调用,它也有返回值,它的返回值也会破坏寄存器 a0 中的原有值。

image.png

关键的修改记录:

diff --git a/kernel/proc.h b/kernel/proc.h
index d021857..cd5ba44 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -104,4 +104,11 @@ struct proc {
   struct file *ofile[NOFILE];  // Open files
   struct inode *cwd;           // Current directory
   char name[16];               // Process name (debugging)
+
+  // sigalarm
+  int siginterval;
+  int sigtickcnt;
+  int sigrunning;
+  uint64 sighandler;
+  struct trapframe sigtrapframe;
 };

diff --git a/kernel/proc.c b/kernel/proc.c
index 58a8a0b..b386e04 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -140,6 +140,12 @@ found:
     return 0;
   }
 
+  // sig init
+  p->sighandler = 0;
+  p->siginterval = 0;
+  p->sigtickcnt = 0;
+  p->sigrunning = 0;
+
   // Set up new context to start executing at forkret,
   // which returns to user space.
   memset(&p->context, 0, sizeof(p->context));
@@ -168,6 +174,11 @@ freeproc(struct proc *p)
   p->chan = 0;
   p->killed = 0;
   p->xstate = 0;
+   // sig init
+  p->sighandler = 0;
+  p->siginterval = 0;
+  p->sigtickcnt = 0;
+  p->sigrunning = 0;
   p->state = UNUSED;
 }

@@ -686,3 +697,19 @@ procdump(void)
     printf("\n");
   }
 }
+
+// sig tickcnt++
+void
+proc_sigtickcnt_inc(void)
+{
+  struct proc *p;
+  for(p = proc; p < &proc[NPROC]; p++) {
+    acquire(&p->lock);
+    if (p->siginterval != 0) {
+      p->sigtickcnt++;
+    }
+    release(&p->lock);
+  }
+  return;
+}

diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index 3b4d5bd..00595b3 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -91,3 +91,33 @@ sys_uptime(void)
   release(&tickslock);
   return xticks;
 }
+
+uint64
+sys_sigalarm(void)
+{
+  int sigticks;
+  uint64 handler;
+  struct proc *p;
+
+  argint(0, &sigticks);
+  argaddr(1, &handler);
+
+  if ((sigticks == 0) && (handler != 0)) {
+    return -1;
+  }
+
+  p = myproc();
+  p->siginterval = sigticks;
+  p->sighandler = handler;
+  return 0;
+}
+
+uint64
+sys_sigreturn(void)
+{
+  struct proc *p = myproc();
+  memmove((void *)(p->trapframe), (const void *)&(p->sigtrapframe), sizeof(struct trapframe));
+  p->sigrunning = 0;
+  p->sigtickcnt = 0;
+  return p->trapframe->a0;
+}

diff --git a/kernel/trap.c b/kernel/trap.c
index 512c850..370400e 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -77,8 +77,10 @@ usertrap(void)
     exit(-1);
 
   // give up the CPU if this is a timer interrupt.
-  if(which_dev == 2)
+  if(which_dev == 2) {
+    proc_sigtickcnt_inc();
     yield();
+  }
 
   usertrapret();
 }
@@ -96,6 +98,12 @@ usertrapret(void)
   // we're back in user space, where usertrap() is correct.
   intr_off();
 
+  if ((p->sigrunning == 0) && (p->siginterval != 0) && (p->sigtickcnt >= p->siginterval)) {
+    p->sigrunning = 1;
+    memmove((void *)&(p->sigtrapframe), (const void *)(p->trapframe), sizeof(struct trapframe));
+    p->trapframe->epc = p->sighandler;
+  }
+
   // send syscalls, interrupts, and exceptions to uservec in trampoline.S
   uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
   w_stvec(trampoline_uservec);
@@ -150,6 +158,9 @@ kerneltrap()
     panic("kerneltrap");
   }
 
+  if(which_dev == 2)
+    proc_sigtickcnt_inc();
+
   // give up the CPU if this is a timer interrupt.
   if(which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING)
     yield();

代码调试记录:

在实现 sys_sigalarm 函数时,由于自己手欠,将 argaddr(1, &handler); 写成了 argaddr(0, &handler);,导致函数地址错误的获取成了2,本来应该为0的。

如下图所示:

image.png

导致返回到 fn 执行时,少执行了第一条指令,于是后面的代码破坏了原有的代码执行流的栈上的内容。导致测试用例 test1 测试不通过,同时也是 test3 测试时异常的原因,因为PC值不对齐。

$ alarmtest
test0 start
..alarm!
test0 passed
test1 start
..alarm!
.alarm!
.alarm!
.alarm!
.alarm!
.alarm!
.alarm!
.alarm!
.alarm!
.alarm!

test1 failed: foo() executed fewer times than it was called, i:1, j:625
test2 start
........................alarm!
alarm!
test2 passed
test3 start
usertrap(): unexpected scause 0x0000000000000002 pid=3
            sepc=0x000000000000000d stval=0x0000000000000000
$ QEMU: Terminated

另外,我对 sigalarm 做了一点点的改进。详见这里