進(jìn)程管理
進(jìn)程是操作系統(tǒng)的基本概念鳄逾,本節(jié)主要總結(jié)Linux內(nèi)核如何管理進(jìn)程:進(jìn)程在內(nèi)核中如何創(chuàng)建,消亡灵莲。
1.進(jìn)程
進(jìn)程是處于執(zhí)行期的程序雕凹,但不僅包含可執(zhí)行的程序代碼,還包括其他資源:打開(kāi)的文件政冻、掛起的信號(hào)枚抵、內(nèi)核內(nèi)部數(shù)據(jù)、處理器狀態(tài)明场、一個(gè)或多個(gè)具有內(nèi)存映射的內(nèi)存地址空間和執(zhí)行線程以及存放全局變量的數(shù)據(jù)段等汽摹。
線程
執(zhí)行線程,簡(jiǎn)稱線程榕堰,是進(jìn)程中活動(dòng)的對(duì)象竖慧。擁有獨(dú)立的程序計(jì)數(shù)器、進(jìn)程棧和進(jìn)程寄存器逆屡。內(nèi)核調(diào)度的對(duì)象是線程而不是進(jìn)程圾旨,在Linux中線程是一種特殊的進(jìn)程。
2.進(jìn)程描述符
內(nèi)核把進(jìn)程的列表存放在叫做任務(wù)隊(duì)列(task list)的雙向循環(huán)列表中(列表插入刪除復(fù)雜度低)魏蔗。列表的每一項(xiàng)類(lèi)型都是task_struct
稱為進(jìn)程描述符(process description)砍的,進(jìn)程描述符能夠完整的描述一個(gè)正在執(zhí)行的程序。
分配進(jìn)程描述符
Linux通過(guò)slab分配task_struct
結(jié)構(gòu)莺治,在棧底(向下增長(zhǎng)的棧)創(chuàng)建一個(gè)新的結(jié)構(gòu)struct thread_info
用于存放task_struct
的偏移地址廓鞠,這樣方便定位task_struct
的實(shí)際指針。
進(jìn)程描述符的存放
內(nèi)核中大部分處理進(jìn)程的代碼都是直接訪問(wèn)task_struct
指針谣旁,通過(guò)current
宏查找當(dāng)前正在運(yùn)行進(jìn)程的進(jìn)程描述符床佳。但是像x86寄存器較少,因此只能通過(guò)內(nèi)核棧的尾端創(chuàng)建thread_info
來(lái)計(jì)算偏移地址查找task_struct
榄审。
進(jìn)程狀態(tài)
進(jìn)程描述符中的state
域描述了進(jìn)程的當(dāng)前狀態(tài)砌们。進(jìn)程狀態(tài)處于下列五種狀態(tài)之一:
- TASK_RUNNING(運(yùn)行)——進(jìn)程可執(zhí)行,處于執(zhí)行中或者運(yùn)行隊(duì)列中等待
- TASK_INTERRUPTIBLE(可中斷)——進(jìn)程正在睡眠(被阻塞),等待某些條件達(dá)成浪感。也可以通過(guò)接收信號(hào)提前被喚醒并隨時(shí)準(zhǔn)備投入運(yùn)行
- TASK_UNITTERUPTIBLE(不可中斷)——對(duì)信號(hào)不做相應(yīng)昔头,其余和可中斷狀態(tài)相同,通常用于重要且不能中斷的進(jìn)程
- __TASK_TRACED——被其他進(jìn)程跟蹤的進(jìn)程影兽,例如通過(guò)ptrace對(duì)調(diào)試程序進(jìn)行跟蹤
- __TASK_STOPPED(停止)——進(jìn)程停止執(zhí)行揭斧,進(jìn)程沒(méi)有投入運(yùn)行也不能投入運(yùn)行
設(shè)置當(dāng)前進(jìn)程狀態(tài)
內(nèi)核調(diào)整某個(gè)進(jìn)程的狀態(tài),可以通過(guò)如下代碼:
set_task_state(task,state);
或者
task->state = state;
設(shè)置當(dāng)前狀態(tài)峻堰,可以通過(guò)set_current_state(state)
或set_task_state(current,state)
進(jìn)程上下文
一般程序在用戶空間執(zhí)行讹开,一旦程序執(zhí)行了系統(tǒng)調(diào)用或者觸發(fā)某個(gè)異常,它就陷入內(nèi)核空間(對(duì)應(yīng)第一節(jié)內(nèi)容)捐名。除非在內(nèi)核空間運(yùn)行期間有更高優(yōu)先級(jí)的進(jìn)程需要執(zhí)行并由調(diào)度器做出了相應(yīng)的調(diào)整萧吠,否則在內(nèi)核退出的時(shí)候,程序恢復(fù)在用戶空間繼續(xù)執(zhí)行桐筏。
系統(tǒng)調(diào)用和異常處理程序是對(duì)內(nèi)核明確定義的接口。進(jìn)程只有通過(guò)這些接口才能陷入內(nèi)核執(zhí)行拇砰,對(duì)內(nèi)核的所有訪問(wèn)必須通過(guò)這些接口梅忌。
進(jìn)程家族樹(shù)
Unix系統(tǒng)的進(jìn)程之間存在明顯的繼承關(guān)系,Linux也是如此除破。內(nèi)核在系統(tǒng)啟動(dòng)最后階段執(zhí)行了init
進(jìn)程牧氮,該進(jìn)程讀取系統(tǒng)初始化腳本并執(zhí)行其他相關(guān)程序,最終完成系統(tǒng)啟動(dòng)的整個(gè)過(guò)程瑰枫,PID為1踱葛,所以所有進(jìn)程都是init
的后代。因此每個(gè)進(jìn)程標(biāo)識(shí)符都有一個(gè)指向父親的task->parent
指針光坝,和子進(jìn)程鏈表&task->children
尸诽。
由于任務(wù)隊(duì)列是一個(gè)雙向循環(huán)鏈表,我們可以通過(guò)下面兩種方式分別獲取前一個(gè)和后一個(gè)進(jìn)程:
list_entry(task->tasks.next, struct task_struct, tasks)
和
list_entry(task->tasks.next, struct task_struct, tasks)
3.進(jìn)程創(chuàng)建
許多操作系統(tǒng)進(jìn)程創(chuàng)建過(guò)程為盯另,首先在新的地址空間創(chuàng)建進(jìn)程性含,讀入可執(zhí)行文件,最后執(zhí)行鸳惯。而Unix將上述兩個(gè)步驟分解到兩個(gè)單獨(dú)的函數(shù)去執(zhí)行:fork()
和exec()
商蕴。
首先,fork()
通過(guò)拷貝當(dāng)前進(jìn)程創(chuàng)建子進(jìn)程芝发,子進(jìn)程與父進(jìn)程區(qū)別僅僅在于PID和PPID和某些資源和統(tǒng)計(jì)量绪商。
然后,exec()
負(fù)責(zé)讀取可執(zhí)行文件并將其載入地址空間運(yùn)行辅鲸。
寫(xiě)時(shí)拷貝
Linux的fork()
函數(shù)進(jìn)行了一個(gè)優(yōu)化格郁,采用寫(xiě)時(shí)拷貝實(shí)現(xiàn)。在創(chuàng)建進(jìn)程階段,內(nèi)核并不復(fù)制整個(gè)地址空間理张,而是讓父進(jìn)程和子進(jìn)程共享同一個(gè)拷貝赫蛇。
進(jìn)程只有在需要寫(xiě)入時(shí),才復(fù)制數(shù)據(jù)雾叭,這樣將頁(yè)拷貝推遲到寫(xiě)入階段悟耘,可以使Linux進(jìn)程快速啟動(dòng),并且往往進(jìn)程在fork()
之后會(huì)馬上exec()
织狐,不會(huì)有寫(xiě)入過(guò)程(這個(gè)優(yōu)化過(guò)程還是相當(dāng)機(jī)智暂幼,Linux快啟動(dòng)的靈魂!)
fork()
由前面介紹我們了解了進(jìn)程需要fork()
拷貝父進(jìn)程的信息移迫,Linux通過(guò)clone()
系統(tǒng)調(diào)用實(shí)現(xiàn)fork()
旺嬉,其功能主要通過(guò)cope_process()
函數(shù)實(shí)現(xiàn):
- 調(diào)用
dup_task_struct()
為新進(jìn)程創(chuàng)建一個(gè)內(nèi)核棧,thread_info結(jié)構(gòu)和task_struct厨埋,這些值與父進(jìn)程完全相同 - 檢查并確保創(chuàng)建子進(jìn)程后邪媳,當(dāng)前用戶的進(jìn)程數(shù)沒(méi)有超過(guò)限制
- 區(qū)分子進(jìn)程和父進(jìn)程,講進(jìn)程描述符中許多成員清零或初始化(主要是統(tǒng)計(jì)信息)荡陷,多數(shù)數(shù)據(jù)仍未修改
- 子進(jìn)程的狀態(tài)設(shè)置為T(mén)ASK_UNINTERRUPTIBLE雨效,保證其不會(huì)被運(yùn)行
- 調(diào)用
copy_flags()
更新進(jìn)程描述符的flag成員,表明是否擁有超級(jí)用戶權(quán)限的標(biāo)志PF_SUPERPRIV標(biāo)志清零废赞,表明進(jìn)程沒(méi)有調(diào)用exec()
函數(shù)的PF_FORKNOEXEC標(biāo)志被設(shè)置徽龟。 - 調(diào)用
alloc_pid()
為新進(jìn)程分配一個(gè)有效PID - 根據(jù)傳遞給
clone()
的參數(shù)標(biāo)志,cope_process()
拷貝或共享打開(kāi)的文件唉地、文件系統(tǒng)信息据悔、信號(hào)處理函數(shù)、進(jìn)程地址空間和命名空間等耘沼。通常對(duì)于制定進(jìn)程的線程极颓,這些資源都是共享;否則耕拷,這些資源對(duì)每個(gè)進(jìn)程都是不同的讼昆,往往需要拷貝到這里。 -
copy_process()
掃尾骚烧,并返回一個(gè)指向子進(jìn)程的指針
一般內(nèi)核會(huì)有意讓子進(jìn)程先執(zhí)行浸赫,減小寫(xiě)時(shí)拷貝可能的開(kāi)銷(xiāo)。
vfork()
對(duì)于vfork()
赃绊,其不拷貝父進(jìn)程的頁(yè)表項(xiàng)既峡,子進(jìn)程會(huì)作為父進(jìn)程的一個(gè)線程執(zhí)行,父進(jìn)程被阻塞碧查,直到子進(jìn)程退出或者執(zhí)行exec()
运敢。子進(jìn)程不能向地址空間寫(xiě)入校仑。
4.線程在Linux中實(shí)現(xiàn)
Linux中線程只是共享父進(jìn)程資源的輕量進(jìn)程,其創(chuàng)建方式和普通進(jìn)程類(lèi)似传惠,只是在調(diào)用clone()
時(shí)迄沫,需要傳遞一些參數(shù)標(biāo)志位,表明需要共享的資源:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
而普通的進(jìn)程為:
clone(SIGHLD, 0);
其中CLONE_VM
——父子進(jìn)程共享地址空間卦方;CLONE_FS
——共享文件系統(tǒng)信息羊瘩;CLONE_FILES
——共享打開(kāi)的文件;CLONE_SIGHAND
——共享信號(hào)處理函數(shù)和被阻斷的信號(hào)盼砍;
5.進(jìn)程終結(jié)
進(jìn)程終結(jié)一般是自身引起的尘吗,它發(fā)生在進(jìn)程調(diào)用exit()
系統(tǒng)調(diào)用時(shí)。當(dāng)進(jìn)程接收到它不能處理且不能忽略的信號(hào)或者異常時(shí)浇坐,也可能被動(dòng)終結(jié)睬捶。不管什么原因終結(jié),進(jìn)程終結(jié)的大部分工作由do_exit()
完成:
- 將
task_struct
的標(biāo)志成員設(shè)置為PF_EXITING - 調(diào)用
del_timer_sync()
刪除任意內(nèi)核定時(shí)器近刘。根據(jù)返回結(jié)果擒贸,確保沒(méi)有定時(shí)器在排隊(duì),也沒(méi)有定時(shí)器處理程序在運(yùn)行 - 若BSD的進(jìn)程記賬功能開(kāi)啟的觉渴,調(diào)用
acct_update_integrals()
來(lái)輸出記賬信息 - 調(diào)用
exit_mm()
函數(shù)釋放進(jìn)程占用的mm_struct
酗宋,若沒(méi)有其他進(jìn)程使用,就徹底釋放 - 調(diào)用
sem_exit()
疆拘。若進(jìn)程排隊(duì)等候IPC信號(hào),則它離開(kāi)隊(duì)列 - 調(diào)用
exit_files()
和exit_fs()
分別遞減文件描述符寂曹、文件系統(tǒng)數(shù)據(jù)的引用次數(shù)哎迄,若為0,可以釋放 - 接著把存放在
task_struct
的exit_code成員中的任務(wù)退出代碼設(shè)置為由exit()
提供的退出代碼隆圆,或者去完成其他由內(nèi)核機(jī)制規(guī)定的退出動(dòng)作漱挚。退出代碼存放在這里供父進(jìn)程隨時(shí)檢索 - 調(diào)用
exit_notify()
向父進(jìn)程發(fā)生信號(hào),給子進(jìn)程重新找養(yǎng)父渺氧,養(yǎng)父為線程組中的其他線程或者init
進(jìn)程旨涝,并設(shè)置task_struct
的exit_state
為EXIT_ZOMBIE - 調(diào)用
schedule()
切換到新進(jìn)程
至此進(jìn)程相關(guān)的所有資源都被釋放掉了,并處于EXIT_ZOMBIE狀態(tài)侣背,僅剩內(nèi)核棧白华、thread_info結(jié)構(gòu)和task_struct結(jié)構(gòu)用于給父進(jìn)程提供信息。父進(jìn)程檢索信息后贩耐,或者通知內(nèi)核那是無(wú)關(guān)信息后弧腥,將該內(nèi)存釋放,歸還系統(tǒng)使用潮太。