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