Bootstrap

linux进程调度(三)-进程终止

2.3 进程退出的几种情况

进程的退出分两种情况:进程主动退出和被动退出。主动退出是指进程已经执行完毕,主动调用exit退出或者main函数执行完毕,编译器会总添加exit函数。被动退出是指系统收到SIGKILL信号、不能处理的信号或者在内核态执行异常导致进程被动的结束。当一个进程终止时,Linux内核会释放它所占有的资源,并把这个消息告知父进程。
当进程退出的时候,根据父进程是否关注子进程退出事件,存在两种可能:第一种,父进程关注子进程的推出事件,那么进程退出时释放各种资源,只留下一个空的进程描述符,变成僵尸进程,发送信号 SIGCHLD通知父进程,父进程在查询进程终止的原因以后回收子进程的进程描述符。第二种,如果父进程不关注子进程退出事件,那么进程退出时释放各种资源,释放进程描述符,自动消失。进程默认关注子进程退出事件的,如果不想关注,需要使用系统调用设置忽略信号SIGCHLD。
进程的主动退出是通过exit函数进行exit的系统调用。

1.SYSCALL_DEFINE1(exit, int, error_code)  
2.{  
3.        do_exit((error_code&0xff)<<8);  
4.}  

2.4 进程终止过程分析

1.void __noreturn do_exit(long code)  
2.{  
3.    struct task_struct *tsk = current;  
4.    int group_dead;  
5....  
6.    force_uaccess_begin();//强制用户进程可以访问此进程的内容  
7.    //对所有注册的监听器发送进程退出的事件通知,以便它们对这个事件做出处理  
8.    profile_task_exit(tsk);  
9.    //如果进程已经设置了PF_EXITING,表示出现了递归错误  
10.    if (unlikely(tsk->flags & PF_EXITING)) {  
11.        pr_alert("Fixing recursive fault but reboot is needed!\n");  
12.        futex_exit_recursive(tsk);//用户空间锁递归解锁  
13.        set_current_state(TASK_UNINTERRUPTIBLE);//设置进程状态为不可中断的睡眠  
14.        schedule();//主动调度出去  
15.    }  
16.    //取消正在等待事件完成的 io_uring 请求  
17.    io_uring_files_cancel(tsk->files);  
18.    //进程flage设置PF_EXITING,处理pending信号  
19.   //用于向用户空间发送有关进程退出的任务统计信息
20.    exit_signals(tsk);  /* sets PF_EXITING */  
21....  
22.    if (tsk->mm)  
23.        sync_mm_rss(tsk->mm);  
24.      
25.    audit_free(tsk);  
26.  
27.    tsk->exit_code = code;//填写进程的退出代码  
28.    taskstats_exit(tsk, group_dead);  
29.  
30.    exit_mm();//释放mm_struct  
31.  
32.    exit_sem(tsk);//释放进程的信号量,sysvsem  
33.    exit_shm(tsk);//释放进程的共享内存,sysvshm  
34.    exit_files(tsk);//释放进程的文件,files_struct  
35.    exit_fs(tsk);//释放进程的的文件系统,fs_struct  
36....  
37.    exit_task_namespaces(tsk);//释放进程的命名空间  
38.    exit_task_work(tsk);//释放进程的工作队列  
39.    cgroup_exit(tsk);//释放cgroup相关  
40.  
41.    //为自己的子进程找一个父亲,然后把自己的死讯通知父进程  
42.    exit_notify(tsk, group_dead);  
43.  
44.    if (tsk->io_context)  
45.        exit_io_context(tsk);//释放io_context  
46.  
47.    if (tsk->splice_pipe)//释放splice_pipe  
48.        free_pipe_info(tsk->splice_pipe);  
49.  
50.    if (tsk->task_frag.page)  
51.        put_page(tsk->task_frag.page);//释放task_frag  
52....  
53.    do_task_dead();//进行死亡的处理  
54.}  
55.EXPORT_SYMBOL_GPL(do_exit);  

