分析do_signal之前我想先要分析下get_signal这个函数。
get_signal
1 如果信号是SIGNAL_CLD_STOPPED或者SIGNAL_CLD_CONTINUED,则调用do_notify_parent_cldstop发送信息给调试进程
2 进程已经处于将要退出状态也就是可能要进入coredump(被设置了SIGNAL_GROUP_EXIT),那么直接设置信号为SIGKILL且进行coredump操作在返回
3 循环调用dequeue_signal从tsk->pending和tsk->signal->shared_pending取下挂起的信号量然后获取sighand->action,如果信号是SIG_IGN则继续循环处理下一个挂起的信号,如果设置了SA_ONESHOT标志则重置处理函数ka->sa.sa_handler = SIG_DFL;跳出循环,返回。
4 如果对应的处理函数是SIG_DFL则判断信号是否是默认无动作信号如果是则继续处理下一个 信号。在判断是否是stop类信号,如果是则调用do_signal_stop(去激活组停止,并且唤醒进程组其他进程)处理。
其实各位想过没信号处理函数是用户太的函数不能在内核太直接引用也就是说执行用户指定的信号处理函数还需要切回用户太并且能够等程序执行完后能够正常恢复到原来的用户程序执行流。
int get_sigframe(struct rt_sigframe_user_layout *user,struct ksignal *ksig, struct pt_regs *regs)
{
1 获取用户太堆栈sp的两个位置是存放user->next_frame (struct frame_record)和user->sigframe(sigframe_size(user)),栈是向下增长的,此处用的都是减法所以原有的栈不会被破坏
2 调用int __save_altstack(stack_t __user *uss, unsigned long sp)存储t->sas_ss_sp、t->sas_ss_flags、t->sas_ss_size都存储到user.sigframe->uc.uc_stack里
3 调用setup_sigframe将用户态的寄存器保存在user->next_frame和user->sigframe->uc.uc_mcontext里。
4 调用setup_return修改用户太的寄存器的值
regs->regs[0] = usig;
regs->sp = (unsigned long)user->sigframe;
regs->regs[29] = (unsigned long)&user->next_frame->fp;
regs->pc = (unsigned long)ka->sa.sa_handler;
还有个最重要的
regs->regs[30] = (unsigned long)sigtramp ; // __sigrestore_t sigtramp, regs[30]存储的是函数返回地址 ,也就是信号处理函数执行完成之后接着执行的指令地址。 这个地址指令其实也就是调用 sigreturn系统调用(eax int0x80),这个系统调用完成的主要任务就是恢复用户太寄存器。
}
current->sas_ss_sp 和 current->sas_ss_size信号处理程序备用堆栈地址和大小
当设置了SA_ONSTACK标志会使用备用堆栈,当没有设置这个标志的时候
handle_signal :
1 主要就是调用setup_rt_frame进行栈构造以及regs修改
2 检查寄存器是否合法(暂时不关心)
do_signal:
1 调用get_signal获取待处理的信号
2 调用handle_signal去处理信号
其实还有个有意思的地方是系统调用重启:
1 首先判断是否在系统调用中通过in_syscall(regs->syscallno != NO_SYSCALL)
2 如果在则设置restart_addr地址为regs->pc - (compat_thumb_mode(regs) ? 2 : 4);
3 获取上个函数返回值retval = regs->regs[0];
switch (retval)
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
case -ERESTART_RESTARTBLOCK:
regs->regs[0] = regs->orig_x0;//保存系统调用号
regs->pc = restart_addr;//重新赋值pc值为上条指令也就是系统调用的指令,因为肯定是当前pc的上条指令导致陷入内核执行后面一系列操作的。也就是上条指令,先不管内核指令跑到那个了,只关心用户态的指令是不是执行了某条指令陷入了内核执行了某个系统调用,然后pc会自动指向下一条用户态的指令。这里将指令后退也就是所重新执行一边刚刚触发陷入内核的执行系统调用的指令。所以等信号处理函数执行完成后会重新执行这条指令那么就会重新陷入内核重新执行系统调用上层应用层不会存在感知。
break;
if (regs->pc == restart_addr &&
(retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK ||
(retval == -ERESTARTSYS &&
!(ksig.ka.sa.sa_flags & SA_RESTART)))) {
regs->regs[0] = -EINTR;//当没有指定SA_RESTART直接设置返回值为 -EINTR表示系统调用被信号中断
regs->pc = continue_addr;//恢复pc为原来的位置也就是触发系统调用指令的下一条指令。主要do_signal函数都是在系统调用返回用户态前执行。这个流程很简单自己去跟一下即可。
}
到这里基本上内核关于信号处理这块主要原理说完了。