linux信号实现浅析3--信号的执行

来源:互联网 发布:vscode css格式化插件 编辑:程序博客网 时间:2024/06/06 08:42
主要议题:
1信号处理概述
2信号处理过程栈的变化
3被信号处理打断的系统调用
4信号处理之后发生了什么
5信号处理部分内核源码浅析




1信号处理概述
一个进程在什么情况下会检测信号的存在呢?在<情景分析>里说到了:“在中断机制中,处理器的硬件在每条指令结束时都要检测是否有中断请求的存在。信号机制是纯软件的,当然不能依靠硬件来检测信号的到来。同时,要在每条指令结束时都来检测显然是不现实的,甚至是不可能的。所以对信号的检测机制是:每当从系统调用,中断处理或异常处理返回到用户空间的前夕;还有就是当进程被从睡眠中唤醒(必定是在系统调用中)的时候,此时若发现有信号在等待就要提前从系统调用返回。总而言之,不管是正常返回还是提前返回,在返回到用户空间的前夕总是要检测信号的存在并作出反应。”


    信号处理函数是要在用户态执行的,并且需要为其准备相应的参数,在执行完信号处理函数后,还需要将进程恢复到进入内核前的状态去继续进行,因此我们就需要精心为其准备对应的栈,在linux内核中采用的方式是:将进程进去到内核时在内核栈中保存的堆栈临时保存在用户态堆栈中,并在用户态堆栈中为信号处理函数准备对应的参数,将信号处理函数的返回地址设为一个系统调用。当信号处理函数执行完毕后,会执行该系统调用,在系统调用的服务例程中,把保存在用户态的堆栈再copy到内核栈中,然后恢复执行信号处理函数前的堆栈情况,这样就可以继续执行相关进程了。
  
2堆栈的变化
   
    假设从中断返回时检测到了进程有信号需要处理,即current->thread_info->flag中TIF_SIGPENDING标记被置位,就会开始准备执行信号处理函数。


堆栈情况变化如下:
1)在用户空间发生中断时,CPU会自动在内核空间保存用户堆栈的SS, 用户堆栈的ESP,EFLAGS, 用户空间的CS, EIP, 中断号- 256


   | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 -256


   进入内核后,会进行一个SAVE_ALL,这样内核栈上的内容为:


   | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 -256 | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX


2)如果检测到有要处理的信号时,就要开始做一些准备工作了,此时内核里的内容为(进入内核现场时的内容)


  | 用户堆栈的SS1 | 用户堆栈的ESP1 | EFLAGS1 | 用户空间的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1


  (注:?的值有三个选择:中断号 -256/出错代码error_code/出错代码error_code)


    假设将要处理的信号对应的信号处理程序是用户自己设置的,即本文中SIGINT对应的信号处理程序sig_int。


现在要做的事情是让cpu去执行信号处理程序sig_int,但是执行前需要做好准备工作:
   2.1setup_frame


    在用户空间设置好信号栈(struct sigframe)(假设设置好栈后esp的值为sigframe_esp,在本文中其值为0xbfffe7ec)。


     注:struct sigframe里至少包含以下内容,将其设入到用户态栈中。
用户堆栈的SS1, 用户堆栈的ESP1,EFLAGS1, 用户空间的CS1,EIP1,ES1,DS1,EAX1,EBP1,EDI1,ESI1,EDX1,ECX1,EBX1


   2.2设置即将运行的eip的值为信号处理函数sig_int的地址(为0x80482e8),并设置用户ESP的值为sigframe_esp(为0xbfffe7ec),这是通过修改内核栈里的EIP和ESP的值实现的,因为在从系统调用里iret时,会从内核栈里取EIP,ESP。
这时内核栈的内核为:


  | 用户堆栈的SS1 | 0xbfffe7ec | EFLAGS1 | 用户空间的CS1 | 0x80482e8 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1


 


  最后,内核进行RESTORE_ALL,准备进入到用户态,内核栈上的内容为:


    | 用户堆栈的SS1 | 0xbfffe7ec | EFLAGS1 | 用户空间的CS1 | 0x80482e8

即变为如图所示:


     
3)2.3 -> 2.4,信号处理函数执行完毕后,进入了sig_return系统调用,在sig_return里,内核栈的内容为(每个名字后面加一个2以便与前面的1区分)


