Chinaunix首页 | 论坛 | 博客
  • 博客访问: 325023
  • 博文数量: 100
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 665
  • 用 户 组: 普通用户
  • 注册时间: 2015-02-02 12:43
文章分类

全部博文(100)

文章存档

2015年(100)

我的朋友

分类: LINUX

2015-06-10 10:51:02

send_signal()函数有四个入参,sig表示要发送的信号,info表征信号的一些信息,t接收所发送信号的进程描述符,group表示是发送给描述符t所代表的单个进程还是进程描述符t所处的整个线程组,send_signal()调用__send_signal(),多了个入参from_ancestor_ns,没有过多关注。

  • prepare()函数

  __send_signal()首先调用prepare_signal(),内核对该函数的注释是“在信号产生时立即执行,而不用考虑信号是否被阻塞、忽略或者处理。对于SIGCONT信号,它真正使接收进程恢复执行,对于停止信号,该函数还不能真正停止进程。如果信号需要被发送就返回真,否则就会被丢弃。”函数主要有三个if组成,分别是三种情况:

复制代码
 1 if (unlikely(signal->flags & SIGNAL_GROUP_EXIT))  2  {  3 /*  4  * The process is in the middle of dying, nothing to do.  5 */  6  }  7 /* 检查要发送的信号是否是停止信号,如果是停止信号就删除掉已挂起的SIGCONT信号 */  8 else if (sig_kernel_stop(sig))  9  { 10 /* 11  * This is a stop signal.  Remove SIGCONT from all queues. 12 */ 13 rm_from_queue(sigmask(SIGCONT), &signal->shared_pending); 14 t = p; 15 do 16  { 17 rm_from_queue(sigmask(SIGCONT), &t->pending); 18 19  } while_each_thread(p, t); 20 21 }
复制代码

 

  1.如果信号接收进程的signal_struct->flags置位SIGNAL_GROUP_EXIT,表示该线程组正在被killed,没有必要再产生信号给它,什么也不做;

  2.调用sig_kernel_stop来检查发送信号是否属于暂停信号,把线程组中共享的挂起信号队列和线程组中所有进程的私有挂起信号队列中的SIGCONT信号删除,因为SIGCONT信号和暂停信号是冲突的;

复制代码
 1 /*  2  * Remove all stop signals from all queues,  3  * and wake all threads.  4 */  5 rm_from_queue(SIG_KERNEL_STOP_MASK, &signal->shared_pending);  6 t = p;  7 do  8  {  9 unsigned int state; 10 rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending); 11 /* 12  * If there is a handler for SIGCONT, we must make 13  * sure that no thread returns to user mode before 14  * we post the signal, in case it was the only 15  * thread eligible to run the signal handler--then 16  * it must not do anything between resuming and 17  * running the handler.  With the TIF_SIGPENDING 18  * flag set, the thread will pause and acquire the 19  * siglock that we hold now and until we've queued 20  * the pending signal. 21  * 22  * Wake up the stopped thread _after_ setting 23  * TIF_SIGPENDING 24 */ 25 state = __TASK_STOPPED; 26 if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) 27  { 28  set_tsk_thread_flag(t, TIF_SIGPENDING); 29 state |= TASK_INTERRUPTIBLE; 30  } 31 /* 唤醒该进程 */ 32  wake_up_state(t, state); 33 34 } while_each_thread(p, t);
复制代码

  3.如果信号是SIGCONT信号,将线程组共享的挂起信号中的停止信号删除,接下来遍历所有进程,删除每个进程私有挂起信号中的暂停信号,设置进程状态,唤醒各进程,恢复其执行;说明要继续执行线程组中的某个轻量级进程,就会把整个线程组都唤醒,使其继续执行。

  调用sig_ignored()来判断信号是否能被忽略:

复制代码
 1 static int sig_ignored(struct task_struct *t, int sig, int from_ancestor_ns)  2 {  3 /*  4  * Blocked signals are never ignored, since the  5  * signal handler may change by the time it is  6  * unblocked.  7 */  8 if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))  9 return 0; 10 11 if (!sig_task_ignored(t, sig, from_ancestor_ns)) 12 return 0; 13 14 /* 15  * Tracers may want to know about even ignored signals. 16 */ 17 return !tracehook_consider_ignored_signal(t, sig); 18 }
复制代码

  首先判断信号是否被阻塞,阻塞和忽略是不同的概念,一旦取消阻塞,就可以发送信号,而被忽略的信号无法发送,被阻塞的信号无法被忽略,返回0;

  tracehook_consider_ignored_signal是判断进程是否被调试器追踪,被追踪的进程不能忽略收到的信号。

复制代码
 1 static int sig_task_ignored(struct task_struct *t, int sig,  2 int from_ancestor_ns)  3 {  4 void __user *handler;  5  6 handler = sig_handler(t, sig);  7  8 if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) &&  9 handler == SIG_DFL && !from_ancestor_ns) 10 return 1; 11 12 return sig_handler_ignored(handler, sig); 13 }
复制代码

  获取信号句柄,如果句柄是默认的且该进程是无法被杀死的(SIGNAL_UNKILLABLE),那么忽略该信号,这是为了避免出现进程执行出了问题时却杀不掉该进程的情况产生。

