当进程被调度时,会调用do_notify_resume()来处理信号队列中的信号。信号处理主要是调用sighand_struct结构中对应的信号处理函数。do_notify_resume()(arch/arm/kernel/signal.c)函数的定义如下:

asmlinkage void
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
 if (thread_flags & _TIF_SIGPENDING)
  do_signal(&current->blocked, regs, syscall);
}

  _TIF_SIGPENDING标志是在signal_wake_up()函数中设置的,检查该标志后,接下来调用do_signal()函数,我们来看看do_signal()(arch/arm/kernel/signal.c)的具体定义:

/*
 * Note that 'init' is a special process: it doesn't get signals it doesn't
 * want to handle. Thus you cannot kill init even with a SIGKILL even by
 * mistake.
 *
 * Note that we go through the signals twice: once to check the signals that
 * the kernel can handle, and then we build all the user-level signal handling
 * stack-frames in one go after that.
 */
static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall)
{
 struct k_sigaction ka;
 siginfo_t info;
 int signr;

 /*
  * We want the common case to go fast, which
  * is why we may in certain cases get here from
  * kernel mode. Just return without doing anything
  * if so.
  */
 if (!user_mode(regs))//regs保存的是进入内核态之前的寄存器现场,必须为用户模式,否则直接返回
  return 0;

 if (try_to_freeze())
  goto no_signal;

 if (current->ptrace & PT_SINGLESTEP)
  ptrace_cancel_bpt(current);//和调试相关,我们在后面的文章中会具体分析

 signr = get_signal_to_deliver(&info, &ka, regs, NULL);//取出等待处理的信号
 if (signr > 0) {
  handle_signal(signr, &ka, &info, oldset, regs, syscall);//处理信号
  if (current->ptrace & PT_SINGLESTEP)
   ptrace_set_bpt(current);
  return 1;
 }

 no_signal:
 /*
  * No signal to deliver to the process - restart the syscall.
  */
 if (syscall) {
  if (regs->ARM_r0 == -ERESTART_RESTARTBLOCK) {
   if (thumb_mode(regs)) {
    regs->ARM_r7 = __NR_restart_syscall;
    regs->ARM_pc -= 2;
   } else {
    u32 __user *usp;

    regs->ARM_sp -= 12;
    usp = (u32 __user *)regs->ARM_sp;

    put_user(regs->ARM_pc, &usp[0]);
    /* swi __NR_restart_syscall */
    put_user(0xef000000 | __NR_restart_syscall, &usp[1]);
    /* ldr pc, [sp], #12 */
    put_user(0xe49df00c, &usp[2]);

    flush_icache_range((unsigned long)usp,
         (unsigned long)(usp + 3));

    regs->ARM_pc = regs->ARM_sp + 4;
   }
  }
  if (regs->ARM_r0 == -ERESTARTNOHAND ||
      regs->ARM_r0 == -ERESTARTSYS ||
      regs->ARM_r0 == -ERESTARTNOINTR) {
   restart_syscall(regs);
  }
 }
 if (current->ptrace & PT_SINGLESTEP)
  ptrace_set_bpt(current);
 return 0;
}

  执行do_signal()函数时,进程一定处于内核空间,通常进程只有通过中断或者系统调用才能进入内核空间,regs保存着系统调用或者中断时的现场。user_mode()根据regs中的cpsr寄存器来判断是中断现场环境还是用户态环境。如果不是用户态环境,不对信号进行任何处理,直接从do_signal()函数返回。

  如果user_mode()函数发现regs的现场是内核态,那意味着这不是一次系统调用的返回,也不是一次普通的中断返回,而是一次嵌套中断返回(或者在系统调用过程中发生了中断)。此时大概的执行路径应该是这样的:假设进场现在运行在用户态,此时发生一次中断,进场进入内核态(此时user_mode(regs)返回1,意味着中断现场是用户态。),此后在中断返回前,发生了一个更高优先级的中断,于是CPU开始执行高优先级的处理函数(此时user_mode(regs)返回0,意味着中断现场是在内核态)。当高优先级中断处理结束后,在它返回时,是不应该处理信号的,因为信号的优先级比中断的优先级低。在这种情况下,对信号的处理将会延迟到低优先级中断处理结束之后。相对于Windows内核来说,尽管linux内核中没有一组显式的操作函数来实现这一系列的优先级管理方案,但是linux内核和Windows内核都使用了同样的机制,优先级关系为:高优先级中断->低优先级中断->软中断(类似Windows内中的DPC)->信号(类似Windows内核中的APC)->进程运行。