2.6.1 线程组退出
系统调用exit_group实现线程组退出,执行流程如图2.17所示,把主要工作委托给函数do_group_exit,执行流程如下。
图2.17 线程组退出的执行流程
(1)如果线程组正在退出,那么从信号结构体的成员group_exit_code取出退出码。
(2)如果线程组未处于正在退出的状态,并且线程组至少有两个线程,那么处理如下。
1)关中断并申请锁。
2)如果线程组正在退出,那么从信号结构体的成员group_exit_code取出退出码。
3)如果线程组未处于正在退出的状态,那么处理如下。
❑ 把退出码保存在信号结构体的成员group_exit_code中,传递给其他线程。
❑ 给线程组设置正在退出的标志。
❑ 向线程组的其他线程发送杀死信号,然后唤醒线程,让线程处理杀死信号。
4)释放锁并开中断。
(3)当前线程调用函数do_exit以退出。
假设一个线程组有两个线程,称为线程1和线程2,线程1调用exit_group使线程组退出,线程1的执行过程如下。
(1)把退出码保存在信号结构体的成员group_exit_code中,传递给线程2。
(2)给线程组设置正在退出的标志。
(3)向线程2发送杀死信号,然后唤醒线程2,让线程2处理杀死信号。
(4)线程1调用函数do_exit以退出。
线程2退出的执行流程如图2.18所示,线程2准备返回用户模式的时候,发现收到了杀死信号,于是处理杀死信号,调用函数do_group_exit,函数do_group_exit的执行过程如下。
图2.18 线程2退出的执行流程
(1)因为线程组处于正在退出的状态,所以线程2从信号结构体的成员group_exit_code取出退出码。
(2)线程2调用函数do_exit以退出。
线程2可能在以下3种情况下准备返回用户模式。
(1)执行完系统调用。
(2)被中断抢占,中断处理程序执行完。
(3)执行指令时生成异常,异常处理程序执行完。
函数do_exit的执行过程如下。
(1)释放各种资源,把资源对应的数据结构的引用计数减一,如果引用计数变成0,那么释放数据结构。
(2)调用函数exit_notify,先为成为“孤儿”的子进程选择“领养者”,然后把自己的死讯通知父进程。
(3)把进程状态设置为死亡(TASK_DEAD)。
(4)最后一次调用函数__schedule以调度进程。
死亡进程最后一次调用函数__schedule调度进程时,进程调度器做了如下特殊处理。
kernel/sched/core.c
__schedule() -> context_switch() -> finish_task_switch()
1 static struct rq *finish_task_switch(struct task_struct *prev)
2 __releases(rq->lock)
3 {
4 …
5 prev_state = prev->state;
6 …
7 if (unlikely(prev_state == TASK_DEAD)) {
8 if (prev->sched_class->task_dead)
9 prev->sched_class->task_dead(prev);
10 …
11 put_task_stack(prev);
12 put_task_struct(prev);
13 }
14 …
15 }
第8行和第9行代码,执行调度类的task_dead方法。
第11行代码,如果结构体thread_info放在进程描述符里面,而不是放在内核栈的顶部,那么释放进程的内核栈。
第12行代码,把进程描述符的引用计数减1,如果引用计数变为0,那么释放进程描述符。