Linux2.6核心實現的是NPTL執行緒模式,依然是用進程來類比線程,但新引入了線程組(進程組)的概念,使得實現效率更好。
在2.4核心中,不存線上程組的概念,當運行一個多線程得程式時,使用ps命令,可以看到有許多個進程,在ps命令看來,線程基本上是等同於進程,在訊號處理中,情況也是如此,只有指定進程號的線程,可以接收到訊號。在2.6核心中引入了線程組的概念,在2.6核心中,如果使用ps命令看,一個多線程的進程,只會顯示一個進程,在給線程組中的任何一個線程發送訊號的時候,整個線程組中的進程都能收到訊號。
在核心task_struct中相關欄位如下(位於include/linux/sched.h):
代碼: 全選
937 struct task_struct {
...
993 pid_t pid;
994 pid_t tgid;
...
1013 struct task_struct
*group_leader; /* threadgroup leader */
...
1017 struct list_head
thread_group;
...
1198 };
pid,從字面上是process id,但其實是thread id。
tgid,從字面上,應該是thread group id,也就是真正的process id。
這一點,可以從系統調用getpid和gettid中看出來(位於kernel/timer.c)。
代碼: 全選
954 asmlinkage long sys_getpid(void)
955 {
956 return
current->tgid;
957 }
1100 asmlinkage long sys_gettid(void)
1101 {
1102 return
current->pid;
1103 }
group_leader欄位,指向線程組中的第一個線程,建立第一個線程的時候,group_leader指向自己,建立其後的線程時,指向第一個線程的task_struct結構;
thread_group,當前進程所有線程的隊列,對於group_leader,這是個隊列頭,對於其後的進程而言,通過這個欄位,掛入隊列中,可以通過此隊列,遍曆所有線程。
線程組中各個線程的關係,是在do_fork中設定的,具體的代碼在copy_process中(位於kernel/fork.c):代碼: 全選
959 copy_process()
960 {
...
1112 p->tgid =
p->pid;
1113 if (clone_flags &
CLONE_THREAD)
1114
p->tgid = current->tgid;
...
1181 p->group_leader =
p;
1182
INIT_LIST_HEAD(&p->thread_group);
...
1234 if (clone_flags &
CLONE_THREAD) {
1235
p->group_leader = current->group_leader;
1236
list_add_tail_rcu(&p->thread_group,
&p->group_leader->thread_group);
...
1252 }
1254 if (likely(p->pid))
{
...
1259 if
(thread_group_leader(p)) {
...
1266
list_add_tail_rcu(&p->tasks, &init_task.tasks);
...
1268 }
...
1271 }
...
1320 }
1113-1114行說明在建立線程時,從父進程擷取tgid,表明他們在同一個線程組中;1181-1182則對group_leader和thread_group初始化,對於第一個線程,則group_leader就是它自己;1234-1236行,將新建立的線程的group_leader設定成為父進程得group_leader,無論父進程是不是線程組中的第一個線程,它的group_leader都是指向第一個線程的task_struct,同時通過thread_group欄位,掛入到第一個線程的thread_group隊列中;1266行表明只有線程組中的第一個線程,才會通過tasks欄位,掛入到init_task隊列中。
在引入線程組概念後,退出部分也引入了一個新的系統調用exit_group(位於kernel/exit.c)
1055 NORET_TYPE void
1056 do_group_exit(int exit_code)
1057 {
1058 BUG_ON(exit_code &
0x80); /* core dumps don't get here */
1059
1060 if
(current->signal->flags & SIGNAL_GROUP_EXIT)
1061 exit_code =
current->signal->group_exit_code;
1062 else if
(!thread_group_empty(current)) {
1063 struct
signal_struct *const sig = current->signal;
1064 struct
sighand_struct *const sighand = current->sighand;
1065
spin_lock_irq(&sighand->siglock);
1066 if
(sig->flags & SIGNAL_GROUP_EXIT)
1067
/* Another thread got here before we took the lock. */
1068
exit_code = sig->group_exit_code;
1069 else {
1070
sig->group_exit_code = exit_code;
1071
zap_other_threads(current);
1072 }
1073
spin_unlock_irq(&sighand->siglock);
1074 }
1075
1076 do_exit(exit_code);
1077 /* NOTREACHED */
1078 }
在1060行中,current->signal其實是線程組中所有線程共用的,對於調用exit_group的那個線程,如果是一個多線程的進程,就會進入1062-1074這部分代碼,如果是單線程,則直接進入do_exit退出進程。這部分代碼的主要操作在zap_other_threads中(位於kernel/signal.c)
void zap_other_threads(struct task_struct *p)
982 {
983 struct task_struct
*t;
984
985
p->signal->flags = SIGNAL_GROUP_EXIT;
986
p->signal->group_stop_count = 0;
987
988 if
(thread_group_empty(p))
989 return;
990
991 for (t =
next_thread(p); t != p; t = next_thread(t)) {
992 /*
993 * Don't
bother with already dead threads
994 */
995 if
(t->exit_state)
996
continue;
997
998 /*
SIGKILL will be handled before any pending SIGSTOP */
999
sigaddset(&t->pending.signal, SIGKILL);
1000
signal_wake_up(t, 1);
1001 }
1002 }
next_thread定義在include/linux/sched.h中,如下
651 static inline struct task_struct *next_thread(const struct
task_struct *p)
1652 {
1653 return
list_entry(rcu_dereference(p->thread_group.next),
1654
struct task_struct, thread_group);
1655 }
其實就是通過task_struct中的thread_group隊列來遍曆線程組中的所有線程。
在其中,會在signal->flags中設定SIGNAL_GROUP_EXIT,同時,搜尋線程組中所有進程,在每個線程中掛上一個SIGKILL訊號,這樣,當那些線程調度到啟動並執行時候,就會處理SIGKILL訊號,對於SIGKILL訊號的處理,會調用do_group_exit,不過,當這次調用到do_group_exit的時候,將運行到1061行,然後就到了1076行的do_exit。這樣,當線程組中的每個線程都運行過一遍後,整個線程組就退出了。