xv6 Lab Traps Alarm 改进

170 阅读3分钟

做完xv6 的 Traps Alarm 的实验后,发现有个很膈应的地方,就是sigalarm系统调用注册的函数fn,在函数的末尾需要手动调用sigreturn。而类似的Linux的signal机制,注册的函数是与普通函数一样的,没有在函数的最后调用sigreturn这样的系统调用。那在Linux系统中,信号的回调函数执行完成后,如何返回原有的代码执行流呢?


下面是ChatGPT给的回答:

当信号处理函数执行return语句时,程序会尝试跳转到栈帧中存储的返回地址。由于这个返回地址被设置为触发sigreturn系统调用,因此控制权被转移到内核,内核通过sigreturn恢复之前保存的执行上下文,使程序继续从被中断的位置执行。

流程图示意:

[信号处理函数开始执行]
        |
        v
[信号处理函数执行完毕,执行return]
        |
        v
[跳转到预先设置的返回地址,触发sigreturn系统调用]
        |
        v
[内核通过sigreturn恢复原始执行上下文]
        |
        v
[程序继续从中断的位置执行]

于是,我在xv6上实现了这个过程。在函数fn中不需要再手动调用sigreturn。

配置ra为sigreturn的入口地址,这样当函数fn执行到ret指令时,会跳转到sigreturn执行。就不用在函数fn里手动调用sigreturn。

注意这里不能把触发sigreturn系统调用的代码映射到trampoline这个PAGE中,因为这个页被设置为U模式下不能访问。所以新增了页映射,将代码映射到这里,权限配置为只读可执行,U模式下可访问。

image.png

image.png

下面是关键的修改:

diff --git a/kernel/kernel.ld b/kernel/kernel.ld
index ee04f22..1b25188 100644
--- a/kernel/kernel.ld
+++ b/kernel/kernel.ld
@@ -16,6 +16,10 @@ SECTIONS
     *(trampsec)
     . = ALIGN(0x1000);
     ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page");
+    _sigtrampline = .;
+    *(sigtrampsec)
+    . = ALIGN(0x1000);
+    ASSERT(. - _sigtrampline == 0x1000, "error: sigtrampoline larger than one page");
     PROVIDE(etext = .);

diff --git a/kernel/memlayout.h b/kernel/memlayout.h
index cac3cb1..768b8c4 100644
--- a/kernel/memlayout.h
+++ b/kernel/memlayout.h
@@ -62,3 +62,5 @@
 //   TRAPFRAME (p->trapframe, used by the trampoline)
 //   TRAMPOLINE (the same page as in the kernel)
 #define TRAPFRAME (TRAMPOLINE - PGSIZE)
+
+#define SIGTRAMPOLINE (TRAPFRAME - PGSIZE)
\ No newline at end of file


diff --git a/kernel/proc.c b/kernel/proc.c
index 58a8a0b..441646e 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -202,6 +213,16 @@ proc_pagetable(struct proc *p)
     return 0;
   }
 
+  // map the sigtrapframe page just below the trampframe page, for
+  // trampoline.S.
+  if(mappages(pagetable, SIGTRAMPOLINE, PGSIZE,
+              (uint64)usersigreturn, PTE_R | PTE_X | PTE_U) < 0){
+    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
+    uvmunmap(pagetable, TRAPFRAME, 1, 0);
+    uvmfree(pagetable, 0);
+    return 0;
+  }
+
   return pagetable;

@@ -212,6 +233,7 @@ proc_freepagetable(pagetable_t pagetable, uint64 sz)
 {
   uvmunmap(pagetable, TRAMPOLINE, 1, 0);
   uvmunmap(pagetable, TRAPFRAME, 1, 0);
+  uvmunmap(pagetable, SIGTRAMPOLINE, 1, 0);
   uvmfree(pagetable, sz);
 }

diff --git a/kernel/trampoline.S b/kernel/trampoline.S
index 693f8a1..c5d06dc 100644
--- a/kernel/trampoline.S
+++ b/kernel/trampoline.S
@@ -145,7 +146,17 @@ userret:
         # return to user mode and user pc.
         # usertrapret() set up sstatus and sepc.
         sret
+
+.section sigtrampsec
+.globl sigtrampoline
+sigtrampoline:
+.align 4
+.globl usersigreturn
+usersigreturn:
+        li a7, SYS_sigreturn
+        ecall
+        ret
\ No newline at end of file

diff --git a/kernel/trap.c b/kernel/trap.c
index 512c850..2e9985e 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -96,6 +98,13 @@ usertrapret(void)
   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;
+    p->trapframe->ra = SIGTRAMPOLINE + (usersigreturn - sigtrampoline);
   }