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

文章目录

    • 2.3 进程退出的几种情况
    • 2.4 进程终止过程分析
      • 2.4.1 exit_notify函数
        • 2.4.1.1 forget_original_parent函数
          • 2.4.1.1.1 find_child_reaper函数
          • 2.4.1.1.2 find_new_reaper函数
          • 2.4.1.1.3 reparent_leader函数
        • 2.4.1.2 do_notify_parent函数
        • 2.4.1.3 release_task函数
      • 2.4.2 do_task_dead函数

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 (;;)都是用来警告进程继续运行这种问题的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/166108.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GitHub桌面版

GitHub桌面版 一、GitHub 桌面版二、clone 仓库三、更新仓库 一、GitHub 桌面版 二、clone 仓库 三、更新仓库

穆桂英挂帅

《穆桂英挂帅》 作家&#xff0f;罗光记 穆桂英挂帅破敌&#xff0c; 威风凛凛立战场。 铁甲如云奔雷急&#xff0c; 英姿飒爽傲寒霜。 烽火连天战鼓擂&#xff0c; 旌旗翻飞壮心惊。 刀光剑影映红日&#xff0c; 豪情壮志天地惊。 风云变幻战事急&#xff0c; 英勇穆桂英…

Azure Machine Learning - Azure可视化图像分类操作实战

目录 一、数据准备二、创建自定义视觉资源三、创建新项目四、选择训练图像五、上传和标记图像六、训练分类器七、评估分类器概率阈值 八、管理训练迭代 在本文中&#xff0c;你将了解如何使用Azure可视化页面创建图像分类模型。 生成模型后&#xff0c;可以使用新图像测试该模型…

温馨提示!办理流量卡千万不要填写别人的身份证信息,切记!

可以用别人的身份证办理流量卡吗&#xff1f;是很多朋友都比较关注的一个问题&#xff0c;在这里明确的告诉大家一下&#xff0c;当然是不可以的。 ​  不管你是在线下营业厅办理&#xff0c;还是在线上申请&#xff0c;都是需要提供本人的证件信息才能办理&#xff1a; 1、…

TIDB拓扑结构

TiDB Server&#xff1a;SQL层&#xff0c;负责接受客户端的连接&#xff0c;执行SQL解析和优化&#xff0c;最终生成分布式执行计划。TiDB Server为无状态的&#xff0c;可增加节点负载均衡。 PD (Placement Driver) Server&#xff1a;整个TiDB集群的元信息管理模块&#xf…

【超详细】手搓一个微信日记本

&#x1f380; 文章作者&#xff1a;二土电子 &#x1f338; 关注公众号获取更多资料&#xff01; &#x1f438; 期待大家一起学习交流&#xff01; 这里对之前的微信记事本小程序进行了重新编写&#xff0c;增加了更加详细的步骤描述&#xff0c;将全部图片都改成了本地图…

用EasyAVFilter将网络文件或者本地文件推送RTMP出去的时候发现CPU占用好高,用的也是vcodec copy呀,什么原因?

最近同事在用EasyAVFilter集成在EasyDarwin中做视频拉流转推RTMP流的功能的时候&#xff0c;发现怎么做CPU占用都会很高&#xff0c;但是视频没有调用转码&#xff0c;vcodec用的就是copy&#xff0c;这是什么原因呢&#xff1f; 我们用在线的RTSP流就不会出现这种情况&#x…

SSM个性化旅游管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 个性化旅游管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B…

raid磁盘阵列

在单机时代&#xff0c;采用单块磁盘进行数据存储和读写的方式&#xff0c;由于寻址和读写的时间消耗&#xff0c;导致I/O性能非常低&#xff0c;且存储容量还会受到限制。另外&#xff0c;单块磁盘极其容易出现物理故障&#xff0c;经常导致数据的丢失。此时&#xff0c;RAID技…

Java设计模式

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

新材料制造ERP用哪个好?企业应当如何挑选适用的

有些新材料存在特殊性&#xff0c;并且在制造过程中对车间、设备、工艺、人员等方面提出更高的要求。还有些新材料加工流程复杂&#xff0c;涉及多种材料的请购、出入库、使用和管理等环节&#xff0c;解决各个业务环节无缝衔接问题是很多制造企业面临的管理难题。 新材料制造…

牙科诊所小程序开发案例

一、背景&#xff1a; 针对传统口腔医疗领域中口腔诊所推广难,纸质信息保存难等问题&#xff0c;设计并开发了基于微信小程序实现口腔服务助手平台。为了给人们提供便捷&#xff0c;快速的预约方式&#xff0c;提高社会人群对口腔健康的关注力度。通过微信小程序互联网技术&…

文旅虚拟人IP:数字时代的传统文化推荐官

近几年&#xff0c;随着文旅虚拟人频“上岗”&#xff0c;虚拟人逐渐成为了文旅品牌的一种新颖的传统文化传播思路。 文旅品牌定制化推出虚拟人&#xff0c;本质原因是2023旅游业全面复苏&#xff0c;各文旅玩法同质化现象严重&#xff0c;在这样的境遇下&#xff0c;文旅品牌开…

OpenMLDB v0.8.4 诊断工具全面升级

新的v0.8.4版本中&#xff0c;我们对于诊断工具进行了全面系统化的升级&#xff0c;以提供更加完整和智能化的诊断报告&#xff0c;有助于高效排查 OpenMLDB 集群问题&#xff0c;大幅提升运维效率。 相比于之前的版本&#xff0c;新的诊断工具增添一键诊断功能&#xff0c;使…

首个央企量子云计算项目,中标!

6月29日&#xff0c;北京玻色量子科技有限公司&#xff08;简称“玻色量子”&#xff09;成功中标中国移动云能力中心“2023—2024年量子算法及光量子算力接入关键技术研究项目”&#xff0c;这是玻色量子继与移动云签订“五岳量子云计算创新加速计划”后&#x1f517;&#xf…

角色管理--体验产品专家岗

研发组织管理--角色管理--体验产品专家岗 定位 产品用户代言人&#xff0c;产品体验守门员&#xff0c;保证用户体验感知不低于行业水平并尝试新体验&#xff1b; 所需资质 对产品交互有自己的心得&#xff0c;可通过设计工具直观表达观点能站在用户角度思考问题&#xff0c…

揭秘 systemd:释放 Linux 服务管理的力量【systemd 一】

&#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 揭秘 systemd&#xff1a;释放 Linux 服务管理的力量【systemd 一】 前言第一&#xff1a;systemd简介第二&#xff1a;核心概念解析第三&#xff1a;服务管理与启动过程第四…

bootstrap插件的基本使用

1.更新表格数据&#xff08;根据行索引&#xff1a;仅更新一个单元格&#xff09; var rows {index : index, //更新列所在行的索引field : "status", //要更新列的fieldvalue : "正常" //要更新列的数据 } $(#table_Id).bootstrapTable("updateCel…

DELPHI开发APP回忆录二安卓与pc端路径的选择

路径方法WinAndroidGetHomePathC:\Users\ggggcexx\AppData\Roaming/data/user/0/com.stella.scan/files/GetDocumentsPathC:\Users\ggggcexx\Documents/data/user/0/com.embarcadero.FirstAidExpert_FMX_D11/filesGetSharedDocumentsPathC:\Users\Public\Documents/storage/emu…

杰发科技AC7801——EEP内存分布情况

简介 按照文档进行配置 核心代码如下 /*!* file sweeprom_demo.c** brief This file provides sweeprom demo test function.**//* Includes */ #include <stdlib.h> #include "ac780x_sweeprom.h" #include "ac780x_debugout.h"/* Define …