做完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模式下可访问。
下面是关键的修改:
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);
}