我们看到do_exit函数主要做了一下几件事:

  1. 调用函数force_uaccess_begin强制用户进程可以访问此进程的内容;
  2. 调用函数profile_task_exit对所有注册的监听器发送进程退出的事件通知,以便它们对这个事件做出处理;
  3. 判断进程的PF_EXITING标志位是否已经置位,如果是,说明进程出现了递归处理退出的问题,需要递归解锁用户空间的锁,设置进程状态为不可中断的睡眠,最后调用函数schedule主动调度出去,后面函数不再执行。因为这种情况最安全的做法是不再处理,等待重启;
  4. 调用函数io_uring_files_cancel取消正在等待事件完成的 io_uring 请求;
  5. 调用函数exit_signals进程flage设置PF_EXITING,处理pending信号;
  6. 把进程的退出码写入进程描述符的exit_code中;
  7. 调用函数taskstats_exit用于向用户空间发送有关进程退出的任务统计信息;
  8. 调用一系列的函数释放进程的资源,比如内存、信号量、共享内存、打开的文件、文件系统相关、命名空间相关、工作队列相关、cgroup相关等等;
  9. 调用函数exit_notify为自己的子进程找一个父亲,然后把自己的死讯通知父进程;
  10. 释放io_context、splice_pipe、task_frag等资源;
  11. 调用函数do_task_dead进行进程的死亡处理。
    我们需要更详细的分析一下exit_notify和do_task_dead函数。

2.4.1 exit_notify函数

1.static void exit_notify(struct task_struct *tsk, int group_dead)  
2.{  
3.    bool autoreap;  
4.    struct task_struct *p, *n;  
5.    LIST_HEAD(dead);  
6.  
7.    write_lock_irq(&tasklist_lock);  
8.    //给所有子进程找一个parent  
9.    forget_original_parent(tsk, &dead);  
10.  
11.    if (group_dead)  
12.        //如果有进程停止运行,给他们发送信号让他们继续运行  
13.        kill_orphaned_pgrp(tsk->group_leader, NULL);  
14.  
15.    tsk->exit_state = EXIT_ZOMBIE;//设置进程的退出状态为僵尸  
16.    //如果有进程在跟踪此退出的进程  
17.    if (unlikely(tsk->ptrace)) {  
18.        int sig = thread_group_leader(tsk) &&  
19.                thread_group_empty(tsk) &&  
20.                !ptrace_reparented(tsk) ?  
21.            tsk->exit_signal : SIGCHLD;  
22.        //发信号通知父进程  
23.        autoreap = do_notify_parent(tsk, sig);  
24.    //如果此进程是线程组的leader  
25.    } else if (thread_group_leader(tsk)) {  
26.        //如果没有其他线程  
27.        autoreap = thread_group_empty(tsk) &&  
28.            //发信号通知父进程  
29.            do_notify_parent(tsk, tsk->exit_signal);  
30.    } else {  
31.        autoreap = true;  
32.    }  
33.      
34.    if (autoreap) {//如果没有父进程关注子进程情况  
35.        tsk->exit_state = EXIT_DEAD;//设置进程状态为死亡  
36.        //将ptrace_entry节点添加到dead 链表的头部,释放进程描述符内存  
37.        list_add(&tsk->ptrace_entry, &dead);  
38.    }  
39.  
40.    //当前进程等待的信号数量超出了其支持的范围,这是一个少有的错误  
41.    if (unlikely(tsk->signal->notify_count < 0))  
42.        //唤醒group_exit_task内核线程来退出进程组,并释放相关资源  
43.        wake_up_process(tsk->signal->group_exit_task);  
44.    write_unlock_irq(&tasklist_lock);  
45.  
46.    //遍历dead链表的每一个节点,把每一个节点删除并且释放对应的进程描述符  
47.    list_for_each_entry_safe(p, n, &dead, ptrace_entry) {  
48.        list_del_init(&p->ptrace_entry);  
49.        release_task(p);  
50.    }  
51.}  