复制代码
1 static int sig_handler_ignored(void __user *handler, int sig) 2 { 3 /* Is it explicitly or implicitly ignored? */ 4 return handler == SIG_IGN || 5 (handler == SIG_DFL && sig_kernel_ignore(sig)); 6 }
复制代码

  如果句柄就是SIG_IGN,该信号显式被忽略;该信号属于SIG_KERNEL_IGNORE_MASK且信号动作是默认的话,就隐式忽略该信号;

  • 选择信号挂起队列

  根据入参group来选择是发送给单个进程或者是整个线程组

    pending = group ? &t->signal->shared_pending : &t->pending;
  • 添加到信号挂起队列
复制代码
 1 q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,  2  override_rlimit);  3 if (q)  4  {  5 list_add_tail(&q->list, &pending->list);  6 switch ((unsigned long) info)  7  {  8 case (unsigned long) SEND_SIG_NOINFO:  9 q->info.si_signo = sig; 10 q->info.si_errno = 0; 11 q->info.si_code = SI_USER; 12 q->info.si_pid = task_tgid_nr_ns(current, 13  task_active_pid_ns(t)); 14 q->info.si_uid = current_uid(); 15 break; 16 case (unsigned long) SEND_SIG_PRIV: 17 q->info.si_signo = sig; 18 q->info.si_errno = 0; 19 q->info.si_code = SI_KERNEL; 20 q->info.si_pid = 0; 21 q->info.si_uid = 0; 22 break; 23 default: 24 copy_siginfo(&q->info, info); 25 if (from_ancestor_ns) 26 q->info.si_pid = 0; 27 break; 28  } 29 } 
复制代码

  调用__sigqueue_alloc()分配一个新的信号队列成员sigqueue,首先增加用户的挂起信号个数,然后检测该用户挂起的信号是否超过了资源限制,如果没有就调用kmem_cache_alloc()分配新的sigqueue。

  如果分配成功,将其添加到信号挂起队列链表上,根据传入的info参数初始化sigqueue的info,传入的info参数是siginfo_t 变量地址或者如下三个特殊值:

  SEND_SIG_NOINFO : 表示信号由用户态进程发送; 
  SEND_SIG_PRIV : 表示信号由内核态( 进程) 发送;
  SEND_SIG_FORCED :
 表示信号由内核态( 进程) 发送, 并且信号是SIGKILL 或者SIGSTOP.

  同时调用sigaddset将信号添加到sigset_t掩码上。

  • complete_signal

  wants_signal()来判断是否能够发送信号给该进程,

    1.在以下情况下无法发送该信号给进程:

      a.信号被进程阻塞

                 b.进程正在退出

      c.进程处于暂停状态

    2.在以下情况下能够发送信号给进程:

      a.信号是SIGKILL,必须发送

      b.进程运行在当前CPU上

      c.进程当前没有挂起的信号

复制代码
 1 /*  2  * Now find a thread we can wake up to take the signal off the queue.  3  *  4  * If the main thread wants the signal, it gets first crack.  5  * Probably the least surprising to the average bear.  6 */  7 if (wants_signal(sig, p))  8 t = p;  9 else if (!group || thread_group_empty(p)) 10 /* 11  * There is just one thread and it does not need to be woken. 12  * It will dequeue unblocked signals before it runs again. 13 */ 14 return; 15 else { 16 /* 17  * Otherwise try to find a suitable thread. 18 */ 19 t = signal->curr_target; 20 while (!wants_signal(sig, t)) 21  { 22 t = next_thread(t); 23 if (t == signal->curr_target) 24 /* 25  * No thread needs to be woken. 26  * Any eligible threads will see 27  * the signal in the queue soon. 28 */ 29 return; 30  } 31 signal->curr_target = t; 32 }
复制代码

   该段代码是为了找到一个合适的能够接收信号的进程,首先判断目标进程p是否合适,如果不合适看该信号是否是发送给一个线程组并且该线程组不为空,这样就可以从线程组中的其他轻量级进程找到一个合适的能够接收信号的轻量级进程。找到的合适的能够接收信号的进程t跟目标进程p不一致的情况也不要紧,因为信号是挂起在shared_pending成员上的,最终处理信号的时候调用dequeue_signal还是可以捕捉到该信号的。

  接下来判断该信号是否是致命信号,如果是的话就添加SIGKILL信号给每一个轻量级进程,并且唤醒它来处理。如果不是致命信号就只唤醒那个合适的进程来处理信号。

复制代码
 1 void signal_wake_up(struct task_struct *t, int resume)  2 {  3 unsigned int mask;  4  5  set_tsk_thread_flag(t, TIF_SIGPENDING);  6  7 /*  8  * For SIGKILL, we want to wake it up in the stopped/traced/killable  9  * case. We don't check t->state here because there is a race with it 10  * executing another processor and just now entering stopped state. 11  * By using wake_up_state, we ensure the process will wake up and 12  * handle its death signal. 13 */ 14 mask = TASK_INTERRUPTIBLE; 15 if (resume) 16 mask |= TASK_WAKEKILL; 17 if (!wake_up_state(t, mask)) 18  kick_process(t); 19 }
复制代码

  调用signal_wake_up ( ) 来通知目标进程有新的信号达到. 这个函数执行如下步骤:
      a.
把标志TIF_SIGPENDING 加到thread_info->flags 中
      b.wake_up_state()
调用try_to_wake_up()来唤醒目标线程.
      c.wake_up_state()
返回1则目标线程处于runnable 状态, 否则检查目标线程是否在别的CPU 上执行,如果是则向该CPU 发送处理器中断以强制该cpu 重调度目标线程。 因为每一个进程在从schedule() 返回时都会检查是否存在挂起信号,所以这个处理器中断将会使目标线程很快就看到这个新的挂起信号.

阅读(1793) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~