標籤:進程管理 進程調度
引言:
在Linux的核心的五大組成模組中,進程管理模組時非常重要的一部分,它雖然不像記憶體管理、虛擬檔案系統等模組那樣複雜,也不像處理序間通訊模組那樣條理化,但作為五大核心模組之一,進程管理對我們理解核心的運作、對於我們以後的編程非常重要。同時,作為五大組成模組中的核心模組,它與其他四個模組都有聯絡。下面就對進程模組進行想寫的介紹,首先要瞭解進程及其相關的概念。其次介紹進程的建立、切換、撤銷等基本操作。除此之外,還給出了Linux核心是如何對進程進行調度管理的。
一、進程及其相關概念
進程:進程可以理解為程式執行的一個執行個體,它包括可執行程式以及與其相關的系統資源,比如開啟的檔案、掛起的訊號、核心內部資料、處理器狀態、記憶體位址空間及包含全域變數的資料區段等。從核心的角度看,進程也可以稱為任務。
進程描述符:與進程相關的事情非常多,比如進程的狀態、進程的優先順序、進程的地址空間、允許該進程訪問的檔案等等,Linux核心為此專門設計了一個類型為task_struct的結構體,稱之為進程描述符。進程描述符中包含了核心管理進程的所有資訊,可以說,只要得到一個進程的進程描述符,就可以知道一個進程的所有資訊。
進程狀態:進程描述符task_struct結構體中有一個state欄位,表示進程當前的所處狀態。從進程的建立到進程的刪除,它可以經過5種不同的狀態,分別是可運行狀態、可中斷的等待狀態、不可中斷的等待狀態、暫停狀態、跟蹤狀態。除此之外,當進程被終止時,還可能會變為僵死狀態、僵死撤消狀態。核心可以使用宏set_current_state(state)設定當前進程的狀態,用set_task_state(task,state)設定某進程的狀態。
進程標示符:進程描述task_struct結構體中的pid欄位可以標識唯一標識一個進程,稱之為進程標識符PID。當建立一個新進程時,PID是按照順序從小到大分配給新進程的。核心通過管理一個pidmap_array位元影像來表示當前已指派的PID和閑置的PID號。注意:在多線程組中,所有的線程共用相同的PID。除了進程標識符外,核心對進程的大部分訪問時通過進程描述符指標進行的。
進程關係:進程之間的關係有親屬關係和非親屬關係。親屬關係包括父子關係和兄弟關係等。其中由tast_struct結構體中的parent/children/real_parent/sibling等欄位描述。除了親屬關係外,還有其他關係,比如,一個進程是一個進程組或登入工作階段的領頭進程,可能是一個線程組的領頭進程,這些關係由group_leader/tgid/signal->pgrp等欄位描述。
進程資源:為了防止進程過度的使用系統資源,核心為每個進程使用資源的數量進行了一些限制。其中包括進程地址空間的最大數、進程使用CPU的最大時間、堆的最大值、檔案大小的最大值、檔案鎖數量的最大值、訊息佇列的最大位元組數、開啟檔案描述符的最大數、進程擁有的頁框最大數等。
二、進程的建立、切換、撤銷
進程的建立:在Linux環境編程時,一般採用fork()函數來建立新的進程,當然,那是在使用者空間的函數,它會調用核心中的clone()系統調用,由clone()函數繼續調用do_fork()完成進程的建立。
傳統Unix系統中,建立的子進程複製父進程所擁有的資源,這種方法效率低,因為子進程需要拷貝父進程的整個地址空間。但是,子進程幾乎不必讀或修改父進程擁有的所有資源,因為很多情況下,子進程建立後會立即調用exec()一族的函數,並清除父進程仔細拷貝過來的地址空間。現代Unix系統用三種方式解決了這個問題:1、寫實複製技術允許父子進程讀相同的物理頁。2、輕量級進程允許父子進程共用每進程在核心的很多資料結構。3、vfork()系統調用建立的進程能共用父進程的記憶體位址空間,為了防止父進程重寫子進程需要的資料,阻塞父進程的執行,一直到子進程退出或執行一個新的程式為止。整個進程建立過程可能涉及到如下函數:
fork()/vfork()/_clone----------->clone()--------->do_fork()---------->copy_process()
上面的建立過程結束之後,就有了處於可運行狀態的完整的子進程,新的子進程有了PID、進程描述符等各種資料結構,要想實際運行它,還需要發送器把CPU交給新建立的子進程。
除了進程外,還有核心線程(用kernet_thread建立)的概念。在Linux中,核心線程與普通進程存在以下兩個方面的不同:
1、核心線程只運行在核心態,而普通進程既可以運行在核心態,也可運行在使用者態。
2、因為核心線程只運行在核心態,它只使用大於PAGE_OFFSET的線性地址空間。另一方面,不管在使用者態還是在核心態,普通進程可以用4GB的線性地址空間。
撤銷進程:進程終止後,需要通知核心以便核心釋放進程所擁有的資源,包括記憶體、開啟檔案以及其他資源,如訊號量。進程終止的一般方式是調用exit()庫函數,該函數釋放C函數庫所分配的資源,執行編程者所註冊的每個函數,並結束從系統回收進程的那個系統調用。
除了進程自己終止自己外,核心可以有選擇地強迫整個線程組死掉。這發生在:當進程接收到一個不能處理或忽視的訊號時,或者當核心正在代表進程運行時再核心態產生一個不可恢複的CPU異常時。
有兩個終止使用者態應用的系統調用:exit_group()系統調用,它終止整個線程組,即整個基於多線程的應用。do_group_exit()是實現這個系統調用的主要核心功能。exit()系統調用,它終止一個線程,而不管該線程所屬線程組中的所有其他進程。do_exit()是實現這個系統調用的主要核心功能。
進程切換:進程切換又稱為任務切換、環境切換。它是這樣一種行為,為了控制進程的執行,核心掛起當前在CPU上啟動並執行進程,並恢複以前掛起的某個進程的執行。
跟函數的調用類似,進程切換時,一般要在CPU上裝載要執行進程的進程上下文。進程的硬體上下文指:可執行程式內容相關的一個子集,是進程恢複執行前裝入寄存器的一組資料。其中一部分放在TSS段,即任務狀態段,剩餘部分存放在核心態堆棧中。進程的切換隻發生在核心態,在執行進程切換之前,使用者態進程使用的所有寄存器內容都已儲存在核心態堆棧上。
進程的切換有兩種方法,一種是硬體切換,一種是軟體切換。軟體切換就是利用程式逐步執行切換,它的優點是,可以對切換時裝入的資料進行合法性檢查,執行時間雖與硬體切換大致相同,但仍有可改進的地方。
進程切換使用schedule()函數完成,在本質上,每個進程切換由兩部分組成:1、切換頁全域目錄以安裝一個新的地址空間。2、切換核心態堆棧和硬體上下文,因為硬體上下文提供了核心執行新進程所需要的所有資訊,包括CPU寄存器,主要有switch_to函數完成。
三、進程調度
調度策略:調度策略就是這樣一組規則:決定什麼時候以怎樣的方式選擇一個新進程啟動並執行規則。Linux的調度基於分時技術:多個進程以“時間多工”方式運行,因為CPU的時間被分成“片”,給每個可運行進程分配一片。調度策略也是根據進程的優先順序對它們進行分類。在Linux中,進程的優先順序是動態。發送器跟蹤進程正在做什麼,並周期性地調整它們的優先順序。根據不同的分類標準,可以把進程分成不同的類型。比如可以把一個進程看作是“I/O受限”或“CPU受限”。也可把進程區分為以下三類:互動式進程、批處理進程、即時進程。Linux的進程是搶佔式的,無論是處於核心態還是使用者態。時間片的長短對系統效能是很關鍵的:它既不能太長也不能太短。如果平均時間片太短,由進程切換引起的系統額外開銷就變得非常高。如果平均時間片太長,進程看起來就不再是並發執行的。對時間片大小的選擇始終是一種折中。Linux採用單憑經驗的方法,即選擇儘可能長、同時能保持良好回應時間的一個時間片。
調度演算法:早起的Linux中,調度演算法是根據進程的優先順序選擇“最佳”進程來執行,它的缺點是時間開銷與“可運行進程數量”有關。現代的Linux中,調度演算法可以在固定時間內(與可運行進程數量無關)選中要啟動並執行進程。首先,我們必須知道進程可以分為即時進程與普通進程。每個LInux進程總是按照如下的調度類型被調度:先進先出的即時進程、時間片輪轉的即時進程、普通的分時進程。調度演算法根據進程是普通進程還是即時進程而有很大不同。
普通進程的調度:每個普通進程都有它自己的靜態優先順序(值是從100到139),發送器使用靜態優先順序來估價系統中這個進程與其他普通進程之間調度的程度。靜態優先順序決定進程的基本時間片,即進程用完了以前的時間片時,系統分配給進程的時間片長度。普通進程除了靜態優先順序,還有動態優先順序。動態優先順序是發送器在選擇新進程來啟動並執行時候使用的數。平均睡眠時間是進程在睡眠狀態所消耗的平均納秒數。即使具有較高靜態優先順序的普通進程獲得了較大的CPU時間片,也不應該使靜態優先順序較低的進程無法運行。為了避免這個問題,提出了活動進程和到期進程的概念,活動進程指進程的時間片還未用完,到期進程指進程的時間片已用完,即使到期進程的優先順序更高,也不能繼續運行,除非等到所有活動進程都到期以後。
即時進程的調度:每個即時進程都與一個試試優先順序相關,即時優先順序是一個範圍從1到99的值。跟普通進程不同,即時進程總是被當作活動進程。
發送器所使用的主要資料結構:資料結構runqueue和進程描述符
資料結構runqueue:runqueue資料結構中最重要的欄位是與可運行進程 的鏈表相關的欄位。其中的arrays欄位是活動進程和到期進程的兩個集合,active欄位是指向活動進程鏈表的指標,expired欄位是指向到期進程鏈表的指標。
進程描述符:每個進程描述符都包括幾個與調度相關的欄位。其中的time_slice欄位是在進程的時間片中還剩餘的時鐘節拍數。它由copy_process函數設定:父進程的剩餘節拍數被劃分為兩等分,一份給父進程,一份給子進程。
四、發送器所使用的函數
發送器依靠幾個函數來完成調度工作,其中最重要的函數如下:
try_to_wake_up()函數通過把進程狀態設定為TASK_RUNNING,並把該進程插入本地CPU的運行隊列來喚醒睡眠或停止的進程。
recalc_task_prio()函數更新進程的平均睡眠時間和動態優先順序。
schedule()憾事實現發送器,它的任務時從運行隊列的鏈表中找到一個進程,並隨後將CPU分配給這個進程。schedule()可以由幾個核心控制路徑調用,可以採用直接調用或延遲調用的方式
總結:
Linux核心中的進程管理模組非常重要,它是串連其他4大模組的重要橋樑,它也非常複雜,理解它的一些基本原理,對於理解Linux核心非常重要,上面只是對它進行了一些簡單的描述,並沒有深入到具體實現細節,希望已有有機會能深入分析實現的細節。
轉自:http://blog.csdn.net/tennysonsky/article/details/45168799
Linux系統編程——進程管理