exit_notify函数主要做了以下几件事:

  1. 调用函数forget_original_parent给所有子进程找一个parent;
  2. 如果是主线程死亡,则调用函数kill_orphaned_pgrp遍历所有子进程,如果存在停止工作的情况,则给这子进程发送SIGHUP和SIGCONT信号,让他们继续运行;
  3. 设置进程的退出状态为僵尸;
  4. 如果有进程在跟踪此退出的进程或者此线程是主线程,则调用函数do_notify_parent发信号通知父进程;
  5. 如果没有父进程关注子进程情况,设置进程状态为死亡,然后将ptrace_entry节点添加到dead 链表的头部,后面的会处理dead的;
  6. 当前进程等待的信号数量超出了其支持的范围,这是一个少有的错误,则唤醒group_exit_task内核线程来退出进程组,并释放相关资源;
  7. 遍历dead链表的每一个节点,把每一个节点删除并且释放对应的进程资源。
    我们继续看看forget_original_parent、do_notify_parent和release_task函数。
2.4.1.1 forget_original_parent函数
1.static void forget_original_parent(struct task_struct *father,  
2.                    struct list_head *dead)  
3.{  
4.    struct task_struct *p, *t, *reaper;  
5.  
6.    //如果father进程的ptraced成员不为空  
7.    if (unlikely(!list_empty(&father->ptraced)))  
8.        //清空ptrace的所有任务  
9.        exit_ptrace(father, dead);  
10.  
11.    //给子进程找一个进程回收器  
12.    reaper = find_child_reaper(father, dead);  
13.    //如果子进程链表是空的,说明没有子进程  
14.    if (list_empty(&father->children))  
15.        return;//没有子进程就不需要帮它找一个parent  
16.    //找一个可以最适合收养孤儿的进程  
17.    reaper = find_new_reaper(father, reaper);  
18.    //遍历father的每一个子进程  
19.    list_for_each_entry(p, &father->children, sibling) {  
20.        for_each_thread(p, t) {//遍历子进程的每一个线程  
21.            //使用rcu同步更新进程的real_parent  
22.            RCU_INIT_POINTER(t->real_parent, reaper);  
23.            BUG_ON((!t->ptrace) != (rcu_access_pointer(t->parent) == father));  
24.            if (likely(!t->ptrace))//子进程没有被跟踪  
25.                t->parent = t->real_parent;//设置parent  
26.            if (t->pdeath_signal)//如果当前进程的父进程退出了,给子进程发送了信号  
27.                //向进程组的所有进程发送pdeath_signal信号  
28.                group_send_sig_info(t->pdeath_signal,  
29.                            SEND_SIG_NOINFO, t,  
30.                            PIDTYPE_TGID);  
31.        }  
32.  
33.        //如果reaper和father不在同一个线程组  
34.        if (!same_thread_group(reaper, father))  
35.            //修改当前进程的父进程为指定的进程,以确保子进程能够正常继承新的父进程  
36.            reparent_leader(father, p, dead);  
37.    }  
38.    //把进程的children链表合并到reaper的children链表中  
39.    list_splice_tail_init(&father->children, &reaper->children);  
40.}  

forget_original_parent函数主要做了一下几件事:

  1. 如果father进程的ptraced成员不为空,清空ptrace的所有任务;
  2. 调用函数find_child_reaper给子进程找一个进程回收器;进程回收器可以回收收养孤儿进程;
  3. 如果子进程链表是空的,说明没有子进程,没有子进程就不需要帮它找一个parent,直接返回;
  4. 调用函数find_new_reaper找一个可以最适合收养孤儿的进程;
  5. 遍历father的每一个子进程,在这个大循环中遍历子进程的每一个线程,在这个小循环调用函数RCU_INIT_POINTER使用rcu同步更新进程的real_parent,如果子进程没有被跟踪,设置子进程的parent,如果当前进程的父进程退出了,向进程组的所有进程发送pdeath_signal信号,到这里小循环介绍了;回到大循环中,调用函数same_thread_group判断如果reaper和father不在同一个线程组;调用函数reparent_leader修改当前进程的父进程为指定的进程,以确保子进程能够正常继承新的父进程;
  6. 调用函数list_splice_tail_init把进程的children链表合并到reaper的children链表中。
2.4.1.1.1 find_child_reaper函数

我们可以继续看看find_child_reaper函数是怎么给子进程找一个进程回收器的:

1.static struct task_struct *find_child_reaper(struct task_struct *father,  
2.                        struct list_head *dead)  
3.    __releases(&tasklist_lock)  
4.    __acquires(&tasklist_lock)  
5.{  
6.    //找到进程的pid命名空间  
7.    struct pid_namespace *pid_ns = task_active_pid_ns(father);  
8.    //找到进程回收器,他是可以回收该PID命名空间内的所有孤儿进程  
9.    struct task_struct *reaper = pid_ns->child_reaper;  
10.    struct task_struct *p, *n;  
11.  
12.    //如果father进程不是进程回收器  
13.    if (likely(reaper != father))  
14.        return reaper;//直接返回进程回收器  
15.  
16.    //在father进程中找一个活着的线程  
17.    reaper = find_alive_thread(father);  
18.    if (reaper) {//找到了  
19.        //设置它pid命名空间的进程回收器  
20.        pid_ns->child_reaper = reaper;  
21.        return reaper;//返回进程回收器  
22.    }  
23.  
24.    write_unlock_irq(&tasklist_lock);  
25.    //遍历dead链表的每一个节点,  
26.    list_for_each_entry_safe(p, n, dead, ptrace_entry) {  
27.        list_del_init(&p->ptrace_entry);//删除节点  
28.        //释放对应的进程内存资源(文件、信号、页表、堆栈、锁等)  
29.        release_task(p);  
30.    }  
31.  
32.    zap_pid_ns_processes(pid_ns);//终止指定PID命名空间中的所有进程  
33.    write_lock_irq(&tasklist_lock);  
34.  
35.    return father;  
36.}  

find_child_reaper函数主要是做了以下几件事:

  1. 调用函数task_active_pid_ns找到进程的pid命名空间;
  2. 找到进程回收器,他是可以回收该PID命名空间内的所有孤儿进程;
  3. 如果father进程不是进程回收器,直接返回我们找到的进程回收器;
  4. 调用函数find_alive_thread在father进程中找一个活着的线程,如果找到了,把这个线程设置为这个pid命令空间的进程回收器,然后返回这个进程回收器;
  5. 到这里说明肯定找不到了,遍历dead链表的每一个节点,删除节点,释放对应的进程内存资源;
  6. 调用函数zap_pid_ns_processes终止指定PID命名空间中的所有进程,因为找不到内存回收器来收养这些进程了;
  7. 返回father进程。
    其实进程回收器一般是init_task进程,也就是我们说的init进程。根pid命名空间定义在kernel/pid.c文件中:
1.struct pid_namespace init_pid_ns = {  
2.        .kref = KREF_INIT(2),  
3.        .idr = IDR_INIT(init_pid_ns.idr),  
4.        .pid_allocated = PIDNS_ADDING,  
5.        .level = 0,  
6.        .child_reaper = &init_task,  
7.        .user_ns = &init_user_ns,  
8.        .ns.inum = PROC_PID_INIT_INO,  
9.#ifdef CONFIG_PID_NS  
10.        .ns.ops = &pidns_operations,  
11.#endif  
12.};  
13.EXPORT_SYMBOL_GPL(init_pid_ns);  

我们看到根pid命名空间的child_reaper成员就是init_task进程。

2.4.1.1.2 find_new_reaper函数
1.static struct task_struct *find_new_reaper(struct task_struct *father,  
2.                       struct task_struct *child_reaper)  
3.{  
4.    struct task_struct *thread, *reaper;  
5.    //在father进程中找一个活着的线程  
6.    thread = find_alive_thread(father);  
7.    if (thread)//找到了  
8.        return thread;//返回这个线程  
9.  
10.    if (father->signal->has_child_subreaper) {  
11.        //获取进程的pid命名空间的级别  
12.        unsigned int ns_level = task_pid(father)->level;  
13.      
14.        //找同级别的pid命名空间的祖先进程  
15.        for (reaper = father->real_parent;  
16.             task_pid(reaper)->level == ns_level;  
17.             reaper = reaper->real_parent) {  
18.            //如果找到了init进程,退出循环  
19.            if (reaper == &init_task)  
20.                break;  
21.            //如果该祖先进程不具备接管孤儿进程的能力  
22.            if (!reaper->signal->is_child_subreaper)  
23.                continue;  
24.            //在该祖先进程下找一个线程  
25.            thread = find_alive_thread(reaper);  
26.            if (thread)//找到了  
27.                return thread;//返回这个线程  
28.        }  
29.    }  
30.      
31.    return child_reaper;//返回进程回收器  
32.}  

