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

一、引出

大家都知道,信号是在进程返回用户态的时候触发执行的一种机制,但是对于内核线程来说,它们并不会返回用户态。这就好像《大话西游》里打劫脚底板的时候那位坐轿的官人没有脚底板一样尴尬。另一方面,通过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的屏蔽。

[tsecer@Harry 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就自觉了断。

原文地址:https://www.cnblogs.com/tsecer/p/10485792.html

时间: 2024-10-04 20:09:56

内核线程对信号的处理策略的相关文章

Linux下的进程类别(内核线程、轻量级进程和用户进程)以及其创建方式--Linux进程的管理与调度(四)

本文声明 日期 内核版本 架构 作者 GitHub CSDN 2016-05-12 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的创建 本文中出现的,内核线程,轻量级进程,用户进程,用户线程等概念,如果不太熟悉, 可以参见 内核线程.轻量级进程.用户线程三种线程概念解惑(线程≠轻量级进程) Linux进程类别 虽然我们在区分Linux进程类别, 但是我还是想说Linux下只有一种类型的进程,那就是task_str

kobox : key_waitqueue.c -v1 怎样内核线程,怎样使用等待队列

平台:TQ2440 按键驱动 (1)在init中创建一个内核线程作为等待队列的处理函数,该内核线程是一个while(1)死循环,一直检测等待队列的触发条件 /* create a kernel thread */ kthread_run(key_wait_queue_handler, "thread_key_waitqueue", "[key_wait_queue]"); static int key_wait_queue_handler(void *name) {

Linux内核线程kernel thread详解--Linux进程的管理与调度(十)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-02 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的描述 内核线程 为什么需要内核线程 Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求). 内核需要多个执行流并行,为了防止可能的阻塞,支持多线程是必要的. 内核线程就是内核的分身,一个分身可以处理一件特定事情.内核线程的调度由内核负责,一个内核线程处于阻

Java线程与Linux内核线程的映射关系

Linux从内核2.6开始使用NPTL (Native POSIX Thread Library)支持,但这时线程本质上还轻量级进程. Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的.Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核轻量级进程有一一对应的关系.线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个例子,在linux下,只要一个Thread.run就会调用一个fork产生一个线程.

kobox : key_waitqueue.c -v1 如何内核线程,如何使用等待队列

平台:TQ2440 按键驱动 (1)在init中创建一个内核线程作为等待队列的处理函数,该内核线程是一个while(1)死循环,一直检測等待队列的触发条件 DECLARE_WAIT_QUEUE_HEAD(key_driver_wq); /* create a kernel thread */ kthread_run(key_wait_queue_handler, "thread_key_waitqueue", "[key_wait_queue]"); static

linux常见进程与内核线程

kthreadd:这种内核线程只有一个,它的作用是管理调度其它的内核线程.它在内核初始化的时候被创建,会循环运行一个叫做kthreadd的函数,该函数的作用是运行kthread_create_list全局链表中维护的kthread.可以调用kthread_create创建一个kthread,它会被加入到kthread_create_list链表中,同时kthread_create会weak up kthreadd_task.kthreadd在执行kthread会调用老的接口——kernel_th

Linux内核线程

<背景> 内核线程类似于用户进程,通常用于并并发处理性质的任务,并且可以抢占调度.不同于用户进程,内核线程位于内核空间,并且可以访问内核函数和内核数据. <创建内核线程> a:ret = kernel_thread(mythread,null,CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD) 参数注释: CLONE_FILES:大开的文件共享 CLONE_SIGHAND:信号处理程序共享 注:由于内核线程通常对设备驱动程序起到辅助作

Java线程与Linux内核线程的映射关系(转)

Java线程与Linux内核线程的映射关系 Java线程与Linux内核线程的映射关系Linux从内核2.6开始使用NPTL (Native POSIX Thread Library)支持,但这时线程本质上还轻量级进程. Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的.Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核轻量级进程有一一对应的关系.线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个

用户线程和内核线程

1.内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态.切换完毕要从内核态返回用户态:可以很好的利用smp,即利用多核cpu.windows线程就是这样的.用户态转化为内核态的时候需要进行上下文的切换,是耗时的操作,因为有寄存器值的保存装载,内存缓存的失效和载入,中断程序的执行等. 2. 用户级线程内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu,目前Linux pthread大体是这么做的. 线程的实现可以分为两类:用