BUAA-OS-lab4challenge
Challenge Sigaction 实现文档
代码改动与说明
include/env.h
在这里添加了新结构体定义、宏定义,并给 Env 进程块结构体添加新的必要信息。注意为了实现队列,在 sigset_t 结构体中添加了
TAILQ_ENTRY
成员变量。在进程块中添加的信息有:注册新号的信息、信号队列、掩码栈、信号处理函数入口、当前正在处理的信号。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
typedef struct sigset_t { uint32_t sig; TAILQ_ENTRY(sigset_t) sig_link; } sigset_t; struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; }; TAILQ_HEAD(Sig_list, sigset_t); #define SIGINT 2 #define SIGILL 4 #define SIGKILL 9 #define SIGSEGV 11 #define SIGCHLD 17 #define SIGSYS 31 #define SIG_BLOCK 1 #define SIG_UNBLOCK 2 #define SIG_SETMASK 3 struct Env { // challenge struct sigaction env_sigactions[35]; struct Sig_list env_sig_list; // 接收到的信号队列 struct sigset_t env_mask_list[55]; // 掩码栈 int env_mask_cnt; u_int env_sig_entry; // sig handler int env_cur_sig; };
kern/env.c
这里修改的是 env_alloc 函数,对 Env 结构体中新增的成员变量进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int env_alloc(struct Env **new, u_int parent_id) { // ... // challenge for(int i=1;i<=32;i++){ e->env_sigactions[i].sa_handler = NULL; e->env_sigactions[i].sa_mask.sig = 1 << (i - 1); } TAILQ_INIT(&e->env_sig_list); e->env_mask_cnt = 0; e->env_sig_entry = 0; e->env_cur_sig = 0; e->env_mask_list[0].sig = 0; // ... }
kern/syscall_all.c
在这里添加了若干与信号机制有关的系统调用函数,并在销毁进程的时候向父进程发送信号,在创建子进程的时候也对进程块中新增变量进行继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
int sys_env_destroy(u_int envid) { // challenge if(e->env_parent_id){ sys_kill(e->env_parent_id, SIGCHLD); } // end_challenge } int sys_exofork(void) { e->env_status = ENV_NOT_RUNNABLE; e->env_pri = curenv->env_pri; for(int i = 1;i <= 32; i++){ e->env_sigactions[i] = curenv->env_sigactions[i]; } e->env_cur_sig = curenv->env_cur_sig; e->env_mask_cnt = curenv->env_mask_cnt; e->env_sig_entry = curenv->env_sig_entry; for(int i = 0; i <= e->env_mask_cnt; i++) { e->env_mask_list[i] = curenv->env_mask_list[i]; } return e->env_id; }
下面逐一对新增实现的系统调用进行介绍:
信号注册 sys_sigaction,照着题意模拟一遍就好了,注意如果是 SIGKILL,应该遵照题目要求,不修改处理函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int sys_sigaction(u_int envid, int signum, const struct sigaction *newact, struct sigaction *oldact) { // 信号注册系统调用 struct Env *e; try(envid2env(envid, &e, 0)); if (oldact) { *oldact = e->env_sigactions[signum]; } if (newact) { if (signum == SIGKILL) { // 不修改处理函数 e->env_sigactions[signum].sa_mask = newact->sa_mask; } else { e->env_sigactions[signum] = *newact; } } return 0; }
信号发送 sys_kill,本质上是 new 一个信号对象,插入到进程控制块的信号列表里。new 的操作用一个 sigset_t 的数组来实现分配,实际上相当于一个空闲 sigseg_t 的列表。
1 2 3 4 5 6 7 8 9 10 11
static sigset_t sigs[4005]; static int sigs_cnt = 0; int sys_kill(u_int envid, int sig) { struct Env *e; try(envid2env(envid, &e, 0)); sigs[sigs_cnt].sig = sig; TAILQ_INSERT_TAIL(&e->env_sig_list, sigs + sigs_cnt, sig_link); sigs_cnt = (sigs_cnt + 1) % 4000; return 0; }
掩码查询与设置 sys_proc_mask,对照题意模拟即可,注意 __set 和 __oset 两个参数为 NULL 时的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
int sys_proc_mask(int __how, const sigset_t * __set, sigset_t * __oset) { struct Env *e; try(envid2env(0, &e, 0)); sigset_t *s = e->env_mask_list + e->env_mask_cnt; if (__oset) { *__oset = *s; // 取出栈顶元素 } if (__set) { if (__how == SIG_BLOCK) { s->sig |= __set->sig; } else if (__how == SIG_UNBLOCK) { s->sig &= (~__set->sig); } else if (__how == SIG_SETMASK) { s->sig = __set->sig; } else { return -1; } } return 0; }
信号队列查询 sys_get_pending,遍历信号队列即可。
1 2 3 4 5 6 7 8 9 10
int sys_get_pending(sigset_t *__set) { struct Env *e; try(envid2env(0, &e, 0)); __set->sig = 0; sigset_t *i; TAILQ_FOREACH(i, &e->env_sig_list, sig_link) { __set->sig |= 1 << (i->sig - 1); } return 0; }
信号异常处理入口设置 sys_set_sig_entry。
1 2 3 4 5 6
int sys_set_sig_entry(u_int envid, u_int func) { struct Env *e; try(envid2env(envid, &e, 0)); e->env_sig_entry = func; return 0; }
信号处理恢复现场的函数 sys_set_sig_trapframe,仿照 sys_set_tf 函数实现即可,注意需要额外处理掩码栈,并切换进程当前处理的信号为 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int sys_set_sig_trapframe(u_int envid, struct Trapframe *tf){ if(is_illegal_va_range((u_long)tf, sizeof *tf)){ return -E_INVAL; } struct Env *e; try(envid2env(envid, &e, 0)); e->env_mask_cnt--; e->env_cur_sig = 0; if(e == curenv){ *((struct Trapframe *)KSTACKTOP -1) = *tf; return tf->regs[2]; } else { e->env_tf = *tf; return 0; } }
user/lib/fork.c
在这里主要是增加了信号的处理入口 sig_entry,首先判断是否设置了专门的处理函数,如果有,就直接调用,否则进行默认处理。注意处理后都需要调用 sys_set_sig_trapframe 来恢复现场。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
void __attribute__((noreturn)) sig_entry(struct Trapframe *tf, void (*sa_handler)(int), int signum, int envid) { if (sa_handler != 0) { sa_handler(signum); // 直接调用定义好的处理函数 int r = syscall_set_sig_trapframe(0, tf); user_panic("sig_entry syscall_set_trapframe returned %d", r); } switch (signum) { case SIGINT: case SIGILL: case SIGKILL: case SIGSEGV: syscall_env_destroy(envid); //默认处理 user_panic("sig_entry syscall_env_destroy returned"); default:; int r = syscall_set_sig_trapframe(0, tf); user_panic("sig_entry syscall_set_trapframe returned %d", r); } }
此外,在 fork 函数里新增了子进程的 sig_entry 设置。
user/lib/ipc.c
在这里实现了对信号集的一些处理函数,以及 sigaction 注册和 kill 发送,调用已经写好的系统调用即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
int is_illegal_sig(int signum) { return signum < 1 || signum > 32; } int sigaction(int signum, const struct sigaction *newact, struct sigaction *oldact){ if (is_illegal_sig(signum)) { return -1; } try(syscall_set_sig_entry(0, (u_int) sig_entry)); return syscall_sigaction(0, signum, newact, oldact) < 0 ? -1 : 0; } int kill(u_int envid, int sig){ if (is_illegal_sig(sig)) { return -1; } return syscall_kill(envid, sig) < 0 ? -1 : 0; } int sigemptyset(sigset_t *__set) { if (__set) { __set->sig = 0; } else { return -1; } return 0; } // 清空参数中的__set掩码,初始化信号集以排除所有信号。这意味着__set将不包含任何信号。(清0) int sigfillset(sigset_t *__set) { if (__set) { __set->sig = ~0; } else { return -1; } return 0; } // 将参数中的__set掩码填满,使其包含所有已定义的信号。这意味着__set将包括所有信号。(全为1) int sigaddset(sigset_t *__set, int __signo) { if (__set && !is_illegal_sig(__signo)) { __set->sig |= 1 << (__signo - 1); } else { return -1; } return 0; } // 向__set信号集中添加一个信号__signo。如果操作成功,__set将包含该信号。(置位为1) int sigdelset(sigset_t *__set, int __signo) { if (__set && !is_illegal_sig(__signo)) { __set->sig &= ~(1 << (__signo - 1)); } else { return -1; } return 0; } // 从__set信号集中删除一个信号__signo。如果操作成功,__set将不再包含该信号。(置位为0) int sigismember(const sigset_t *__set, int __signo) { if (!__set || is_illegal_sig(__signo)) { return -1; } return (__set->sig >> (__signo - 1)) & 1; } // 检查信号__signo是否是__set信号集的成员。如果是,返回1;如果不是,返回0。 int sigisemptyset(const sigset_t *__set) { if (!__set) { return -1; } return __set->sig == 0; } // 检查信号集__set是否为空。如果为空,返回1;如果不为空,返回0。 int sigandset(sigset_t *__set, const sigset_t *__left, const sigset_t *__right) { if (!__set || !__left || !__right) { return -1; } __set->sig = __left->sig & __right->sig; return 0; } // 计算两个信号集__left和__right的交集,并将结果存储在__set中。 int sigorset(sigset_t *__set, const sigset_t *__left, const sigset_t *__right) { if (!__set || !__left || !__right) { return -1; } __set->sig = __left->sig | __right->sig; return 0; } // 计算两个信号集__left和__right的并集,并将结果存储在__set中。 int sigprocmask(int __how, const sigset_t * __set, sigset_t * __oset) { return syscall_proc_mask(__how, __set, __oset) < 0 ? -1 : 0; } // 根据__how的值更改当前进程的信号屏蔽字。__set是要应用的新掩码,__oset(如果非NULL)则保存旧的信号屏蔽字。 // __how可以是SIG_BLOCK(添加__set到当前掩码)、SIG_UNBLOCK(从当前掩码中移除__set)、或SIG_SETMASK(设置当前掩码为__set)。 int sigpending(sigset_t *__set) { if (!__set) { return -1; } return syscall_get_pending(__set) < 0 ? -1 : 0; } // 获取当前被阻塞且未处理的信号集,并将其存储在__set中。
user/lib/libos.c
在进入 main 函数之前,就设置好 sig_entry,防止在信号注册之前就进行了信号的处理。
1 2 3 4 5 6 7 8 9
void libmain(int argc, char **argv) { // set env to point at our env structure in envs[]. env = &envs[ENVX(syscall_getenvid())]; syscall_set_sig_entry(syscall_getenvid(), sig_entry); // add for challenge // call user main routine main(argc, argv); // exit gracefully exit(); }
至此,信号系统的基础处理函数已经全部实现完毕,只剩下如何把整个信号的处理过程“打通”,也即实现信号处理的跳转。
kern/tlbex.c
实现 do_signal 函数,先遍历信号列表找到将要处理的信号(考虑信号屏蔽、信号优先级、SIGKILL 最优先执行),然后设置掩码和 tf。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
void do_signal(struct Trapframe *tf) { if (curenv->env_cur_sig == SIGKILL) { return; } struct sigset_t *sig_todo = NULL; // 将要处理的信号 struct sigset_t *ss; TAILQ_FOREACH(ss, &curenv->env_sig_list, sig_link) { if(ss->sig == SIGKILL) { // 如果有 SIGKILL,优先处理 sig_todo = ss; break; } u_int cur_mask = curenv->env_mask_list[curenv->env_mask_cnt].sig; if(!((cur_mask >> (ss->sig - 1)) & 1)) { // 如果信号未被屏蔽 if(sig_todo == NULL) { // 如果当前还没有需要处理的信号 sig_todo = ss; } else { // 已经有了,对比优先级 if(ss->sig < sig_todo->sig){ sig_todo = ss; } } } } if (!sig_todo) { return; } else { // printk("catch signal %d\n", sig_todo->sig); } TAILQ_REMOVE(&curenv->env_sig_list, sig_todo, sig_link); // 把新掩码 push 进掩码栈, 上一个掩码,该信号掩码及该信号本身 u_int mask = curenv->env_mask_list[curenv->env_mask_cnt].sig | curenv->env_sigactions[sig_todo->sig].sa_mask.sig | (1 << (sig_todo->sig - 1)); curenv->env_mask_cnt++; curenv->env_mask_list[curenv->env_mask_cnt].sig = mask; curenv->env_cur_sig = sig_todo->sig; struct Trapframe tmp_tf = *tf; if (tf->regs[29] < USTACKTOP || tf->regs[29] >= UXSTACKTOP) { tf->regs[29] = UXSTACKTOP; } tf->regs[29] -= sizeof(struct Trapframe); *(struct Trapframe *)tf->regs[29] = tmp_tf; tf->regs[4] = tf->regs[29]; tf->regs[5] = (unsigned int) (curenv->env_sigactions[sig_todo->sig].sa_handler); tf->regs[6] = sig_todo->sig; tf->regs[7] = curenv->env_id; tf->regs[29] -= 4 * sizeof(tf->regs[4]); tf->cp0_epc = curenv->env_sig_entry; // 跳转到异常处理入口 }
kern/genex.S
异常处理跳转到 do_signal 函数。
FEXPORT(ret_from_exception) move a0, sp addiu sp, sp, -8 jal do_signal addiu sp, sp, 8 RESTORE_ALL eret