find_new_reaper函数主要做了以下几件事:

  1. 调用函数find_alive_thread在father进程中找一个活着的线程,如果找到了直接返回这个线程,因为这个线程跟我们的父进程是同一个线程组,共享很多资源,是最适合收养的存在了;
  2. 如果进程具备接管孤儿进程的能力,获取进程的pid命名空间的级别,使用for循环找同级别的pid命名空间的祖先进程,如果找到了init进程,退出循环;如果该祖先进程不具备接管孤儿进程的能力,在往上找上一级的祖先;如果该祖先进程不具备接管孤儿进程的能力,调用函数find_alive_thread在该祖先进程下找一个线程,找到了就返回这个线程;这是从父进程开始往上找,寻找接管孤儿进程的能力的祖先;
  3. 到这里说明祖先进程也不靠谱,只能返回进程回收器,也就是init进程了。
2.4.1.1.3 reparent_leader函数
1.static void reparent_leader(struct task_struct *father, struct task_struct *p,  
2.                struct list_head *dead)  
3.{     
4.    //如果子进程的推迟状态是死亡  
5.    if (unlikely(p->exit_state == EXIT_DEAD))  
6.        return;//直接返回  
7.  
8.    /* We don't want people slaying init. */  
9.    p->exit_signal = SIGCHLD;//设置exit_signal  
10.  
11.    //如果进程没有被跟踪,其状态是僵尸,没有其他线程  
12.    if (!p->ptrace &&  
13.        p->exit_state == EXIT_ZOMBIE && thread_group_empty(p)) {  
14.        //发信号通知父进程  
15.        if (do_notify_parent(p, p->exit_signal)) {  
16.            p->exit_state = EXIT_DEAD;//设置父进程的退出状态为死亡  
17.            //将ptrace_entry节点添加到dead 链表的头部,释放进程描述符内存  
18.            list_add(&p->ptrace_entry, dead);  
19.        }  
20.    }  
21.    //如果有进程停止运行,给他们发送信号让他们继续运行  
22.    kill_orphaned_pgrp(p, father);  
23.}  

reparent_leader函数主要做了几件事:

  1. 如果子进程的退出状态是死亡,直接返回;
  2. 设置exit_signal为SIGCHLD;
  3. 如果进程没有被跟踪,进程的状态是僵尸而且进程没有其他线程,那么调用函数do_notify_parent发信号通知父进程,根据返回值,如果父进程没有关注子进程的退出状态,设置父进程的退出状态为死亡,然后将ptrace_entry节点添加到dead 链表的头部,释放进程描述符内存;
  4. 到这里说明子进程是正常运行的,调用函数kill_orphaned_pgrp检测是否有进程停止运行,如果有则给他们发送信号让他们继续运行。
