Linux內(nèi)核學(xué)習(xí)011——進(jìn)程管理(七)
進(jìn)程終結(jié)
當(dāng)一個(gè)進(jìn)程終結(jié)時(shí)淌山,內(nèi)核必須釋放其所占有的資源,并通知其父進(jìn)程顾瞻。
一般而言泼疑,進(jìn)程的結(jié)束是由自身引起的。進(jìn)程中介發(fā)生在進(jìn)程調(diào)用exit()系統(tǒng)調(diào)用時(shí)荷荤,無論是顯式調(diào)用或者是隱式地從某個(gè)程序的主函數(shù)返回退渗。當(dāng)進(jìn)程終結(jié)時(shí)移稳,主要時(shí)依靠do_exit()完成,該函數(shù)定義于Linux2.6.34//kernel/exit.c#L900会油。其主要完成以下工作:
- 將task_struct中的標(biāo)志成員設(shè)置為PF_EXITING秒裕,以表示進(jìn)程正在被刪除
- 調(diào)用del_timer_sync()刪除任一內(nèi)核定時(shí)器,根據(jù)返回的結(jié)果钞啸,它確保沒有定時(shí)器在排隊(duì)几蜻,也沒有定時(shí)器處理程序在運(yùn)行
- 若BSD的進(jìn)程記賬功能開啟,則調(diào)用acct_update_integrals()來輸出記賬信息
- 調(diào)用exit_mm()釋放進(jìn)程占用的mm_struct体斩,若沒有別的進(jìn)程還在使用梭稚,則徹底釋放
- 接著調(diào)用exit_sem()函數(shù),若進(jìn)程在排隊(duì)等待IPC信號(hào)絮吵,則離開隊(duì)列
- 調(diào)用exit_files()和exit_fs()弧烤,以分別遞減文件描述符、文件系統(tǒng)數(shù)據(jù)的應(yīng)用計(jì)數(shù)蹬敲。若其中的某個(gè)計(jì)數(shù)降為0暇昂,則可以釋放該資源、
- 接著將task_struct中的exit_code設(shè)置為exit()提供的退出值伴嗡,或者去完成任何其他由內(nèi)惡化機(jī)制規(guī)定的退出動(dòng)作急波,退出代碼存放在此處供父進(jìn)程檢索
- 調(diào)用exit_notify()向父進(jìn)程發(fā)送信號(hào),為本進(jìn)程的子進(jìn)程(若存在)尋找新的父進(jìn)程瘪校,要么是線程組中的其他線程或者為init進(jìn)程澄暮,并把進(jìn)程狀態(tài)設(shè)置為EXIT_ZOMBIE
- 調(diào)用schedule()切換到新的進(jìn)程。因?yàn)樘幱贓XIT_ZOMBIE狀態(tài)的進(jìn)程不會(huì)再被調(diào)度阱扬,所以這是進(jìn)程執(zhí)行的最后一段代碼
do_exit()函數(shù)如下:
NORET_TYPE void do_exit(long code)
{
struct task_struct *tsk = current;
int group_dead;
profile_task_exit(tsk);
WARN_ON(atomic_read(&tsk->fs_excl));
if (unlikely(in_interrupt()))
panic("Aiee, killing interrupt handler!");
if (unlikely(!tsk->pid))
panic("Attempted to kill the idle task!");
tracehook_report_exit(&code);
validate_creds_for_do_exit(tsk);
/*
* We're taking recursive faults here in do_exit. Safest is to just
* leave this task alone and wait for reboot.
*/
if (unlikely(tsk->flags & PF_EXITING)) {
printk(KERN_ALERT
"Fixing recursive fault but reboot is needed!\n");
/*
* We can do this unlocked here. The futex code uses
* this flag just to verify whether the pi state
* cleanup has been done or not. In the worst case it
* loops once more. We pretend that the cleanup was
* done as there is no way to return. Either the
* OWNER_DIED bit is set by now or we push the blocked
* task into the wait for ever nirwana as well.
*/
tsk->flags |= PF_EXITPIDONE;
set_current_state(TASK_UNINTERRUPTIBLE);
schedule();
}
exit_irq_thread();
exit_signals(tsk); /* sets PF_EXITING */
/*
* tsk->flags are checked in the futex code to protect against
* an exiting task cleaning up the robust pi futexes.
*/
smp_mb();
raw_spin_unlock_wait(&tsk->pi_lock);
if (unlikely(in_atomic()))
printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n",
current->comm, task_pid_nr(current),
preempt_count());
acct_update_integrals(tsk);
/* sync mm's RSS info before statistics gathering */
if (tsk->mm)
sync_mm_rss(tsk, tsk->mm);
group_dead = atomic_dec_and_test(&tsk->signal->live);
if (group_dead) {
hrtimer_cancel(&tsk->signal->real_timer);
exit_itimers(tsk->signal);
if (tsk->mm)
setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm);
}
acct_collect(code, group_dead);
if (group_dead)
tty_audit_exit();
if (unlikely(tsk->audit_context))
audit_free(tsk);
tsk->exit_code = code;
taskstats_exit(tsk, group_dead);
exit_mm(tsk);
if (group_dead)
acct_process();
trace_sched_process_exit(tsk);
exit_sem(tsk);
exit_files(tsk);
exit_fs(tsk);
check_stack_usage();
exit_thread();
cgroup_exit(tsk, 1);
if (group_dead)
disassociate_ctty(1);
module_put(task_thread_info(tsk)->exec_domain->module);
proc_exit_connector(tsk);
/*
* FIXME: do that only when needed, using sched_exit tracepoint
*/
flush_ptrace_hw_breakpoint(tsk);
/*
* Flush inherited counters to the parent - before the parent
* gets woken up by child-exit notifications.
*/
perf_event_exit_task(tsk);
exit_notify(tsk, group_dead);
#ifdef CONFIG_NUMA
mpol_put(tsk->mempolicy);
tsk->mempolicy = NULL;
#endif
#ifdef CONFIG_FUTEX
if (unlikely(current->pi_state_cache))
kfree(current->pi_state_cache);
#endif
/*
* Make sure we are holding no locks:
*/
debug_check_no_locks_held(tsk);
/*
* We can do this unlocked here. The futex code uses this flag
* just to verify whether the pi state cleanup has been done
* or not. In the worst case it loops once more.
*/
tsk->flags |= PF_EXITPIDONE;
if (tsk->io_context)
exit_io_context(tsk);
if (tsk->splice_pipe)
__free_pipe_info(tsk->splice_pipe);
validate_creds_for_do_exit(tsk);
preempt_disable();
exit_rcu();
/* causes final put_task_struct in finish_task_switch(). */
tsk->state = TASK_DEAD;
schedule();
BUG();
/* Avoid "noreturn function does return". */
for (;;)
cpu_relax(); /* For when BUG is null */
}
至此泣懊,與進(jìn)程相關(guān)聯(lián)的所有資源都被釋放掉了,此時(shí)進(jìn)程不可運(yùn)行麻惶,并處于EXIT_ZOMBIE退出狀態(tài)馍刮。它此時(shí)僅占有的內(nèi)存為內(nèi)核棧、thread_info結(jié)構(gòu)體和task_struct結(jié)構(gòu)體窃蹋。此時(shí)的進(jìn)程是為了向父進(jìn)程提供信息卡啰。當(dāng)父進(jìn)程檢索到信息或者通知內(nèi)核那是無關(guān)信息后,由進(jìn)程所持有的剩余內(nèi)存被釋放脐彩。
刪除進(jìn)程描述符
在調(diào)用do_exit()之后碎乃,進(jìn)程已經(jīng)僵死不能運(yùn)行了,但是系統(tǒng)還保留了它的進(jìn)程描述符惠奸。因此梅誓,進(jìn)程終結(jié)時(shí)所需的清理工作和進(jìn)程描述符的刪除是被分開執(zhí)行的。
wait一族函數(shù)都是通過唯一的一個(gè)系統(tǒng)調(diào)用wait4()來實(shí)現(xiàn)的。wait4()會(huì)掛起調(diào)用它的進(jìn)程梗掰,直到其中的一個(gè)子進(jìn)程退出嵌言,此時(shí)函數(shù)會(huì)返回該子進(jìn)程的PID。此外及穗,調(diào)用該函數(shù)時(shí)提供的指針包含子函數(shù)退出時(shí)的退出代碼摧茴。
當(dāng)最終需要釋放進(jìn)程描述符時(shí),會(huì)調(diào)用release_task()函數(shù)埂陆,該函數(shù)定義在Linux2.6.34/kernel/exit.c#L168完成以下工作:
- 調(diào)用__exit_signal()函數(shù)苛白,該函數(shù)會(huì)調(diào)用_unhash_process(),后者會(huì)調(diào)用detach_pid()從pidhash上撒謊才能胡該進(jìn)程焚虱,同時(shí)也會(huì)在任務(wù)列表中刪除該進(jìn)程购裙。
- _exit_signal()會(huì)釋放目前僵死進(jìn)程所使用的所有剩余資源,并進(jìn)行最終統(tǒng)計(jì)和記錄
- 若該進(jìn)程是線程組的最后一個(gè)進(jìn)程鹃栽,且領(lǐng)頭進(jìn)程已經(jīng)死掉躏率,那么release_task()需要通知僵死的領(lǐng)頭進(jìn)程的父進(jìn)程
- release_task()調(diào)用put_task_struct()釋放進(jìn)程內(nèi)核棧和thread_info結(jié)構(gòu)所占的頁,并釋放task_struct所占的高速緩存
通過上述操作民鼓,完成了釋放進(jìn)程描述符和所有進(jìn)程獨(dú)享的資源
具體代碼如下:
void release_task(struct task_struct * p)
{
struct task_struct *leader;
int zap_leader;
repeat:
tracehook_prepare_release_task(p);
/* don't need to get the RCU readlock here - the process is dead and
* can't be modifying its own credentials. But shut RCU-lockdep up */
rcu_read_lock();
atomic_dec(&__task_cred(p)->user->processes);
rcu_read_unlock();
proc_flush_task(p);
write_lock_irq(&tasklist_lock);
tracehook_finish_release_task(p);
__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(task_detached(leader));
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 = task_detached(leader);
/*
* This maintains the invariant that release_task()
* only runs on a task in EXIT_DEAD, just for sanity.
*/
if (zap_leader)
leader->exit_state = EXIT_DEAD;
}
write_unlock_irq(&tasklist_lock);
release_thread(p);
call_rcu(&p->rcu, delayed_put_task_struct);
p = leader;
if (unlikely(zap_leader))
goto repeat;
}