| 用户堆栈的SS2 | 用户堆栈的ESP2 | EFLAGS2 | 用户空间的CS2 | EIP2 | ? | ES2 | DS2 | EAX2 | EBP2 | EDI2 | ESI2 | EDX2 | ECX2 | EBX2
sig_return要做的主要工作就是根据用户栈里sigframe的值修改内核栈里的内容,使内核栈变为:


| 用户堆栈的SS1 | 用户堆栈的ESP1 | EFLAGS1 | 用户空间的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
至此内核栈里的内容和进行信号处理前一样了。经过RESTORE_ALL后,用户堆栈里的内容也和以前一样(主要指ESP的值一样)。


3信号处理函数执行完毕后,做了些什么?
信号处理函数执行完毕后,会跳转到用户态栈中保存的返回地址处:
 
然后再次执行系统调用119,在系统调用中将内核态堆栈中的regs上下文恢复原样,然后会再次返回从内核态返回用户态(系统调用已经执行完毕了),从而会再次检查TIF_SIGPENDING标记,有此标记代表还有信号未被处理完,会再次处理,直到把所有挂起信号都处理完毕为止。


4系统调用的重新执行
内核不总是能够立即满足系统调用所发出的请求,在这种情况下一般系统会把进程置为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE,如果进程处于TASK_INTERRUPTIBLE,并且系统向该进程发送了一个信号,内核会将系统调用置为TASK_RUNNING,,这种情况下系统调用没有被执行完毕,会返回EINTR错误码。
当从用户态使用系统调用陷入到内核态时,保存在内核态的regs硬件上下文中的orig_eax中会存放着系统调用号,当内核执行完信号处理函数时,会通过该值判断出自己打断了一个系统调用,并决定是否重新执行该系统调用。
若需要重新执行该系统调用的话,只需要在do_signal()内核函数中将内核栈中的regs硬件上下文中的eip及eax修改即可,eip指向系统调用的指令处,即用户当前eip的前一个指令,即regs->eip -= 2;regs->eax = regs->orig_eax;  regs->eax中存放着的是系统调用号。这样在从内核态返回到用户态时,会从eip指令开始执行,即开始执行系统调用。


5信号处理代码浅析
5.1信号处理主函数
static void fastcall do_signal(struct pt_regs *regs)
{
siginfo_t info;
int signr;
struct k_sigaction ka;
sigset_t *oldset;


//用来确认是否返回的是用户态空间,比如在执行系统调用或者中断代码时被中断,又返回到中断中
if (!user_mode(regs))
return;


//信号处理完毕后,是否需要恢复信号
if (test_thread_flag(TIF_RESTORE_SIGMASK))
oldset = &current->saved_sigmask;
else
oldset = &current->blocked;


//从挂起的信号队列中选择一个信号来进行处理,先摘取进程私有的信号挂起队列,再摘取线程组共享的信号挂起队列
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
if (signr > 0) {
if (unlikely(current->thread.debugreg[7]))
set_debugreg(current->thread.debugreg[7], 7);


//对信号进行处理,调用信号处理函数
if (handle_signal(signr, &info, &ka, oldset, regs) == 0) {
if (test_thread_flag(TIF_RESTORE_SIGMASK))
clear_thread_flag(TIF_RESTORE_SIGMASK);
}
return;
}


//有可能信号处理会中断一个系统调用,根据regs->eax的值来决定是否需要重新执行该系统调用。这个分支发生在被未捕获的信号所中断的时候
if (regs->orig_eax >= 0) {
/* Restart the system call - no handlers present */
switch (regs->eax) {
//非时间类的系统调用,将系统调用号放入保存用户
//态堆栈的eax中,将用户态堆栈的eip指向int 0x80系统调用,
//以待进入用户态时恢复其系统调用
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
regs->eax = regs->orig_eax;
regs->eip -= 2;
break;
//时间类的系统调用,比如sleep,将系统调用号指向__NR_restart_syscall
//随后会调用该系统调用,该系统调用会执行current对应的thread_info
//中的restart_block字段。
case -ERESTART_RESTARTBLOCK:
regs->eax = __NR_restart_syscall;
regs->eip -= 2;
break;
}
}


if (test_thread_flag(TIF_RESTORE_SIGMASK)) {
clear_thread_flag(TIF_RESTORE_SIGMASK);
sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL);
}
}


