治肾虚不含糖,专注内核性能优化二十年。 https://github.com/KnightKu
分类: C/C++
2013-02-20 09:33:56
内核signal_struct中notify_count变量的作用
在进程或者线程执行exec函数的时候,由于exec函数会影响整个进程组的内存结构,所以其他线程的存在是没有意义的,这时需要杀死线程组中其他的线程,也包括主线程,杀死线程的任务交给了信号SIGKILL,同时执行exec函数的进程需要被挂起,等待所有线程结束后,在重新唤醒执行exec的进程。这一系列的控制,在signal_struct中,变量notify_count和group_exit_task起到控制进程挂起和唤醒的作用。notify_count记录需要被结束的线程的个数,group_exit_task记录执行exec函数的进程,这个进程等待子线程结束的时候需要被挂起,每个线程在结束的时候,notify_count都会减一,在notify_count等于0的时候,表示线程都已经结束,group_exit_task需要被重新唤醒。继续执行exec函数后面的工作。
代码:fs/exec.c
828 sig->group_exit_task = tsk; //tsk就是当前线程或者进程
829 sig->notify_count = zap_other_threads(tsk); //notify_count就是线程组中线程的个数
830 if (!thread_group_leader(tsk)) //如果当前线程不是主线程,那么需要去除主线程
831 sig->notify_count--;
833 while (sig->notify_count) { //循环等待所有的子线程结束,不包括主线程
834 __set_current_state(TASK_UNINTERRUPTIBLE);
835 spin_unlock_irq(lock);
836 schedule();
837 spin_lock_irq(lock);
838 }
通过上面的代码可以看出group_exit_task就是调用exec函数的进程或者线程,zap_other_threads的作用是结束当前线程组中其他的线程,不包括自己,包括主线程。notify_count是需要结束线程的个数。由于线程组中的所有线程会共用一个signal_struct,那么这个notify_count在这个线程组中就是共用的。这里会判断如果当前task不是主线程,不用等待主线程退出。只等待其他线程退出即可,因为根本不确定当前task是否是当前线程组中的主线程,如果是的话,notify_count就不减一,等待notify_count等于零就表示所有的线程都已经退出。如果不是主线程,notify_count减一, 等待notify_count等于零就表示所有的线程不包括主线程都已经退出。在线程或者进程退出的时候会设置notify_count和判断它的值,如果notify_count等于0,表示所有的线程都已经结束了,这时候需要唤醒被挂起的group_exit_task。
代码:kernel/exit.c
79 static void __exit_signal(struct task_struct *tsk) // 进程结束的时候会调用这个函数
80 {
81 struct signal_struct *sig = tsk->signal;
82 bool group_dead = thread_group_leader(tsk); //判断tsk是否是主线程
83 struct sighand_struct *sighand;
84 struct tty_struct *uninitialized_var(tty);
85
86 sighand = rcu_dereference_check(tsk->sighand,
87 rcu_read_lock_held() ||
88 lockdep_tasklist_lock_is_held());
89 spin_lock(&sighand->siglock);
90
91 posix_cpu_timers_exit(tsk);
92 if (group_dead) {
93 posix_cpu_timers_exit_group(tsk);
94 tty = sig->tty;
95 sig->tty = NULL;
96 } else { //如果不是主线程
97 /*
98 * This can only happen if the caller is de_thread().
99 * FIXME: this is the temporary hack, we should teach
100 * posix-cpu-timers to handle this case correctly.
101 */
102 if (unlikely(has_group_leader_pid(tsk)))
103 posix_cpu_timers_exit_group(tsk);
104
105 /*
106 * If there is any task waiting for the group exit
107 * then notify it:
108 */
109 if (sig->notify_count > 0 && !--sig->notify_count) //如果notify_count大于0,那么notify_count减一,表示结束了一个线程,如果同时减一后如果notify_count为0,那么就表示线程组中所有的进程都已经结束了,那么是时候唤醒执行de_thread函数的那个进程了,就是group_exit_task
110 wake_up_process(sig->group_exit_task);
__exit_signal函数会在release_task中调用到,也就是释放进程的剩余内存结构的时候会改变notify_count的值,同时如果所有线程组中的其他线程都结束,会唤醒group_exit_task进程。至于一个线程在退出的时候并不会向父进程发送结束的信号SIGCHLD,而是选择直接结束,直接release_task,这个和exit_signal变量有关,这个变量的作用后面再讨论。
如果是线程组中的主线程退出,那么这个线程不会调用release_task来释放内存结构,同时调用de_thread函数的线程会将notify_count置为-1,这样主线程在退出的时候会看到这个变量,如果为-1,那么会唤醒那个调用de_thread函数的线程。
代码:fs/exec.c
846 if (!thread_group_leader(tsk)) {
847 struct task_struct *leader = tsk->group_leader;
848
849 sig->notify_count = -1; /* for exit_notify() */ //将notify_count置为-1
850 for (;;) { //等待主线程开始退出
851 write_lock_irq(&tasklist_lock);
852 if (likely(leader->exit_state)) //exit_state大于0表示线程已经开始退出
853 break;
854 __set_current_state(TASK_UNINTERRUPTIBLE);
855 write_unlock_irq(&tasklist_lock);
856 schedule();
857 }
de_thread函数会判断当前task是否是主线程,如果不是主线程,要等待主线程结束,如果主线程的exit_state大于0,表示主线程已经进入了退出流程了,如果小于0表示还没有退出,那么需要挂起当前线程来等待主线程结束。主线程会判断如果notify_count小于0,那么就会尝试唤醒那个被挂起的线程。
下面这段代码在exit_notify函数中
代码:fs/exec.c
860 /* mt-exec, de_thread() is waiting for group leader */
861 if (unlikely(tsk->signal->notify_count < 0))
862 wake_up_process(tsk->signal->group_exit_task);
总结:
在执行exec函数的时候,notify_count记录了需要被结束的线程的个数,从大于0变到0的时候,表示所有的线程都已经结束,这个时候去唤醒group_exit_task,使得exec函数继续执行,exit_signal变量的作用后面会一步一步的分析。以上分析代表个人观点,个人水平有限,不正确的地方希望大家指出,积极讨论。
参考文章: