内核线程对信号的处理策略

Posted tsecer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核线程对信号的处理策略相关的知识,希望对你有一定的参考价值。

一、引出

大家都知道,信号是在进程返回用户态的时候触发执行的一种机制,但是对于内核线程来说,它们并不会返回用户态。这就好像《大话西游》里打劫脚底板的时候那位坐轿的官人没有脚底板一样尴尬。另一方面,通过sigprocmask是不能屏蔽掉SIGKILL和SIGSTOP两个信号的,所以如果我们通过kill -9 来杀死一个内核线程的话,内核线程是否会被杀死,如果会,它又是何时如何处理信号的呢?

二、内核线程对信号的处理

1、屏蔽

daemonize:

 /* Block and flush all signals */
 sigfillset(&blocked);
 sigprocmask(SIG_BLOCK, &blocked, NULL);

可以看到,内核线程首先会禁用掉所有的信号。这里直接调用的是sigprocmask,而不是sys_rt_sigprocmask,因为后者中会强制禁掉对SIGKILL和SIGSTOP的屏蔽

asmlinkage long
sys_rt_sigprocmask(int how, sigset_t __user *set, sigset_t __user *oset, size_t sigsetsize)
{
 int error = -EINVAL;
 sigset_t old_set, new_set;

 /* XXX: Don‘t preclude handling different sized sigset_t‘s.  */
 if (sigsetsize != sizeof(sigset_t))
  goto out;

 if (set) {
  error = -EFAULT;
  if (copy_from_user(&new_set, set, sizeof(*set)))
   goto out;
  sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));

这样内核就可以忽略掉所有的信号了。从而绕过了对SIGKILL和SIGSTOP的屏蔽。

[[email protected] ArgABI]$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   2004   624 ?        Ss   Oct05   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S<   Oct05   0:00 [kthreadd]

SigQ: 1/8192
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: ffffffffffffffff

从2.6.31内核开始,看来这个信号是被忽略了,从而避免了内核信号队列中有信号挤压的问题。在至少2.6.21内核中,内核态的信号将会被一只挤压在内核的队列中,永远无法释放。

2、信号的处理

虽然这里是屏蔽了所有的信号,但是如果有些愣头青就是不识趣,就是要给内核发信号,比如sigkill,那么内核线程又将如何应对呢?相当于说,我已经声明闭门谢客,但是就是有人非要往里面闯,那看一下线程是如何处理的。

static int sig_ignored(struct task_struct *t, int sig)
{
 void __user * handler;

 /*
  * Tracers always want to know about signals..
  */
 if (t->ptrace & PT_PTRACED)
  return 0;

 /*
  * Blocked signals are never ignored, since the
  * signal handler may change by the time it is
  * unblocked.
  */
 if (sigismember(&t->blocked, sig))
  return 0;

 /* Is it explicitly or implicitly ignored? */
 handler = t->sighand->action[sig-1].sa.sa_handler;
 return   handler == SIG_IGN ||

可以看到,这里直接返回了SIG_IGN,从而表示这个信号将会被忽略到,这样这个信号就不会放置到线程的信号队列中,从而避免对内核空间的永久占用。

int
__group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
 int ret = 0;

 assert_spin_locked(&p->sighand->siglock);
 handle_stop_signal(sig, p);

 /* Short-circuit ignored signals.  */
 if (sig_ignored(p, sig))
  return ret;这里将会直接返回,从而避免添加到线程信号队列中,以为即使添加到那里也不会有人来处理,所以根本就来个“本来无一物、何处惹尘埃”。

对于早期通过SIGBLOCK屏蔽的信号,它将会在

static void
__group_complete_signal(int sig, struct task_struct *p)
{
 struct task_struct *t;

 /*
  * Now find a thread we can wake up to take the signal off the queue.
  *
  * If the main thread wants the signal, it gets first crack.
  * Probably the least surprising to the average bear.
  */
 if (wants_signal(sig, p))
  t = p;
 else if (thread_group_empty(p))
  /*
   * There is just one thread and it does not need to be woken.
   * It will dequeue unblocked signals before it runs again.
   */

  return;

这里返回,同样不会惊动内核线程。

3、显式处理

如果有些内核的线程比较多愁善感,他可能也会关心民间的疾苦,它可能会处理用户态发送的信号,以示“亲民”。那么他就要主动的通过allow_signal来打开自己可以接受的信号,从而可以让信号发送过来。该函数的实现

int allow_signal(int sig)
{
 if (!valid_signal(sig) || sig < 1)
  return -EINVAL;

 spin_lock_irq(&current->sighand->siglock);
 sigdelset(&current->blocked, sig);
 if (!current->mm) {
  /* Kernel threads handle their own signals.
     Let the signal code know it‘ll be handled, so
     that they don‘t get converted to SIGKILL or
     just silently dropped */
  current->sighand->action[(sig)-1].sa.sa_handler = (void __user *)2;
 }
 recalc_sigpending();
 spin_unlock_irq(&current->sighand->siglock);
 return 0;
}

这样,当信号过来的时候,它通过kill就可以穿过__group_send_sig_info最后的__group_complete_signal,从而将这个内核线程唤醒。但是此时内核就需要自力更生、自己主动判断信号的类型,由于默认是禁止的,所以它自己allow的信号就容易判断了,所以他们就可以在自己被唤醒之后主动判断自己是不是有允许的信号发送过来,如果有的话就执行相关的处理。比如说,如果收到SIGKILL就自觉了断。

以上是关于内核线程对信号的处理策略的主要内容,如果未能解决你的问题,请参考以下文章

Linux 内核线程调度示例一 ① ( 获取线程调度策略 | 断言 assert | 代码示例 )

Linux 内核线程调度示例一 ③ ( 获取线程优先级 | 设置线程调度策略 | 代码示例 )

JVM:线程的实现

内核同步机制

Linux 内核进程优先级与调度策略 ③ ( 设置获取线程优先级的核心函数 | 修改线程调度策略函数 )

线程池