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

全部博文(100)

文章存档

2015年(100)

我的朋友

分类: LINUX

2015-06-08 16:08:10

在分析内核对信号处理的代码,一直不太明白signal_structgroup_stop_count这个变量的作用,google下只发现了这个帖子http://tsecer.blog.163.com/blog/static/15018172011919103338913/,介绍了一下group_stop_count,大致看懂了一些。我说下对这个变量的理解,group_stop_count是记录当前线程组中未停止的线程的个数,在通过tkill系统调用的时候发送给某个线程停止信号(SIGSTOPSIGTSTPSIGTTINSIGTTOU),这会使得线程组中的所有线程停止。在进程fork线程的时候,如果flagCLONE_THREAD(复制线程),多个线程会共用一个signal_struct,如果tkill一个信号到某个线程,但是这个tkill会把信号发送到线程task_structpending中,这个字段是属于线程的私有信号结构。按照道理,当这个线程由于系统调用,异常,中断等返回用户态后,会处理线程中的信号,无论是私有的还是共有的信号都会处理。那些信号都是私有信号,那么就tkill对应的线程会,只有那一个线程会采取stop操作。

   但是实际上,内核中对停止信号(SIGSTOPSIGTSTPSIGTTINSIGTTOU),会采取特殊的处理方式,就是stop信号会使得线程组中其他线程将状态转换为TASK_STOPED,使得整个线程组停止。就是说一个线程的停止会导致整个线程组的停止。这个特性靠signal_structgroup_stop_count变量来实现,对于一个线程组来说每个线程的使用的都是同一个signal_struct,这个变量来记录,还有多少个线程还未stop。这样可以想到逻辑就是如果一个线程收到了SIGSTOP信号,group_stop_count的值就是线程组中的线程个数,同时这个线程会通过signal_wake_up来告诉线程组中的线程有信号需要处理,那么线程组中的每个线程都会调用信号处理函数,每个线程都会首先处理stop信号,判断group_stop_count如果大于0就代表当前线程需要被停止了,停止之前将group_stop_count减一,如果线程组中的线程都停止了,group_stop_count=0,那么就通知父进程告诉他子线程组停止了。

   首先线程在get_signal_to_deliver函数会处理信号,返回需要返回到用户空间的信号(返回用户空间的原因是进程设置了信号处理函数),这个函数会处理stop信号。

代码:kernel/signal.c

1936 if (sig_kernel_stop(signr)) {      //如果是stop类的信号(SIGSTOPSIGTSTPSIGTTINSIGTTOU

1937 /*

1938 * The default action is to stop all threads in

1939 * the thread group. The job control signals

1940 * do nothing in an orphaned pgrp, but SIGSTOP

1941 * always works. Note that siglock needs to be

1942 * dropped during the call to is_orphaned_pgrp()

1943 * because of lock ordering with tasklist_lock.

1944 * This allows an intervening SIGCONT to be posted.

1945 * We need to check for that and bail out if necessary.

1946 */                                            //注释也说了stop信号的默认行为就是停止线程组中的所有线程

1947     if (signr != SIGSTOP) {

1948          spin_unlock_irq(&sighand->siglock);

1949

1950 /* signals can be posted during this window */

1951

1952     if (is_current_pgrp_orphaned())

1953          goto relock;

1954

1955      spin_lock_irq(&sighand->siglock);

1956 }

1957

1958 if (likely(do_signal_stop(info->si_signo))) {    //处理退出信号

1959 /* It released the siglock. */

1960      goto relock;

1961 }


  实际停止线程的代码在 do_signal_stop函数中


1724 static int do_signal_stop(int signr)

1725 {

1726      struct signal_struct *sig = current->signal;

1727      int notify;

1728

1729     if (!sig->group_stop_count) {            //初始这个group_stop_count肯定是0

1730          struct task_struct *t;

1731                                                           //signal_struct中的flag,后面会专门的说,先说说这里 SIGNAL_STOP_DEQUEUED,这个标志位的意思是,从信号队列中已经dequeue了一个stop的信号,如果没有就没有后面的逻辑了

1732      if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) ||

1733          unlikely(signal_group_exit(sig)))

1734      return 0;

1735 /*

1736 * There is no group stop already in progress.

1737 * We must initiate one now.

1738 */

1739      sig->group_exit_code = signr;

1740

1741      sig->group_stop_count = 1;                    //初始这个值是1,代表当前线程需要被stop

1742     for (t = next_thread(current); t != current; t = next_thread(t)) //递归线程组

1743 /*

1744 * Setting state to TASK_STOPPED for a group

1745 * stop is always done with the siglock held,

1746 * so this check has no races.

1747 */

1748          if (!(t->flags & PF_EXITING) &&

1749              !task_is_stopped_or_traced(t)) {

1750                  sig->group_stop_count++;             //每个线程使得group_stop_count1

1751                  signal_wake_up(t, 0);                    //同时通知那个线程有信号需要处理,这个函数就是设置TIF_SIGPENDING,告诉进程有信号需要处理, 其实这个时候并没有所谓的stop信号

1752              }

1753          }

