當一個進程終結時,內核必須釋放掉它所占有的資源并把這一終結事件告知父進程。進程的終結大部分都要靠 exit() 來完成的,最終的系統調用為 do_exit()。
當一個進程終結時,內核必須釋放掉它所占有的資源并把這一終結事件告知父進程。

進程的終結大部分都要靠 exit() 來完成的,最終的系統調用為 do_exit()。
asmlinkage long sys_exit(int error_code)
{
do_exit((error_code&0xff)<<8);
}
/*當cpu進入到do_exit后,當前進程就會在中途壽終正寢,不會從這個函數中返回,當然也就不會從sys_exit
中返回,從而也就不會從系統調用exit()中返回*/
fastcall NORET_TYPE void do_exit(long code)
{
...
WARN_ON(atomic_read(&tsk->fs_excl));
/*由于中斷服務程序根本不應該調用do_exit,不管是直接還是間接,所以首先通過in_interrupt進行加以檢查
若發現是在某個中斷服務程序中調用的,那就一定是出了問題*/
if (unlikely(in_interrupt()))
panic("Aiee, killing interrupt handler!");
...
/*current->flags的PF_EXITING標志表示進程正在被刪除 */
if (unlikely(tsk->flags & PF_EXITING)) {
printk(KERN_ALERT
"Fixing recursive fault but reboot is needed!\n");
...
tsk->flags |= PF_EXITPIDONE; /* 設置進程標識為PF_EXITPIDONE*/
if (tsk->io_context)
exit_io_context();
/* 設置進程狀態為不可中斷的等待狀態 */
set_current_state(TASK_UNINTERRUPTIBLE);
/* 調度其它進程 */
schedule();
}
tsk->flags |= PF_EXITING;
/* 內存屏障,用于確保在它以后的操做開始執行以前,它以前的操做已經完成 */
smp_mb();
spin_unlock_wait(&tsk->pi_lock);
...
//清除定時器
group_dead = atomic_dec_and_test(&tsk->signal->live);//live用來表示線程組中活動進程的數量
if (group_dead) { //當沒有活動的進程時
exit_child_reaper(tsk);
//取消高精度定時器
hrtimer_cancel(&tsk->signal->real_timer);
//刪除POSIX.1b類型的定時器
exit_itimers(tsk->signal);
}
//收集進程會計信息
acct_collect(code, group_dead);
...
//設置終止代碼
tsk->exit_code = code;
taskstats_exit(tsk, group_dead);
//釋放線性區描述符和頁表
exit_mm(tsk);
if (group_dead)
acct_process();
//遍歷current->sysvsem.undo_list鏈表,并清除進程所涉及的每個IPC信號量的操作痕跡
exit_sem(tsk);
//釋放文件對象相關資源
__exit_files(tsk);
//釋放struct fs_struct結構體
__exit_fs(tsk);
//檢查有多少未使用的進程內核棧
check_stack_usage();
exit_thread();
cgroup_exit(tsk, 1);
exit_keys(tsk);
if (group_dead && tsk->signal->leader)
disassociate_ctty(1);
module_put(task_thread_info(tsk)->exec_domain->module);
if (tsk->binfmt)
module_put(tsk->binfmt->module);
proc_exit_connector(tsk);
//給父進程發送信號,讓其知道子進程生命已經結束,來料理子進程的后事. 同時把進程狀態exit_state 設置成 EXIT_ZOMBIE
exit_notify(tsk);
...
tsk->flags |= PF_EXITPIDONE;
...
preempt_disable();
/* causes final put_task_struct in finish_task_switch(). */
tsk->state = TASK_DEAD;
/*do_exit 不返回的真正原因在這里,由于進程狀態設置成了EXIT_ZOMBIE,使得該進程永遠不會再被選中進行調度,所以
也就不會使用schedule()調度別的進程后從schedule中返回。因此只能等父進程收到子進程發送的信號來處理子進程,并將
子進程的task_struct結構釋放掉,子進程最終從系統中消失。而父進程在wait4(對應系統函數sys_wait4)中等待著。
*/
schedule();
BUG();
/* Avoid "noreturn function does return". */
for (;;)
cpu_relax(); /* For when BUG is null */
}
do_exit() 完成工作如下:
- 對該調用進行檢查,比如該方法是不能在中斷服務程序中調用的。
- 將 task_struct 中的標志成員設置為 PF_EXITING。
- 刪除內核定時器,根據返回的結果,它確保沒有定時器在排隊,也沒有定時器處理程序在運行。
- 把進程的退出代碼 exit_code 設置為由 exit() 提供的退出代碼,或者去完成任何其他由內核機制規定的退出動作。退出代碼存放在這里供父進程隨時檢索。
- 調用 exit_mm( )釋放進程占用的 mm_struct,若沒有別的進程使用它們(也即是這個地址空間沒有被共享),就徹底釋放它們。
- 調用 exit_sem(),清除進程所涉及的每個IPC信號量的操作痕跡,使得若進程排隊等候IPC信號,則離開隊列。
- 調用 __exit_files、__exit_fs,分別遞減文件描述符、文件系統數據的引用計數。若其中某個引用計數的數值降為零,那么就代表沒有進程在使用相應的資源,此時就可以釋放。
- 調用 exit_notify() 向父進程發送信號,給子進程重新找養父,養父為線程組中的其他線程或者init進程,并把進程狀態(task_strcut 結構中的exit_state)設置成 EXIT_ZOMBIE。
- 調用 schedule() 切換到新的進程。由于處于 EXIT_ZOMBIE 狀態的進程不會再會被調度,所以這是進程所執行的最后一段代碼。do_exit 永不返回。
到此,與進程相關的所有資源該釋放的都釋放掉了(假設該進程是這些資源的唯一使用者)。進程不可運行(實際上它也沒有地址空間可供它運行)并處于EXIT_ZOMBIE 退出狀態。

該進程目前所占用的內存資源就是內核棧、thread_info 結構和 task_struct 結構。此時進程存在的唯一目的就是向它的父進程提供信息。父進程檢索到信息后,或者通知內核它不關心那些無關的信息后,子進程的這些剩余資源才被釋放歸還給系統。
進程描述符的刪除
從上面可以知道,進程在調用 do_exit() 后,進程處于僵死狀態且不能運行。但是系統還保留它的進程描述符相關信息。之所以保留這些信息是為了讓系統有辦法在子進程終結后仍能獲得它的信息。
當父進程獲取已終結的子進程的信息后,或者通知內核它不關心那些無關的信息后,子進程的這些剩余資源才被釋放歸還給系統。
wait() 這一族函數都是通過唯一的一條系統調用 wait4() 來實現的。它的作用就是掛起調用它的進程,直到其中的一個子進程退出,此時函數會返回該子進程的 PID。另外,調用該函數時提供的指針會包含子函數退出時的退出代碼。
wait4() 最終會調用 sys_wait4()。
asmlinkage long sys_wait4(pid_t pid, int __user *stat_addr,
int options, struct rusage __user *ru)
{
long ret;
if (options & ~(WNOHANG|WUNTRACED|WCONTINUED|
__WNOTHREAD|__WCLONE|__WALL))
return -EINVAL;
ret = do_wait(pid, options | WEXITED, NULL, stat_addr, ru);
/* avoid REGPARM breakage on x86: */
prevent_tail_call(ret);
return ret;
}
當父進程因子進程在 exit() 中向其發送信號而被喚醒,父進程在將子進程在用戶空間運行的時間和系統空間運行的時間兩項統計數據合并入其自身的統計數據中,然后,在典型的條件下,就會調用 release_task() 將子進程殘存的資源,就是其 task_struct 結構和系統空間堆棧,全部釋放掉。
調用過程如下:
sys_wait4
--> do_wait
--> wait_task_zombie
--> release_task
release_task() 實現如下:
void release_task(struct task_struct * p)
{
struct task_struct *leader;
int zap_leader;
repeat:
...
/* 1)該函數調用_unhash_process(),后者調用detach_pid()從pidhash
? 上刪除該進程,同時也要從任務列表中刪除該進程
? 2)釋放目前僵死進程所使用的所有剩余資源,并進行最終統計和記錄
*/
__exit_signal(p);
/*
? If we are the last non-leader member of the thread
? group, and the leader is zombie, then notify the
? group leader's parent process. (if it wants notification.)
*/
zap_leader = 0;
leader = p->group_leader;
/*若該進程是線程組最后一個進程,并且領頭進程已經死掉,,則通知僵死的領頭進程的父進程 /
if (leader != p && thread_group_empty(leader) && leader->exit_state == EXIT_ZOMBIE) {
BUG_ON(leader->exit_signal == -1);
do_notify_parent(leader, leader->exit_signal);
/
? If we were the last child thread and the leader has
? exited already, and the leader's parent ignores SIGCHLD,
? then we are the one who should release the leader.
?
? do_notify_parent() will have marked it self-reaping in
? that case.
*/
zap_leader = (leader->exit_signal == -1);
}
write_unlock_irq(&tasklist_lock);
release_thread(p);
//調用 put_task_struct 釋放進程內核棧和thread_info結構所占的頁,并釋放task_struct 所占的slab告訴緩存。
call_rcu(&p->rcu, delayed_put_task_struct);
p = leader;
if (unlikely(zap_leader))
goto repeat;
}
release_task 完成的工作如下:
- 調用__exit_signal(),該函數調用_unhash_process(),后者調用detach_pid() 從 pidhash 上刪除該進程,同時也要從任務列表中刪除該進程。
- __exit_signal() 釋放目前僵死進程所使用的所有剩余資源,并進行最終統計和記錄。
- 若該進程是線程組最后一個進程,并且領頭進程已經死掉,則通知僵死的領頭進程的父進程 。
- 調用 put_task_struct() 釋放進程內核棧和 thread_info 結構所占的頁,并釋放 task_struct 所占的 slab 告訴緩存。
到此,進程描述符和進程所有獨享的資源全部就釋放掉了。