Linux2.6內(nèi)核進(jìn)程管理分析
linux內(nèi)核是linux操作系統(tǒng)中最核心的部分,用于實(shí)現(xiàn)對硬件部件的編程控制和接口操作。Linux內(nèi)核主要由5個模塊構(gòu)成,分別是:進(jìn)程調(diào)度模塊、內(nèi)存管理模塊、虛擬文件系統(tǒng)模塊、進(jìn)程間通信模塊。 Linux經(jīng)常使用散列表來實(shí)現(xiàn)高速緩存,高速緩存是需要快速訪問的信息。
本文將介紹Linux2.6內(nèi)核的進(jìn)程管理。我們首先來了解它的定義。
何為進(jìn)程 ?
進(jìn)程的模型包括進(jìn)程控制塊(PCB)、程序部分和數(shù)據(jù)集合三部分。
1、進(jìn)程控制塊PCB
PCB是進(jìn)程存在的唯一標(biāo)識。
PCB按功能分主要包含以下四部分:進(jìn)程標(biāo)示符、處理機(jī)狀態(tài)、進(jìn)程調(diào)度信息、進(jìn)程控 制信息。
(1)進(jìn)程標(biāo)示符:唯一標(biāo)識一個進(jìn)程。
(2)處理機(jī)狀態(tài):有處理機(jī)的各種寄存器中的內(nèi)容組成,寄存器包括通用寄存器、指令寄存器、程序狀態(tài)字PSW、和用戶棧指針。當(dāng)初立即被中斷時,進(jìn)程運(yùn)行信息必須保存在PCB中,以便運(yùn)行時從斷點(diǎn)繼續(xù)執(zhí)行。
(3)進(jìn)程調(diào)度信息:存放進(jìn)程狀態(tài)、進(jìn)程優(yōu)先級、進(jìn)程調(diào)度所需其他信息(如調(diào)度算法,進(jìn)程已運(yùn)行時間,等待CPU時間)、時間或阻塞原因。
(4)進(jìn)程控制信息:包括程序和數(shù)據(jù)的內(nèi)存或者外存地址,進(jìn)程同步和通信機(jī)制,資源清單(除CPU以外進(jìn)程所需的全部資源以及已經(jīng)分配的資源)、鏈接指針(下一進(jìn)程PCB地址)。
Linux的進(jìn)程控制塊PCB使用一個成為task_struct的結(jié)構(gòu)體來描述。該結(jié)構(gòu)體中定義了進(jìn)程的幾種狀態(tài):
(1)TASK_RUNNING狀態(tài)。Linux的進(jìn)程運(yùn)行狀態(tài)包括實(shí)際的運(yùn)行和就緒狀態(tài),對兩者的區(qū)分是根據(jù)當(dāng)前是否占有CPU,結(jié)構(gòu)體中current變量可以區(qū)分兩者。
(2)TASK_INTERRUPTIBLE狀態(tài)。即可中斷的等待狀態(tài),當(dāng)進(jìn)程在等待某個事件和某個資源,可中斷等待狀態(tài)的進(jìn)程可以被信號喚醒而進(jìn)入就緒狀態(tài)等待調(diào)度。
(3)TASK_UNINTERRUPTIBLE狀態(tài)。即不可中斷等待狀態(tài),該狀態(tài)進(jìn)程由于硬件不能滿足,不能被信號喚醒,必須等到得到所等待的資源之后才能被喚醒。
(4)TASK_ZOMBIE狀態(tài)。即僵死狀態(tài),終止進(jìn)程所占有的資源全部釋放之后,還保存著PCB信息,這種占有PCB但已被撤銷的進(jìn)程處于僵死狀態(tài)(如僵死進(jìn)程)。
(5)TASK_STOPPED狀態(tài)。即暫停狀態(tài),一般都是有運(yùn)行狀態(tài)轉(zhuǎn)換來,正等待某種特殊處理,如調(diào)試跟蹤的程序。
(6)TASK_DEAD狀態(tài)。新增加的狀態(tài),指已經(jīng)退出但是不需要父進(jìn)程回收的進(jìn)程。 Linux內(nèi)核創(chuàng)建一個進(jìn)程時,首先會新建一個空的task_struct結(jié)構(gòu)體,并將相應(yīng)信息填入結(jié)構(gòu)體中,然后將該結(jié)構(gòu)體的指針添加進(jìn)task數(shù)組,這個數(shù)組大小由NR_TASK(默認(rèn)一般為512)指定。調(diào)度程序一直維持著一個current指針,它指向當(dāng)前正在運(yùn)行的程序。Task[0]必須指向init_task進(jìn)程(0號進(jìn)程)。
Linux中,內(nèi)核將所有struct_task結(jié)構(gòu)體以兩種方式組織:
(1)哈希表,將進(jìn)程的PID作為哈希算法的輸入,可以用一個給定PID快速查找到進(jìn)程,通過find_task_pid()來定位相應(yīng)進(jìn)程。
(2)雙向循環(huán)鏈表,這樣可以使系統(tǒng)很容易遍歷所有的進(jìn)程。通過調(diào)用for_each_task()來實(shí)現(xiàn)遍歷。task_struct結(jié)構(gòu)體中的變量list_head的作用就是將進(jìn)程通過雙向鏈表將進(jìn)程連接起來。鏈表的首部和頭部都是init_task進(jìn)程。
2、進(jìn)程的創(chuàng)建
Linux提供了三種創(chuàng)建新進(jìn)程的方法:fork()、vfork()、clone() 三者分別對應(yīng)系統(tǒng)調(diào)用的sys_fork()、sys_vfork()、sys_clone(),最終三者都是通過do_fork() 調(diào)用完成的。
目前Linux在創(chuàng)建進(jìn)程時,采用“寫時拷貝”技術(shù),即在創(chuàng)建進(jìn)程時并不將父進(jìn)程所有的資源都復(fù)制給子進(jìn)程,而是需要時才進(jìn)行資源的拷貝,可以大大提高Linux的性能。
(1)fork()函數(shù)
調(diào)用fork后,系統(tǒng)會創(chuàng)建一個子進(jìn)程,子進(jìn)程和父進(jìn)程不同的只有它的進(jìn)程ID和父進(jìn)程ID,其他都一樣。地址空間不共享,由于采用“寫時拷貝”技術(shù),子進(jìn)程并不完全拷貝父進(jìn)程的數(shù)據(jù)段和棧、堆等的復(fù)制,這些區(qū)域作為父子進(jìn)程的共享區(qū)域,而且內(nèi)核將他們訪問權(quán)限設(shè)置為只讀,如果父子進(jìn)程任何一個試圖修改此區(qū)域,內(nèi)核就為那塊內(nèi)存拷貝制作一個副本。
之所以采用“寫時拷貝”是因為一般fork后會調(diào)用exec調(diào)用其他的執(zhí)行體。
父子進(jìn)程的執(zhí)行順序不確定。
fork函數(shù)被調(diào)用一次,但是返回兩次值。兩次返回值的區(qū)別是,子進(jìn)程的返回值是0,父進(jìn)程返回值是子進(jìn)程的進(jìn)程ID。調(diào)用失敗的話返回-1。
(2)vfork()函數(shù)
該函數(shù)與fork基本一致,只不過父子進(jìn)程共享父進(jìn)程的地址空間。
對于vfork創(chuàng)建新進(jìn)程后,父進(jìn)程會阻塞,子進(jìn)程借用父進(jìn)程的地址空間運(yùn)行,直到子進(jìn)程退出或者調(diào)用exec(exec函數(shù)族的作用是啟動另一個程序的執(zhí)行),父進(jìn)程才可以運(yùn)行。vfork和fork返回值相同。
(3)clone()函數(shù)
clone函數(shù)和fork、vfork不同,它接受一個指向函數(shù)的指針和該函數(shù)的參數(shù),在創(chuàng)建子進(jìn)程成功時就調(diào)用這個函數(shù)執(zhí)行。
3、進(jìn)程終止
分為自愿終止和被動終止。
(1)自愿終止
a.顯式自愿終止:在進(jìn)程中調(diào)用exit()函數(shù) b.隱式自愿終止:進(jìn)程從某個程序的主函數(shù)退出
(2)被動終止
a.當(dāng)進(jìn)程接收到一個它既不能處理也不能忽略的信號和異常 b.進(jìn)程接收到SIGABRT或者其他終止信號。
上述進(jìn)程終止主要分為兩步來完成:
(1) 首先通過調(diào)用do_exit()函數(shù)釋放掉與進(jìn)程相關(guān)的大部分資源,并使進(jìn)程處于僵死狀態(tài),但是進(jìn)程描述符不釋放。
(2) 然后對進(jìn)程的處理應(yīng)看子進(jìn)程與父進(jìn)程誰先終止。子進(jìn)程先終止的話,則子進(jìn)程一直處于僵死狀態(tài),直到父進(jìn)程調(diào)用wait()或者waitpid()。調(diào)用完成后則完全釋放。父進(jìn)程先終止,則內(nèi)核必須為子進(jìn)程找到新的父進(jìn)程,方法是首先給子進(jìn)程在當(dāng)前組內(nèi)找一個線程最為父進(jìn)程,不行就讓init做父進(jìn)程。
wait()函數(shù)的兩個作用:獲取內(nèi)核發(fā)送來的子進(jìn)程終止消息和清除子進(jìn)程的所有獨(dú)享資源。wait函數(shù)會首先掛起調(diào)用它的進(jìn)程,知道該進(jìn)程的一個子進(jìn)程終止,此時函數(shù)會返回該子進(jìn)程的PID給父進(jìn)程。
4、線程的實(shí)現(xiàn)
Linux內(nèi)核中沒有專門的實(shí)現(xiàn)線程的機(jī)制,而是通過用戶級程序庫來實(shí)現(xiàn)的,例如pthread庫,以便將所有的線程映射到一個單獨(dú)的內(nèi)核級進(jìn)程中。Linux提供的一種不區(qū)分進(jìn)程和線程的方案:通過使用一種類似于Solaris輕量級進(jìn)程的方法,用戶級線程被映射到內(nèi)核級進(jìn)程上,組成一個用戶級進(jìn)程的多個用戶級線程被映射到共享同一個ID的多個Linux內(nèi)核級進(jìn)程上。這使得這些進(jìn)程可以共享文件和內(nèi)存等資源,使得同一組中的進(jìn)程調(diào)度切換時不需要切換上下文。
5、Linux進(jìn)程調(diào)度
Linux是一個搶占式多任務(wù)系統(tǒng),高優(yōu)先級的可以搶占低優(yōu)先級的CPU運(yùn)行。Linux優(yōu)先級分為靜態(tài)優(yōu)先級和動態(tài)優(yōu)先級。
Linux進(jìn)程分為普通進(jìn)程和實(shí)時進(jìn)程兩類。實(shí)時進(jìn)程創(chuàng)建時靜態(tài)優(yōu)先級就已經(jīng)分配而且不會改變,不為實(shí)時進(jìn)程計算動態(tài)優(yōu)先級,實(shí)時進(jìn)程的優(yōu)先級范圍為0~99都高于普通進(jìn)程100~139。普通進(jìn)程優(yōu)先級同樣有靜態(tài)優(yōu)先級,但是沒有作用,內(nèi)核為普通進(jìn)程計算動態(tài)優(yōu)先級,并根據(jù)優(yōu)先級分配時間片,來調(diào)度進(jìn)程。
Linux提供了三種調(diào)度策略:
(1)SCHED_NORMAL面向普通進(jìn)程的時間片輪轉(zhuǎn)策略。時間片用完后再選擇一個優(yōu)先級相對較高的進(jìn)程進(jìn)程調(diào)度。
(2)SCHED_FIFO面向?qū)憫?yīng)時間要求比較高、運(yùn)行所需時間較短的實(shí)時進(jìn)程。
(3)SCHED_RR面向?qū)憫?yīng)時間要求比較高、運(yùn)行所需時間較長的實(shí)時進(jìn)程。
總結(jié)調(diào)度,根據(jù)進(jìn)程的分類調(diào)度可分為實(shí)時調(diào)度和非實(shí)時調(diào)度。
(1)實(shí)時調(diào)度—針對實(shí)時進(jìn)程靜態(tài)優(yōu)先級。
對于實(shí)時進(jìn)程,靜態(tài)優(yōu)先級決定了對CPU的搶占,當(dāng)高優(yōu)先級的進(jìn)程到達(dá)時,會搶占低優(yōu)先級進(jìn)程的CPU,同樣可以知道實(shí)時進(jìn)程總是能搶占普通進(jìn)程的CPU。對于同一優(yōu)先級的實(shí)時進(jìn)程則又可采用兩種調(diào)度算法:FIFO(先來先服務(wù))和RR(時間片輪轉(zhuǎn))。
例如,當(dāng)前進(jìn)程有A(30),B(20),C(20),D(5)且B早于C到達(dá),括號內(nèi)為進(jìn)程的靜態(tài)優(yōu)先級。則采用FIFO為:D優(yōu)先級最高先執(zhí)行B,然后是B和C優(yōu)先級相同,由于B早到達(dá),所以先執(zhí)行B再C,最后是優(yōu)先級最低的A。執(zhí)行順序為D—B—C—A.采用RR則仍然是先運(yùn)行D,完畢后則交換運(yùn)行B和C,運(yùn)行完畢后是A。順序為D—B—C—B—C—A。
(2)非實(shí)時調(diào)度—普通進(jìn)程動態(tài)優(yōu)先級。
內(nèi)核為普通進(jìn)程計算動態(tài)優(yōu)先級,根據(jù)此優(yōu)先級為進(jìn)程分配不同的時間片(RR),此優(yōu)先級只作為分配時間片的基礎(chǔ),不能夠通過動態(tài)優(yōu)先級高低搶占CPU。每次當(dāng)進(jìn)程的時間片使用完后都會為其重新計算動態(tài)優(yōu)先級及分配的時間片。