1754 /*

1755 * If there are no other threads in the group, or if there is

1756 * a group stop in progress and we are the last to stop, report

1757 * to the parent. When ptraced, every thread reports itself.

1758 */                                                                  //注释也告诉我们如果在线程组中如果都已经结束了,就通知父进程子进程都已经结束了

1759     notify = sig->group_stop_count == 1 ? CLD_STOPPED : 0; //等于1说明其他的线程都已经结束了,还差当前进程未结束。

1760      notify = tracehook_notify_jctl(notify, CLD_STOPPED); 

1761 /*

1762 * tracehook_notify_jctl() can drop and reacquire siglock, so

1763 * we keep ->group_stop_count != 0 before the call. If SIGCONT

1764 * or SIGKILL comes in between ->group_stop_count == 0.

1765 */

1766      if (sig->group_stop_count) {

1767          if (!--sig->group_stop_count)                   //这里就是如果原来group_stop_count1,减一后为0说明了线程组都已经结束,设置 signalflagSIGNAL_STOP_STOPPED,这些标志的作用后面在讲。

1768                 sig->flags = SIGNAL_STOP_STOPPED;

1769                 current->exit_code = sig->group_exit_code;

1770                  __set_current_state(TASK_STOPPED); //将当前线程状态改为停止

1771           }

1772      spin_unlock_irq(¤t->sighand->siglock);

1773

1774      if (notify) {                                             //如果都结束了通知父进程,这里父进程的意义是有可能是trace进程或者是真实的父进程,那么这里就是parantreal_parent的区别。parent表示有可能是其他进程attach到这个进程上的,所有real_parent指向原始的父进程,parent指向trace进程

1775         read_lock(&tasklist_lock);

1776          do_notify_parent_cldstop(current, notify);

1777          read_unlock(&tasklist_lock);

1778      }



tkill信号到指定线程后,调度线程返回用户态之前,会最终触发get_signal_to_deliver函数


