Linux内核深度解析
上QQ阅读APP看书,第一时间看更新

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,那么释放进程描述符。