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