1868      for (;;) {                                     //for循环处理信号,并且选择出返回用户态的信号

1869              struct k_sigaction *ka;

1870 /*

1871 * Tracing can induce an artifical signal and choose sigaction.

1872 * The return value in @signr determines the default action,

1873 * but @info->si_signo is the signal number we will report.

1874 */

1875              signr = tracehook_get_signal(current, regs, info, return_ka);

1876              if (unlikely(signr < 0))

1877                         goto relock;

1878              if (unlikely(signr != 0))

1879                          ka = return_ka;

1880             else {                                      //dequeue信号之前,会首先看是否当前线程是否需要停止,如果当前线程,属于tkill stop信号的线程组,那么这时group_stop_count就会大于0,那么就会执行 do_signal_stop函数,暂停线程的运行。

1881                      if (unlikely(signal->group_stop_count > 0) &&

1882                          do_signal_stop(0))

1883                                  goto relock;         //如果没有group stop,那么后面才去处理其他的信号,从公共或者私有信号挂起队列中处理信号

1885                          signr = dequeue_signal(current, ¤t->blocked,

1886                            info);


     还有一种情况会影响到group_stop_count,在计算group_stop_count的时候,计算的未结束的线程,那么线程在这之后开始退出,那么也应该减少group_stop_count的值。group_stop_count=0的时候那么也应该通知父进程,说明子线程已经停止了,虽然有线程已经结束了。后面这段代码是函数exit_signals函数中的逻辑,这段函数在进程退出(do_exit)的时候会调用到。


代码:kernel/exit.c

2028     if (unlikely(tsk->signal->group_stop_count) &&

2029      !--tsk->signal->group_stop_count) {             //同时也让group_stop_count1,在等于0的时候也标志了signal_structflag标志位。

2030             tsk->signal->flags = SIGNAL_STOP_STOPPED;

2031             group_stop = tracehook_notify_jctl(CLD_STOPPED, CLD_STOPPED);

2032      }

2033     out:

2034     spin_unlock_irq(&tsk->sighand->siglock);

2035                                                                       //同时全部结束了,需要通知父进程子进程的状态有变化

2036      if (unlikely(group_stop)) {

2037      read_lock(&tasklist_lock);

2038      do_notify_parent_cldstop(tsk, group_stop);

2039      read_unlock(&tasklist_lock);

2040 } 


     如果在group stop的过程中,或者get_signal_to_deliver函数已经从信号挂起队列中已经dequeue出了一个stop信号,线程组中的一个线程收到了SIGCONT信号,意味着线程组不用在继续stop了,这时需要将gourp_stop_count变为0。不让线程组再继续stop了,这里前面讲到的signal_struct标志位的SIGNAL_STOP_DEQUEUED,就起到了作用,在dequeuestop信号的时候,会加上这个标志位。在执行do_signal_stop的之前,也就是第一个stop的进程处理的时候,也会判断这个SIGNAL_STOP_DEQUEUED标志位,这时如果收到了SIGCONT信号,清除标志位SIGNAL_STOP_DEQUEUED是有必要了,起到了避免了收到了SIGCONT,而继续停止第一个线程的作用。下面的代码是进程收到了SIGCONT信号的代码:


753      why = 0;

754      if (signal->flags & SIGNAL_STOP_STOPPED)       //说明所有的线程都已经停止了

755      why |= SIGNAL_CLD_CONTINUED;

756      else if (signal->group_stop_count) //group_stop_count>0说明group stop还在进行中

757      why |= SIGNAL_CLD_STOPPED;

758

759     if (why) {                                    //满足上面那两中情况,就是说已经开始group stop

760 /*

761 * The first thread which returns from do_signal_stop()

762 * will take ->siglock, notice SIGNAL_CLD_MASK, and

763 * notify its parent. See get_signal_to_deliver().

764 */

765          signal->flags = why | SIGNAL_STOP_CONTINUED; //这标志在group stop停止的过程中重新开始执行了

766          signal->group_stop_count = 0; //那么就停止group stop

767          signal->group_exit_code = 0;

768      } else {                                    //没有group stop 或者刚刚处理第一个线程的stop信号

769 /*

770 * We are not stopped, but there could be a stop

771 * signal in the middle of being processed after

772 * being removed from the queue. Clear that too.

773 */                                                //如果这个时候dequeue处理第一个stop信号准备处理,恰好这是SIGCONT信号来了,那么内核会忽略掉那个刚刚被dequeue出来的stop信号

774          signal->flags &= ~SIGNAL_STOP_DEQUEUED;

775 } 


     那么get_signal_to_deliver函数首先会判断是否当前进程是由group stop那个过程到继续执行的过程,如果是的话,就应该通知父进程(trace的进程或者真正的父进程),子进程的状态发生了改变。



1854    if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {         //判断是否是从group_stop 到 continue

1853          int why = (signal->flags & SIGNAL_STOP_CONTINUED)

1854          ? CLD_CONTINUED : CLD_STOPPED;

1855          signal->flags &= ~SIGNAL_CLD_MASK;                 //清除标志位

1856

1857          why = tracehook_notify_jctl(why, CLD_CONTINUED);

1858          spin_unlock_irq(&sighand->siglock);

1859

1860          if (why) {

1861              read_lock(&tasklist_lock);

1862              do_notify_parent_cldstop(current->group_leader, why);      //通知父进程,子进程发生了变化

1863              read_unlock(&tasklist_lock);

1864          }

1865          goto relock;

1866 } 



总结:  

      stop类的信号是对整个线程组起作用的,一个线程收到了stop信号会迫使线程组中其它的线程也停止,如果在这停止线程组过程中收到了SIGCONT信号,那么group stop会立刻停止,使得线程组继续执行,已经停止的进程应该是没有办法通过一个SIGCONT信号来唤醒已经停止的其他线程了,只能tkill每一个线程,发送SIGCONT信号,来唤醒停止的线程。

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