内核中信号发送的过程

kill信号涉及到的系统调用有sys_kill,sys_tkillsys_tgkill函数等,sys_kill的处理的机制有以下这么几种:

  1. pid>0信号被发送到pid的那个进程上。
  2. pid=0
  3. pid=-1信号被发送给所有进程。

  4. pid<-1信号被发送到-pid的线程组上。

这里说下sys_kill函数的过程,流程图如下:




    从上图中可以看出,sys_kill系统调用最终还是调用了group_send_sig_info,这个是线程组通知的函数,也就是信号进入公共信号队列,kill_pgrp_info就是递归某个进程组,然后给进程组中的每个线程组发送信号。

    分析一下kill_something_info函数:


   

  1. static int kill_something_info(int sig, struct siginfo *info, pid_t pid){
  2.             int ret;

  3.             if (pid > 0) { //当pid大于0的时候,发送给pid的那个进程
  4.                     rcu_read_lock();
  5.                     ret = kill_pid_info(sig, info, find_vpid(pid));
  6.                     rcu_read_unlock();
  7.                     return ret;
  8.                 }

  9.             read_lock(&tasklist_lock);
  10.             if (pid != -1) { //如果不是-1的时候,也就是发送给进程组,如果0就是当前进程组,不是0也不是-1就是发送给-pid的进程组
  11.                     ret = __kill_pgrp_info(sig, info,pid ? find_vpid(-pid) : task_pgrp(current));
  12.             } else { //发送给所有进程
  13.                     int retval = 0, count = 0;
  14.                     struct task_struct * p;

  15.                     for_each_process(p) { //递归从init_task
  16.                                 if (task_pid_vnr(p) > 1 &&!same_thread_group(p, current)) {
  17.                                         int err = group_send_sig_info(sig, info, p);
  18.                                         ++count;
  19.                                         if (err != -EPERM)
  20.                                                 retval = err;
  21.                                          }
  22.                                   }
  23.                                 ret = count ? retval : -ESRCH;
  24.                     }
  25.         read_unlock(&tasklist_lock);
  26.         return ret;
  27.         }


    实际发送信号的过程就是在对应的进程的pending队列,写入信号的相关信息,这个过程在send_signal函数中,比较重要的函数就是prepare_signal函数和complete_signal函数,prepare_signal函数,在发送信号的过程中,如果这个进程收到了stop类的信号,那么就需要移除pending队列中的SIGCONT信号,如果收到了SIGCONT信号,那么group stop过程就应该停止,移除pending队列中的stop类信号,同时让那个暂停的进程重新开始运行。

       

  1. if (unlikely(signal->flags & SIGNAL_GROUP_EXIT)) { //处于group exit过程,参考 内核signal_struct中notify_count变量的作用 
  2.         } else if (sig_kernel_stop(sig)) { //如果是stop类信号,移除SIGCONT信号
  3.             rm_from_queue(sigmask(SIGCONT), &signal->shared_pending);
  4.             t = p;
  5.             do {
  6.                     rm_from_queue(sigmask(SIGCONT), &t->pending);
  7.             } while_each_thread(p, t);
  8.         } else if (sig == SIGCONT) { //如果是SIGCONT信号,移除stop信号,同时使那个进程重新开始执行
  9.             unsigned int why;
  10.             rm_from_queue(SIG_KERNEL_STOP_MASK, &signal->shared_pending);
  11.             t = p;
  12.             do {
  13.                     unsigned int state;
  14.                     rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
  15.                     state = __TASK_STOPPED;
  16.                     if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) {
  17.                             set_tsk_thread_flag(t, TIF_SIGPENDING);
  18.                             state |= TASK_INTERRUPTIBLE;
  19.                     }
  20.                     wake_up_state(t, state);
  21.                 } while_each_thread(p, t);


     对于那种SIGKILLSIGSTOP这种信号,在调试器发送stop信号的时候,调用ptraceattach操作的时候发送的信号是SEND_SIG_FORCED的,在处理信号的时候,在prepare_signal函数之后,会判断如果是SEND_SIG_FORCED,那么就不记录那个siginfo了。

complete_signal函数的作用就是找到线程组中的一个线程,唤醒他,设置TIF_SIGPENDING来处理信号。这个筛选线程的处理逻辑在want_signal

       

  1. static inline int wants_signal(int sig, struct task_struct *p)
  2.         {
  3.                 if (sigismember(&p->blocked, sig)) //如果当前信号被block,就说明这个线程不适合处理信号
  4.                         return 0;
  5.                 if (p->flags & PF_EXITING) //如果当前进程正在退出,说明这个线程不能处理信号
  6.                         return 0;
  7.                 if (sig == SIGKILL) //如果是SIGKILL信号,就必须处理信号
  8.                         return 1;
  9.                 if (task_is_stopped_or_traced(p)) //如果当前进程处于暂停或者调试状态,说明这个线程不适合处理信号
  10.                         return 0;
  11.                 return task_curr(p) || !signal_pending(p); //如果是发送给当前线程的信号或者这个线程上没有信号需要处理,那么这个线程就应该处理这个信号
  12.         }

complete_signal的逻辑  kernel/signal.c

       

  1. static void complete_signal(int sig, struct task_struct *p, int group)
  2.         {
  3.                 struct signal_struct *signal = p->signal;
  4.                 struct task_struct *t;
  5.                 if (wants_signal(sig, p)) //判断目标线程是否可以发送信号
  6.                         t = p;
  7.                 else if (!group || thread_group_empty(p)) //如果目标线程不可以发送信号,在若这个信号是tkill过来的或者线程组中没有其他的线程,那么只能什么都不唤醒了。
  8.                         return;
  9.                 else { //递归线程组,找到一个合适的线程
  10.                         t = signal->curr_target;
  11.                         while (!wants_signal(sig, t)) {
  12.                                 t = next_thread(t);
  13.                                 if (== signal->curr_target)
  14.                                 return;
  15.                         }
  16.                         signal->curr_target = t;
  17.                 }
  18.                 if (sig_fatal(p, sig) &&!(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&!sigismember(&t->real_blocked, sig) &&
  19.                 (sig == SIGKILL || !t->ptrace)) {
  20.                         if (!sig_kernel_coredump(sig)) {
  21.                                     signal->flags = SIGNAL_GROUP_EXIT;
  22.                                     signal->group_exit_code = sig;
  23.                                     signal->group_stop_count = 0;
  24.                                     t = p;
  25.                                     do {
  26.                                             sigaddset(&t->pending.signal, SIGKILL);
  27.                                             signal_wake_up(t, 1);
  28.                                     } while_each_thread(p, t);
  29.                         return;
  30.                 }
  31.         }
  32.         signal_wake_up(t, sig == SIGKILL); //最后唤醒指定的线程,设置TIF_SIGPENDING
  33.         return;
  34. }

这里进程调度的代码不熟悉,不多做分析,等看懂了进程调用的代码再来仔细分析。

总结:

     kill信号发送给线程组,因为线程是共享主线程的signal结构的,发送给主线程的want_signal如果为false,那么是由那个线程组中其他的一个线程负责处理这个信号的,也就是说这种线程组group kill,只需要线程组中有一个线程来处理信号即可,其他非线程组kill另当别论。




阅读更多
换一批

没有更多推荐了,返回首页