java开发工程师,专注于内核源码,算法,数据结构。 qq:630501400
分类: C/C++
2013-02-23 17:57:09
在分析内核对信号处理的代码,一直不太明白signal_struct中group_stop_count这个变量的作用,google下只发现了这个帖子http://tsecer.blog.163.com/blog/static/15018172011919103338913/,介绍了一下group_stop_count,大致看懂了一些。我说下对这个变量的理解,group_stop_count是记录当前线程组中未停止的线程的个数,在通过tkill系统调用的时候发送给某个线程停止信号(SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU),这会使得线程组中的所有线程停止。在进程fork线程的时候,如果flag是CLONE_THREAD(复制线程),多个线程会共用一个signal_struct,如果tkill一个信号到某个线程,但是这个tkill会把信号发送到线程task_struct的pending中,这个字段是属于线程的私有信号结构。按照道理,当这个线程由于系统调用,异常,中断等返回用户态后,会处理线程中的信号,无论是私有的还是共有的信号都会处理。那些信号都是私有信号,那么就tkill对应的线程会,只有那一个线程会采取stop操作。
但是实际上,内核中对停止信号(SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU),会采取特殊的处理方式,就是stop信号会使得线程组中其他线程将状态转换为TASK_STOPED,使得整个线程组停止。就是说一个线程的停止会导致整个线程组的停止。这个特性靠signal_struct的group_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类的信号(SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU)
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_count加1
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_count为1,减一后为0说明了线程组都已经结束,设置 signal的flag为SIGNAL_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进程或者是真实的父进程,那么这里就是parant和real_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_count减1,在等于0的时候也标志了signal_struct的flag标志位。
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,就起到了作用,在dequeue出stop信号的时候,会加上这个标志位。在执行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信号,来唤醒停止的线程。