5.2处理某个信号
static int
handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
     sigset_t *oldset,struct pt_regs * regs)
{
int ret;
//判断是否是从一个系统调用中返回,是否打断了一个系统调用,打断了系统调用的话,为系统调用的重新执行做准备。
if (regs->orig_eax >= 0) {
/* If so, check system call restarting.. */
switch (regs->eax) {
       case -ERESTART_RESTARTBLOCK:
case -ERESTARTNOHAND:
regs->eax = -EINTR;
break;


case -ERESTARTSYS:
if (!(ka->sa.sa_flags & SA_RESTART)) {
regs->eax = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
regs->eax = regs->orig_eax;
regs->eip -= 2;
}
}


if (unlikely(regs->eflags & TF_MASK)
   && likely(current->ptrace & PT_DTRACE)) {
current->ptrace &= ~PT_DTRACE;
regs->eflags &= ~TF_MASK;
}


/* Set up the stack frame */
//为信号处理函数创建用户态的栈帧,setup_rt_frame需要sig_info的信息,setup_frame则不需要sig_info的信息
if (ka->sa.sa_flags & SA_SIGINFO)
ret = setup_rt_frame(sig, ka, info, oldset, regs);
else
ret = setup_frame(sig, ka, oldset, regs);


if (ret == 0) {
spin_lock_irq(&current->sighand->siglock);
//有些信号处理函数执行时,希望阻塞掉对某些信号的响应。
sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
if (!(ka->sa.sa_flags & SA_NODEFER))
sigaddset(&current->blocked,sig);
//重新设置进程的信号标记位
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
}


return ret;
}


5.3为信号处理函数准备栈
static int setup_frame(int sig, struct k_sigaction *ka,
      sigset_t *set, struct pt_regs * regs)
{
void __user *restorer;
struct sigframe __user *frame;
int err = 0;
int usig;


//首先先在用户态栈中为信号帧得到其在栈中的起始位置,esp = ((reg->esp - sizeof(*frame) + 4) & -16ul) - 4;
frame = get_sigframe(ka, regs, sizeof(*frame));
//验证这些栈空间是否可以访问
if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
goto give_sigsegv;


usig = current_thread_info()->exec_domain
&& current_thread_info()->exec_domain->signal_invmap
&& sig < 32
? current_thread_info()->exec_domain->signal_invmap[sig]
: sig;
//把信号量放入栈里面的信号帧中
err = __put_user(usig, &frame->sig);
if (err)
goto give_sigsegv;
    //将当前内核栈中的regs上下文存放在信号栈中,还有浮点数状态
err = setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
if (err)
goto give_sigsegv;


//将阻塞的实时信号存放在里面
if (_NSIG_WORDS > 1) {
err = __copy_to_user(&frame->extramask, &set->sig[1],
     sizeof(frame->extramask));
if (err)
goto give_sigsegv;
}


restorer = (void *)VDSO_SYM(&__kernel_sigreturn);
if (ka->sa.sa_flags & SA_RESTORER)
restorer = ka->sa.sa_restorer;


//设置信号处理函数返回地址,restorer返回地址会引发系统调用,在系统调用中恢复以前内核栈中所存放的regs硬件上下文 
err |= __put_user(restorer, &frame->pretcode);
err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));


if (err)
goto give_sigsegv;


//设置内核态中保存的用户态的regs,主要是修改
//esp栈寄存器,eip指令寄存器
regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) ka->sa.sa_handler;
regs->eax = (unsigned long) sig;
regs->edx = (unsigned long) 0;
regs->ecx = (unsigned long) 0;


set_fs(USER_DS);
regs->xds = __USER_DS;
regs->xes = __USER_DS;
regs->xss = __USER_DS;
regs->xcs = __USER_CS;


regs->eflags &= ~TF_MASK;
if (test_thread_flag(TIF_SINGLESTEP))
ptrace_notify(SIGTRAP);
return 0;


give_sigsegv:
force_sigsegv(sig, current);
return -EFAULT;
}
原创粉丝点击