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.pswp.cn/news/166108.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166108.shtml
英文地址,請注明出處:http://en.pswp.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 …