2.4.1.2 do_notify_parent函数
1.bool do_notify_parent(struct task_struct *tsk, int sig)  
2.{  
3.    struct kernel_siginfo info;  
4.    unsigned long flags;  
5.    struct sighand_struct *psig;  
6.    bool autoreap = false;  
7.    u64 utime, stime;  
8.  
9.    //获取进程描述符的thread_pid成员,唤醒所有的wait_pidfd  
10.    do_notify_pidfd(tsk);  
11.    //填写info信息  
12.    info.si_signo = sig;  
13.    info.si_errno = 0;  
14.    //填写发送者的pid和uid  
15.    info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent));  
16.    info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns),  
17.                       task_uid(tsk));  
18.    //获取进程的utime和stime  
19.    task_cputime(tsk, &utime, &stime);  
20.    //填写用户时间和系统时间  
21.    info.si_utime = nsec_to_clock_t(utime + tsk->signal->utime);  
22.    info.si_stime = nsec_to_clock_t(stime + tsk->signal->stime);  
23.    //填写退出状态和退出码  
24.    info.si_status = tsk->exit_code & 0x7f;  
25.    if (tsk->exit_code & 0x80)  
26.        info.si_code = CLD_DUMPED;  
27.    else if (tsk->exit_code & 0x7f)  
28.        info.si_code = CLD_KILLED;  
29.    else {  
30.        info.si_code = CLD_EXITED;  
31.        info.si_status = tsk->exit_code >> 8;  
32.    }  
33.    //找到父进程的信号处理程序  
34.    psig = tsk->parent->sighand;  
35.    spin_lock_irqsave(&psig->siglock, flags);  
36.    //如果没有被跟踪,发送的是子进程退出信号,  
37.    if (!tsk->ptrace && sig == SIGCHLD &&  
38.        //父进程没有SIGCHLD信号的处理程序或者父进程没有调用wait  
39.        (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||  
40.         (psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {  
41.        autoreap = true;  
42.        //如果父进程没有SIGCHLD信号的处理程序,说明不关注该信号  
43.        if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)  
44.            sig = 0;//信号设置为0,没有信号的意思  
45.    }  
46.  
47.    if (valid_signal(sig) && sig)//如果信号有效且不为0  
48.        //发送信号sig给父进程  
49.        __send_signal(sig, &info, tsk->parent, PIDTYPE_TGID, false);  
50.    __wake_up_parent(tsk, tsk->parent);//唤醒阻塞在等待队列的父进程  
51.    spin_unlock_irqrestore(&psig->siglock, flags);  
52.  
53.    return autoreap;  
54.}  

do_notify_parent函数主要做了以下几件事:

  1. 调用函数do_notify_pidfd获取进程描述符的thread_pid成员,唤醒所有的wait_pidfd;
  2. 填写填写info信息,包括错误信号、错误码、发送者的pid和uid、进程的用户时间和系统时间、进程的退出状态和退出码;
  3. 如果进程没有被跟踪并且发送的信号是子进程退出信号,在满足这两个条件的前提下,如果父进程没有SIGCHLD信号的处理程序或者父进程没有调用wait,那么我们进行下面的操作,把autoreap设置为true,这说明父进程不关心子进程的退出,子进程可以自己消亡了,不必进入僵尸状态。如果如果父进程没有SIGCHLD信号的处理程序,则需要把信号设置为0,这是说明我们不必把子进程退出的信号发送出去的意思;
  4. 如果信号有效且不为0,那么调用函数__send_signal发送信号sig给父进程;
  5. 调用函数__wake_up_parent唤醒阻塞在等待队列的父进程,如果父进程阻塞在wait函数,则会被唤醒,如果父进程没有阻塞在wait函数,那么父进程不会在等待队列中,对父进程不会有影响。
  6. 最后返回autoreap,如果autoreap为TRUE,说明子进程发送的信号是退出信号并且父进程不关注该信号,子进程不需要进入僵尸状态。
2.4.1.3 release_task函数
1.void release_task(struct task_struct *p)  
2.{  
3.    struct task_struct *leader;  
4.    struct pid *thread_pid;  
5.    int zap_leader;  
6.repeat:  
7.    rcu_read_lock();  
8.    //进程已经死亡了,其用户引用计数(processes)变量的值减一  
9.    atomic_dec(&__task_cred(p)->user->processes);  
10.    rcu_read_unlock();  
11.  
12.    cgroup_release(p);//遍历进程的cgroup种类,释放cgroup,删除cg_list成员  
13.  
14.    write_lock_irq(&tasklist_lock);//保证在对进程列表进行修改的过程中,不会发生中断  
15.    ptrace_release_task(p);//清除ptrace相关  
16.    thread_pid = get_pid(p->thread_pid);//thread_pid使用计数加一  
17.    __exit_signal(p);//进程的signal信息更新,处理pending signal,进程的信号处理程序改为NULL,释放sighand  
18.  
19.    zap_leader = 0;  
20.    leader = p->group_leader;  
21.    //如果此进程不是主线程并且是线程组的最后一个线程并且主线程是僵尸状态  
22.    if (leader != p && thread_group_empty(leader)  
23.            && leader->exit_state == EXIT_ZOMBIE) {  
24.        //发信号通知父进程  
25.        zap_leader = do_notify_parent(leader, leader->exit_signal);  
26.        if (zap_leader)//如果没有父进程关注子进程情况  
27.            leader->exit_state = EXIT_DEAD;//设置父进程的退出状态为死亡  
28.    }  
29.  
30.    write_unlock_irq(&tasklist_lock);//进程列表进行修改结束  
31.    //将进程从其过滤器树中分离出来,删除其引用计数,并通知未使用的过滤器  
32.    seccomp_filter_release(p);  
33.    proc_flush_pid(thread_pid);//更新某个pid的proc文件系统中信息  
34.    put_pid(thread_pid);//thread_pid使用计数减一  
35.    release_thread(p);//空  
36.    put_task_struct_rcu_user(p);  
37.  
38.    p = leader;//上面的操作对主线程再来一次  
39.    if (unlikely(zap_leader))  
40.        goto repeat;  
41.} 

release_task函数的主要工作是:

  1. 进程已经死亡了,使用原子操作将其用户引用计数(processes)变量的值减一;
  2. 调用函数cgroup_release遍历进程的cgroup种类,释放cgroup,删除cg_list成员;
  3. 调用函数ptrace_release_task清除ptrace相关;
  4. 调用函数get_pid将thread_pid使用计数加一;
  5. 调用函数__exit_signal进程的signal信息更新,处理pending signal,进程的信号处理程序改为NULL,释放sighand;
  6. 如果此进程不是主线程并且是线程组的最后一个线程并且主线程是僵尸状态,则调用函数do_notify_parent发信号通知父进程,然后通过返回值判断,如果父进程不关注leader进程的退出情况,则设置父进程的退出状态为死亡;
  7. 调用函数seccomp_filter_release将进程从其过滤器树中分离出来,删除其引用计数,并通知未使用的过滤器;
  8. 调用函数proc_flush_pid更新进程的proc文件系统信息;
  9. 调用函数put_pidthread_pid使用计数减一;
  10. 如果zap_leader为真,说明现在退出的线程不是主线程而且父进程不关注主线程的退出情况,那么上面的操作对主线程再来一次。在来一次的时候,p肯定为主线程了,zap_leader值为0,不会出现死循环的情况。

2.4.2 do_task_dead函数

1.void __noreturn do_task_dead(void)  
2.{  
3.    /* Causes final put_task_struct in finish_task_switch(): */  
4.    set_special_state(TASK_DEAD);//设置进程的状态为死亡  
5.  
6.    /* Tell freezer to ignore us: */  
7.    current->flags |= PF_NOFREEZE;//设置PF_NOFREEZE,表示进程不可以冻结  
8.  
9.    __schedule(false);//调用函数__schedule 以调度进程  
10.    BUG();//函数不会执行到这里,如果运行到这里,说明是一个BUG  
11.  
12.    /* Avoid "noreturn function does return" - but don't continue if BUG() is a NOP: */  
13.    for (;;)//死循环,避免函数退出  
14.        cpu_relax();  
15.}  

do_task_dead函数主要做了以下几件事:

  1. 调用函数set_special_state设置进程的状态为TASK_DEAD,这跟前面的状态状态不是同一个东西,前面设置的都是进程的退出状态,用于判断进程是否处于僵尸状态,现在这个状态表示进程已经完全死亡了,不再运行了;
  2. 设置PF_NOFREEZE标志位,表示进程不可以冻结,实际上进程都已经退出了,也不需要冻结;
  3. 调用函数__schedule 以把进程调度出去,从此以后该进程不会再运行了,后面的BUG()和for (;;)都是用来警告进程继续运行这种问题的。
;