文章目錄
- 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函數主要做了一下幾件事:
- 調用函數force_uaccess_begin強制用戶進程可以訪問此進程的內容;
- 調用函數profile_task_exit對所有注冊的監聽器發送進程退出的事件通知,以便它們對這個事件做出處理;
- 判斷進程的PF_EXITING標志位是否已經置位,如果是,說明進程出現了遞歸處理退出的問題,需要遞歸解鎖用戶空間的鎖,設置進程狀態為不可中斷的睡眠,最后調用函數schedule主動調度出去,后面函數不再執行。因為這種情況最安全的做法是不再處理,等待重啟;
- 調用函數io_uring_files_cancel取消正在等待事件完成的 io_uring 請求;
- 調用函數exit_signals進程flage設置PF_EXITING,處理pending信號;
- 把進程的退出碼寫入進程描述符的exit_code中;
- 調用函數taskstats_exit用于向用戶空間發送有關進程退出的任務統計信息;
- 調用一系列的函數釋放進程的資源,比如內存、信號量、共享內存、打開的文件、文件系統相關、命名空間相關、工作隊列相關、cgroup相關等等;
- 調用函數exit_notify為自己的子進程找一個父親,然后把自己的死訊通知父進程;
- 釋放io_context、splice_pipe、task_frag等資源;
- 調用函數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函數主要做了以下幾件事:
- 調用函數forget_original_parent給所有子進程找一個parent;
- 如果是主線程死亡,則調用函數kill_orphaned_pgrp遍歷所有子進程,如果存在停止工作的情況,則給這子進程發送SIGHUP和SIGCONT信號,讓他們繼續運行;
- 設置進程的退出狀態為僵尸;
- 如果有進程在跟蹤此退出的進程或者此線程是主線程,則調用函數do_notify_parent發信號通知父進程;
- 如果沒有父進程關注子進程情況,設置進程狀態為死亡,然后將ptrace_entry節點添加到dead 鏈表的頭部,后面的會處理dead的;
- 當前進程等待的信號數量超出了其支持的范圍,這是一個少有的錯誤,則喚醒group_exit_task內核線程來退出進程組,并釋放相關資源;
- 遍歷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函數主要做了一下幾件事:
- 如果father進程的ptraced成員不為空,清空ptrace的所有任務;
- 調用函數find_child_reaper給子進程找一個進程回收器;進程回收器可以回收收養孤兒進程;
- 如果子進程鏈表是空的,說明沒有子進程,沒有子進程就不需要幫它找一個parent,直接返回;
- 調用函數find_new_reaper找一個可以最適合收養孤兒的進程;
- 遍歷father的每一個子進程,在這個大循環中遍歷子進程的每一個線程,在這個小循環調用函數RCU_INIT_POINTER使用rcu同步更新進程的real_parent,如果子進程沒有被跟蹤,設置子進程的parent,如果當前進程的父進程退出了,向進程組的所有進程發送pdeath_signal信號,到這里小循環介紹了;回到大循環中,調用函數same_thread_group判斷如果reaper和father不在同一個線程組;調用函數reparent_leader修改當前進程的父進程為指定的進程,以確保子進程能夠正常繼承新的父進程;
- 調用函數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函數主要是做了以下幾件事:
- 調用函數task_active_pid_ns找到進程的pid命名空間;
- 找到進程回收器,他是可以回收該PID命名空間內的所有孤兒進程;
- 如果father進程不是進程回收器,直接返回我們找到的進程回收器;
- 調用函數find_alive_thread在father進程中找一個活著的線程,如果找到了,把這個線程設置為這個pid命令空間的進程回收器,然后返回這個進程回收器;
- 到這里說明肯定找不到了,遍歷dead鏈表的每一個節點,刪除節點,釋放對應的進程內存資源;
- 調用函數zap_pid_ns_processes終止指定PID命名空間中的所有進程,因為找不到內存回收器來收養這些進程了;
- 返回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函數主要做了以下幾件事:
- 調用函數find_alive_thread在father進程中找一個活著的線程,如果找到了直接返回這個線程,因為這個線程跟我們的父進程是同一個線程組,共享很多資源,是最適合收養的存在了;
- 如果進程具備接管孤兒進程的能力,獲取進程的pid命名空間的級別,使用for循環找同級別的pid命名空間的祖先進程,如果找到了init進程,退出循環;如果該祖先進程不具備接管孤兒進程的能力,在往上找上一級的祖先;如果該祖先進程不具備接管孤兒進程的能力,調用函數find_alive_thread在該祖先進程下找一個線程,找到了就返回這個線程;這是從父進程開始往上找,尋找接管孤兒進程的能力的祖先;
- 到這里說明祖先進程也不靠譜,只能返回進程回收器,也就是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函數主要做了幾件事:
- 如果子進程的退出狀態是死亡,直接返回;
- 設置exit_signal為SIGCHLD;
- 如果進程沒有被跟蹤,進程的狀態是僵尸而且進程沒有其他線程,那么調用函數do_notify_parent發信號通知父進程,根據返回值,如果父進程沒有關注子進程的退出狀態,設置父進程的退出狀態為死亡,然后將ptrace_entry節點添加到dead 鏈表的頭部,釋放進程描述符內存;
- 到這里說明子進程是正常運行的,調用函數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函數主要做了以下幾件事:
- 調用函數do_notify_pidfd獲取進程描述符的thread_pid成員,喚醒所有的wait_pidfd;
- 填寫填寫info信息,包括錯誤信號、錯誤碼、發送者的pid和uid、進程的用戶時間和系統時間、進程的退出狀態和退出碼;
- 如果進程沒有被跟蹤并且發送的信號是子進程退出信號,在滿足這兩個條件的前提下,如果父進程沒有SIGCHLD信號的處理程序或者父進程沒有調用wait,那么我們進行下面的操作,把autoreap設置為true,這說明父進程不關心子進程的退出,子進程可以自己消亡了,不必進入僵尸狀態。如果如果父進程沒有SIGCHLD信號的處理程序,則需要把信號設置為0,這是說明我們不必把子進程退出的信號發送出去的意思;
- 如果信號有效且不為0,那么調用函數__send_signal發送信號sig給父進程;
- 調用函數__wake_up_parent喚醒阻塞在等待隊列的父進程,如果父進程阻塞在wait函數,則會被喚醒,如果父進程沒有阻塞在wait函數,那么父進程不會在等待隊列中,對父進程不會有影響。
- 最后返回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函數的主要工作是:
- 進程已經死亡了,使用原子操作將其用戶引用計數(processes)變量的值減一;
- 調用函數cgroup_release遍歷進程的cgroup種類,釋放cgroup,刪除cg_list成員;
- 調用函數ptrace_release_task清除ptrace相關;
- 調用函數get_pid將thread_pid使用計數加一;
- 調用函數__exit_signal進程的signal信息更新,處理pending signal,進程的信號處理程序改為NULL,釋放sighand;
- 如果此進程不是主線程并且是線程組的最后一個線程并且主線程是僵尸狀態,則調用函數do_notify_parent發信號通知父進程,然后通過返回值判斷,如果父進程不關注leader進程的退出情況,則設置父進程的退出狀態為死亡;
- 調用函數seccomp_filter_release將進程從其過濾器樹中分離出來,刪除其引用計數,并通知未使用的過濾器;
- 調用函數proc_flush_pid更新進程的proc文件系統信息;
- 調用函數put_pidthread_pid使用計數減一;
- 如果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函數主要做了以下幾件事:
- 調用函數set_special_state設置進程的狀態為TASK_DEAD,這跟前面的狀態狀態不是同一個東西,前面設置的都是進程的退出狀態,用于判斷進程是否處于僵尸狀態,現在這個狀態表示進程已經完全死亡了,不再運行了;
- 設置PF_NOFREEZE標志位,表示進程不可以凍結,實際上進程都已經退出了,也不需要凍結;
- 調用函數__schedule 以把進程調度出去,從此以后該進程不會再運行了,后面的BUG()和for (;;)都是用來警告進程繼續運行這種問題的。