進程調度

進程調度

計算機術語
無論是在批處理系統還是分時系統中,用戶進程數一般都多于處理機數、這将導緻它們互相争奪處理機。另外,系統進程也同樣需要使用處理機。這就要求進程調度程序按一定的策略,動态地把處理機分配給處于就緒隊列中的某一個進程,以使之執行。個進程的上下文(context)包括進程的狀态、有關變量和數據結構的值、機器寄存器的值和PCB以及有關程序、數據等。一個進程的執行是在進程的上下文中執行。當正在執行的進程由于某種原因要讓出處理機時,系統要做進程上下文切換,以使另一個進程得以執行[1]。當進行上下文切換時點統要首先檢查是否允許做上下文切換(在有些情況下,上下文切換是不允許的,例如系統正在執行某個不允許中斷的原語時)。然後,系統要保留有關被切換進程的足夠信息,以便以後切換回該進程時,順利恢複該進程的執行。在系統保留了CPU現場之後,調度程序選擇一個新的處于就緒狀态的進程、并裝配該進程的上下文,使CPU的控制權掌握在被選中進程手中。
  • 中文名:進程調度
  • 外文名:
  • 所屬學科:
  • 基本屬性:多态性
  • 領 域:操作系統
  • 基本狀态:等待态、運行态、就緒态
  • 類 型:計算機術語
  • 處 理:高級

基本屬性

1.多态性從誕生、運行,直至消滅。

2.多個不同的進程可以包括相同的程序

3.三種基本狀态它們之間可進行轉換

4.并發性并發執行的進程輪流占用處理器

基本狀态

1.等待态:等待某個事件的完成;

2.就緒态:等待系統分配處理器以便運行;

3.運行态:占有處理器正在運行。

運行态→等待态往往是由于等待外設,等待主存等資源分配或等待人工幹預而引起的。

等待态→就緒态則是等待的條件已滿足,隻需分配到處理器後就能運行。

運行态→就緒态不是由于自身原因,而是由外界原因使運行狀态的進程讓出處理器,這時候就變成就緒态。例如時間片用完,或有更高優先級的進程來搶占處理器等。

就緒态→運行态系統按某種策略選中就緒隊列中的一個進程占用處理器,此時就變成了運行态

處理機

高級、中級和低級調度作業從提交開始直到完成,往往要經曆下述三級調度:

高級調度:(High-Level Scheduling)又稱為作業調度,它決定把後備作業調入内存運行;

低級調度:(Low-Level Scheduling)又稱為進程調度,它決定把就緒隊列的某進程獲得CPU;

中級調度:(Intermediate-Level Scheduling)又稱為在虛拟存儲器中引入,在内、外存對換區進行進程對換。

方式

非剝奪方式

分派程序一旦把處理機分配給某進程後便讓它一直運行下去,直到進程完成或發生某事件而阻塞時,才把處理機分配給另一個進程。

剝奪方式

當一個進程正在運行時,系統可以基于某種原則,剝奪已分配給它的處理機,将之分配給其它進程。剝奪原則有:優先權原則、短進程優先原則、時間片原則。

例如,有三個進程P1、P2、P3先後到達,它們分别需要20、4和2個單位時間運行完畢。

假如它們就按P1、P2、P3的順序執行,且不可剝奪,則三進程各自的周轉時間分别為20、24、

26個單位時間,平均周轉時間是23.33個時間單位。

假如用時間片原則的剝奪調度方式,可得到:

可見:P1、P2、P3的周轉時間分别為26、10、6個單位時間(假設時間片為2個單位時間),平均周轉時間為14個單位時間。

衡量進程調度性能的指标有:周轉時間、響應時間、CPU-I/O執行期。

算法

先進先出算法

算法總是把處理機分配給最先進入就緒隊列的進程,一個進程一旦分得處理機,便一直執行下去,直到該進程完成或阻塞時,才釋放處理機。

例如,有三個進程P1、P2和P3先後進入就緒隊列,它們的執行期分别是21、6和3個單位時間,

執行情況如下圖:

對于P1、P2、P3的周轉時間為21、27、30,平均周轉時間為26。

可見,FIFO算法服務質量不佳,容易引起作業用戶不滿,常作為一種輔助調度算法。

短進程優先

最短CPU運行期優先調度算法(SCBF--Shortest CPU Burst First)

該算法從就緒隊列中選出下一個“CPU執行期最短”的進程,為之分配處理機。

例如,在就緒隊列中有四個進程P1、P2、P3和P4,它們的下一個執行期分别是16、12、4和3個單位時間,執行情況如下圖:

P1、P2、P3和P4的周轉時間分别為35、19、7、3,平均周轉時間為16。

該算法雖可獲得較好的調度性能,但難以準确地知道下一個CPU執行期,而隻能根據每一個進程的執行曆史來預測。

輪轉法

前幾種算法主要用于批處理系統中,不能作為分時系統中的主調度算法,在分時系統中,都采用時間片輪轉法。

簡單輪轉法:系統将所有就緒進程按FIFO規則排隊,按一定的時間間隔把處理機分配給隊列中的進程。這樣,就緒隊列中所有進程均可獲得一個時間片的處理機而運行。

多級隊列方法:将系統中所有進程分成若幹類,每類為一級。

多級反饋隊列

多級反饋隊列方式是在系統中設置多個就緒隊列,并賦予各隊列以不同的優先權。

實現

引起原因

正在執行的進程執行完畢或因發生某事件而不能再繼續執行;

執行中的進程因提出I/O請求而暫停執行;

在進程通信或同步過程中執行了某種原語操作如P操作、阻塞、挂起原語等;

在可剝奪式調度中,有比當前進程優先權更高的進程進入就緒隊列;

在時間片輪轉法中,時間片完;

△通常系統是按先來先服務或優先權形式來組織調度隊列。

其中,RQ為就緒隊列指針,EP為運行隊列指針。

功能

記錄系統中所有進程的執行情況

進程調度的具體功能可總結為如下幾點:作為進程調度的準備,進程管理模塊必須将系統中各進程的執行情況和狀态特征記錄在各進程的PCB表中。并且,根據各進程的狀态特征和資源需求等、進程管理模塊還将各進程的PCB表排成相應的隊列并進行動态隊列轉接。進程調度模塊通過PCB變化來掌握系統中存在的所有進程的執行情況和狀态特征,并在适當的時機從就緒隊列中選擇出一個進程占據處理機。

選擇占有處理機的進程

進程調度的主要功能是按照一定的策略選擇—個處于就緒狀态的進程,使其獲得處理機執行。根據不同的系統設計目的,有各種各樣的選擇策略,例如系統開銷較少的靜态優先數調度法,适合于分時系統的輪轉法(Round RoLin)和多級互饋輪轉法(Round Robin with Multip1e feedback)等。這些選擇策略決定了調度算法的性能。

進行進程上下文切換

—個進程的上下文(context)包括進程的狀态、有關變量和數據結構的值、機器寄存器的值和PCB以及有關程序、數據等。一個進程的執行是在進程的上下文中執行。當正在執行的進程由于某種原因要讓出處理機時,系統要做進程上下文切換,以使另一個進程得以執行。當進行上下文切換時點統要首先檢查是否允許做上下文切換(在有些情況下,上下文切換是不允許的,例如系統正在執行某個不允許中斷的原語時)。然後,系統要保留有關被切換進程的足夠信息,以便以後切換回該進程時,順利恢複該進程的執行。在系統保留了CPU現場之後,調度程序選擇一個新的處于就緒狀态的進程、并裝配該進程的上下文,使CPU的控制權掌握在被選中進程手中。

時機

引起進程調度的原因有以下幾類

進程調度發生在什麼時機呢?這與引起進程調度的原因以及進程調度的方式有關。

(1)正在執行的進程執行完畢。這時,如果不選擇新的就緒進程執行,将浪費處理機資源。

(2)執行中進程自己調用阻塞原語将自己阻塞起來進入睡眠等狀态。

(3)執行中進程調用了P原語操作,從而因資源不足而被阻塞;或調用了v原語操作激活了等待資源的進程隊列。

(4)執行中進程提出I/O請求後被阻塞。

(5)在分時系統中時間片已經用完。

(6)在執行完系統調用等系統程序後返回用戶進程時,這時可看作系統進程執行完畢,從而可調度選擇一新的用戶進程執行。

以上都是在可剝奪方式下的引起進程調度的原因。在CPU執行方式是可剝奪時.還有

(7)就緒隊列中的某進程的優先級變得高于當前執行進程的優先級,從而也将引發進程調度。

兩種占用CPU的方式

可剝奪式(可搶占式preemptive):就緒隊列中一旦有優先級高于當前執行進程優先級的進程存在時,便立即發生進程調度,轉讓處理機。

不可剝奪式(不可搶占式non_preemptive):即使在就緒隊列存在有優先級高于當前執行進程時,當前進程仍将占用處理機直到該進程自己因調用原語操作或等待I/O而進入阻塞、睡眠狀态,或時間片用完時才重新發生調度讓出處理機。

上下切換

進程上下文由正文段、數據段、硬件寄存器的内容以及有關數據結構等組成。硬件寄存器主要包括存放CPU将要執行的下條指令虛拟地址的程序計數器PC,指出機器與進程相關聯的硬件狀态的處理機狀态寄存器PS,存放過程調用(或系統調用)時所傳遞參數的通用寄存器R以及堆棧指針寄存器S等。數據結構則包括PCB等在内的所有與執行該進程有關的管理和控制用表格、數組、鍊等。在發生進程調度時系統要做進程上下文切換。

在進程(上下文)中切換的步驟

n保存處理器的上下文,包括程序計數器和其它寄存器

n用新狀态和其它相關信息更新正在運行進程的PCB

n把原來的進程移至合适的隊列-就緒、阻塞

n選擇另一個要執行的進程

n更新被選中進程的PCB

n從被選中進程中重裝入

CPU上下文

性能評價

進程調度雖然是在系統内部的低級調度,但進程調度的優劣直接影響作業調度的性能。那麼,怎樣評價進程調度的優劣呢?反映作業調度優劣的周轉時間和平均周轉時間隻在某種程度上反映了進程調度的性能,例如,其執行時間部分中實際上包含有進程等待(包括就緒狀态時的等待)時間,而進程等待時間的多少是要依靠進程調度策略和等待事件何時發生等來決定的。因此,進程調度性能的商量是操作系統設計的一個重要指标。

我們說進程調度性能的衡量方法可分為定形和定量兩種。在定形衡量方面,首先是調度的可靠性。包括一次進程調度是否可能引起數據結構的破壞等。這要求我們對調度時機的選擇和保存CPU現場十分謹慎。另外,簡潔性也是衡量進程調度的一個重要指标,由于調度程序的執行涉及到多個進程和必須進行上下文切換,如果調度程序過于繁瑣和複雜,将會耗去較大的系統開銷。這在用戶進程調用系統調用較多的情況下,将會造成響應時間大幅度增加。

進程調度的定量評價包括CPU的利用率評價、進程在就緒隊列中的等待時間與執行時間之比等。實際上由于進程進入就緒隊列的随機模型很難确定,而且進程上下文切換等也将影響進程的執行效率,LL而對進程調度進行解析是很困難的。一般情況下,大多利用模拟或測試系統響應時間的方法來評價進程調度的性能。

實時系統

綜述

實時系統與其他操作系統不同在于計算機要能及時響應外部事件的請求,在規定的嚴格時間内完成對該事件的處理,并控制所有實時設備和實時任務協調一緻地工作,對于對時間要求嚴格性的不同,實時系統又分為硬實時系統和軟實時系統,其中硬實時系統是指這種時限的要求是絕對的,任何一個實時任務都能夠在時限之前完成;而軟實時系統的要求就沒有這麼嚴格,允許偶爾有實時任務不滿足時限的要求.實時系統一般用于嵌入式的系統中,分為實時過程控制和實時通信處理,其中實時過程控制主要用于工業控制,軍事控制領域;時事通信用于電信,銀行,飛機訂票等領域,正是由于在這些特殊領域的運用使得實時操作系統設計時主要追求的是:對外部請求在嚴格時間範圍内做出反應,有高可靠性和完備性.為達到時間要求,進程的調度策略就顯得尤為重要.

優先級

最簡單最直觀的進程調度策略是基于優先級的調度,多數實時系統采用基于優先級的調度,每個進程根據它重要程度的不同被賦予不同的優先級,調度器在每次調度時,總選擇優先級最高的進程開始執行.

首先要考慮的問題是如何分配優先級,對于進程優先級的分配可以采用靜态和動态兩種方式,靜态優先級調度算法:這種調度算法給那些系統中得到運行的所有進程都靜态地分配一個優先級.靜态優先級的分配可以根據應用的屬性來進行,比如進程的周期,用戶優先級,或者其它的預先确定的策略.單調率算法(RM)調度算法是一種典型的靜态優先級調度算法,它根據進程的執行周期的長短來決定調度優先級,那些具有小的執行周期的進程具有較高的優先級.動态優先級調度算法:這種調度算法根據進程的資源需求來動态地分配進程的優先級,其目的就是在資源分配和調度時有更大的靈活性.在實時系統中,最早期限優先算法(EDF)算法是使用最多的一種動态優先級調度算法,該算法給就緒隊列中的各個進程根據它們的截止期限(Deadline)來分配優先級,具有最近的截止期限的進程具有最高的優先級.

分配好優先級之後下一個要考慮的問題是何時讓高優先級進程掌握CPU的使用權,這取決于操作系統的内核,有不可搶占式和可搶占式兩種.

不可搶占式内核要求每個進程自我放棄CPU的所有權,各個進程彼此合作共享一個CPU.異步事件還是由中斷服務來處理.中斷服務可以使一個高優先級的進程由挂起狀态變為就緒狀态.但中斷服務以後控制權還是回到原來被中斷了的那個進程,直到該進程主動放棄CPU的使用權時,那個高優先級的進程才能獲得CPU的使用權.這就出現了響應時間的問題,高優先級的進程已經進入了就緒狀态但不能執行,這樣進程的響應時間變得不再确定這與實時系統的要求不符,因此一般的實時操作系統都要求是可搶占式的内核,當一個運行着的進程使一個比它優先級高的進程進入了就緒态,當前進程的CPU使用權就被剝奪了,或者說被挂起了,那個高優先級的進程立刻得到了CPU的控制權,如果是中斷服務子程序使一個高優先級的進程進入就緒态,中斷完成時,中斷了的進程被挂起,優先級高的那個進程開始運行.在這種内核設置下,多個進程可能處于并發的狀态,這就出現了多個進程共享資源的情況,因此我們需要設置信号量來保證臨界資源的正确使用,任何一個想使用臨界資源的進程在進入臨界區之前必須擁有使用臨界資源的信号量,否則不可以執行臨界區代碼.

這樣基于優先級的可搶占式進程調度策略就基本架構完成,但此時仍然有系統崩潰的危險,假設系統中有3個進程,分别為p1,p2和p3.p1的優先權高于p2,而p2的優先權高于p3.恰在此時p1和p2因某種原因被阻塞,這時候系統調度p3執行.p3執行一段時間後,p1被喚醒.由于采取的是PBP的調度策略,因此p1搶占p3的CPU,p1執行.p1執行一段時間後要進入臨界區,但此時p3占有此臨界資源的信号量.因此p1被阻塞,處于等待狀态,等待p3釋放此信号量.經過這麼一段時間後,p2此時此刻處于就緒狀态.因此系統調度p2執行.如果p3在p2的執行期間一直沒有能夠被調度執行的話,那p1和p3将一直等到p2執行完後才能執行,p1更要等到p3釋放它所把持的信号量才能執行;而這段時間完全有可能超出p1的Deadline,使得p1崩潰.我們看到在這個過程中,由于臨界資源的使用問題使得優先級低的進程先于優先級高的進程先執行,這就出現了優先級反轉的問題,從而造成了系統崩潰,對于這個問題可以采用優先級繼承的辦法來進行解決.在優先級繼承方案中,當高優先級進程在等待低優先級的進程占有的信号量時,讓低優先級進程繼承高優先級進程的優先級,即把低優先級進程的優先權提高到高優先級進程的優先級;當低優先級進程釋放高優先級進程等待的信号量時,立即把其優先權降低到原來的優先權.采用這種方法可以有效地解決上面所述的優先權反轉的問題.當高優先級進程p1想要進入臨界區時,由于低優先級進程p3占有這個臨界資源的信号量,導緻p1被阻塞.這時候,系統把p3的優先權升到p1的優先權,此時優先權處于p1和p3之間的進程p2,即使處于就緒狀态也不可以被調度執行,因為此時p3的優先權已經高于p2,所以p3此時被調度執行.當p3釋放p1需要的信号量時,系統立即把p3的優先權降到原來的高度,來保證p1和p2正常有序執行,有許多實時系統是采用這種方法來防止優先級反轉的,如VXWORKS.

比例共享

雖然基于優先級的調度簡單而且易于實現,是目前使用最廣泛的實時系統的進程調度策略,但對于一些軟實時系統而言這種方法不再适用,比如實時多媒體會議,在這種情況下我們可以選擇基于共享的進程調度算法,其基本思想就是按照一定的權重(比例)對一組需要調度的進程進行調度,讓它們的執行時間與它們的權重完全成正比.我們可以通過兩種方法來實現比例共享調度算法:第一種方法是調節各個就緒進程出現在調度隊列隊首的頻率,并調度隊首的進程執行;第二種做法就是逐次調度就緒隊列中的各個進程投入運行,但根據分配的權重調節分配個每個進程的運行時間片.比例共享調度算法的一個問題就是它沒有定義任何優先級的概念;所有的進程都根據它們申請的比例共享CPU資源,當系統處于過載狀态時,所有的進程的執行都會按比例地變慢.所以為了保證系統中實時進程能夠獲得一定的CPU處理時間,一般采用一種動态調節進程權重的方法.

時間

對于那些具有穩定,已知輸入的簡單系統,可以使用時間驅動的調度算法,它能夠為數據處理提供很好的預測性.這種調度算法本質上是一種設計時就确定下來的離線的靜态調度方法.在系統的設計階段,在明确系統中所有的處理情況下,對于各個進程的開始,切換,以及結束時間等就事先做出明确的安排和設計.這種調度算法适合于那些很小的嵌入式系統,自控系統,傳感器等應用環境.這種調度算法的優點是進程的執行有很好的可預測性,但最大的缺點是缺乏靈活性,并且會出現有進程需要被執行而CPU卻保持空閑的情況.

對于不同要求下的實時系統可以采用不同的進程調度策略來進行設計,也可以将這些方法進行綜合之後得到更适合的調度策略.

Linux原理

依據

調度程序運行時,要在所有可運行狀态的進程中選擇最值得運行的進程投入運行。選擇進程的依據是什麼呢?在每個進程的task_struct結構中有以下四項:policy、priority、counter、rt_priority。這四項是選擇進程的依據。其中,policy是進程的調度策略,用來區分實時進程和普通進程,實時進程優先于普通進程運行;priority是進程(包括實時和普通)的靜态優先級;counter是進程剩餘的時間片,它的起始值就是priority的值;由于counter在後面計算一個處于可運行狀态的進程值得運行的程度goodness時起重要作用,因此,counter也可以看作是進程的動态優先級。rt_priority是實時進程特有的,用于實時進程間的選擇。

調度方法

1,SCHED_OTHER分時調度策略,

2,SCHED_FIFO實時調度策略,先到先服務

3,SCHED_RR實時調度策略,時間片輪轉

實時進程将得到優先調用,實時進程根據實時優先級決定調度權值,分時進程則通過nice和counter值決定權值,nice越小,counter越大,被調度的概率越大,也就是曾經使用了cpu最少的進程将會得到優先調度。

SHCED_RR和SCHED_FIFO的不同:

當采用SHCED_RR策略的進程的時間片用完,系統将重新分配時間片,并置于就緒隊列尾。放在隊列尾保證了所有具有相同優先級的RR任務的調度公平。

SCHED_FIFO一旦占用cpu則一直運行。一直運行直到有更高優先級任務到達或自己放棄。

如果有相同優先級的實時進程(根據優先級計算的調度權值是一樣的)已經準備好,FIFO時必須等待該進程主動放棄後才可以運行這個優先級相同的任務。而RR可以讓每個任務都執行一段時間。

相同點:

RR和FIFO都隻用于實時任務。

創建時優先級大于0(1-99)。

按照可搶占優先級調度算法進行。

就緒态的實時任務立即搶占非實時任務。

所有任務都采用linux分時調度策略時。

1,創建任務指定采用分時調度策略,并指定優先級nice值(-20~19)。

2,将根據每個任務的nice值确定在cpu上的執行時間(counter)。

3,如果沒有等待資源,則将該任務加入到就緒隊列中。

4,調度程序遍曆就緒隊列中的任務,通過對每個任務動态優先級的計算(counter+20-nice)結果,選擇計算結果最大的一個去運行,當這個時間片用完後(counter減至0)或者主動放棄cpu時,該任務将被放在就緒隊列末尾(時間片用完)或等待隊列(因等待資源而放棄cpu)中。

5,此時調度程序重複上面計算過程,轉到第4步。

6,當調度程序發現所有就緒任務計算所得的權值都為不大于0時,重複第2步。

所有任務都采用FIFO時,

1,創建進程時指定采用FIFO,并設置實時優先級rt_priority(1-99)。

2,如果沒有等待資源,則将該任務加入到就緒隊列中。

3,調度程序遍曆就緒隊列,根據實時優先級計算調度權值(1000+rt_priority),選擇權值最高的任務使用cpu,該FIFO任務将一直占有cpu直到有優先級更高的任務就緒(即使優先級相同也不行)或者主動放棄(等待資源)。

4,調度程序發現有優先級更高的任務到達(高優先級任務可能被中斷或定時器任務喚醒,再或被當前運行的任務喚醒,等等),則調度程序立即在當前任務堆棧中保存當前cpu寄存器的所有數據,重新從高優先級任務的堆棧中加載寄存器數據到cpu,此時高優先級的任務開始運行。重複第3步。

5,如果當前任務因等待資源而主動放棄cpu使用權,則該任務将從就緒隊列中删除,加入等待隊列,此時重複第3步。

所有任務都采用RR調度策略時

1,創建任務時指定調度參數為RR,并設置任務的實時優先級和nice值(nice值将會轉換為該任務的時間片的長度)。

2,如果沒有等待資源,則将該任務加入到就緒隊列中。

3,調度程序遍曆就緒隊列,根據實時優先級計算調度權值(1000+rt_priority),選擇權值最高的任務使用cpu。

4,如果就緒隊列中的RR任務時間片為0,則會根據nice值設置該任務的時間片,同時将該任務放入就緒隊列的末尾。重複步驟3。

5,當前任務由于等待資源而主動退出cpu,則其加入等待隊列中。重複步驟3。

系統中既有分時調度,又有時間片輪轉調度和先進先出調度

1,RR調度和FIFO調度的進程屬于實時進程,以分時調度的進程是非實時進程。

2,當實時進程準備就緒後,如果當前cpu正在運行非實時進程,則實時進程立即搶占非實時進程。

3,RR進程和FIFO進程都采用實時優先級做為調度的權值标準,RR是FIFO的一個延伸。FIFO時,如果兩個進程的優先級一樣,則這兩個優先級一樣的進程具體執行哪一個是由其在隊列中的未知決定的,這樣導緻一些不公正性(優先級是一樣的,為什麼要讓你一直運行?),如果将兩個優先級一樣的任務的調度策略都設為RR,則保證了這兩個任務可以循環執行,保證了公平。

策略

調度程序運行時,要在所有處于可運行狀态的進程之中選擇最值得運行的進程投入運行。選擇進程的依據是什麼呢?在每個進程的task_struct結構中有這麼四項:

policy,priority,counter,rt_priority

這四項就是調度程序選擇進程的依據.其中,policy是進程的調度策略,用來區分兩種進程-實時和普通;priority是進程(實時和普通)的優先級;counter是進程剩餘的時間片,它的大小完全由priority決定;rt_priority是實時優先級,這是實時進程所特有的,用于實時進程間的選擇。

首先,Linux根據policy從整體上區分實時進程和普通進程,因為實時進程和普通進程度調度是不同的,它們兩者之間,實時進程應該先于普通進程而運行,然後,對于同一類型的不同進程,采用不同的标準來選擇進程:

對于普通進程,Linux采用動态優先調度,選擇進程的依據就是進程counter的大小。進程創建時,優先級priority被賦一個初值,一般為0~70之間的數字,這個數字同時也是計數器counter的初值,就是說進程創建時兩者是相等的。字面上看,priority是“優先級”、counter是“計數器”的意思,然而實際上,它們表達的是同一個意思-進程的“時間片”。Priority代表分配給該進程的時間片,counter表示該進程剩餘的時間片。在進程運行過程中,counter不斷減少,而priority保持不變,以便在counter變為0的時候(該進程用完了所分配的時間片)對counter重新賦值。當一個普通進程的時間片用完以後,并不馬上用priority對counter進行賦值,隻有所有處于可運行狀态的普通進程的時間片(p->;;counter==0)都用完了以後,才用priority對counter重新賦值,這個普通進程才有了再次被調度的機會。這說明,普通進程運行過程中,counter的減小給了其它進程得以運行的機會,直至counter減為0時才完全放棄對CPU的使用,這就相對于優先級在動态變化,所以稱之為動态優先調度。至于時間片這個概念,和其他不同操作系統一樣的,Linux的時間單位也是“時鐘滴答”,隻是不同操作系統對一個時鐘滴答的定義不同而已(Linux為10ms)。進程的時間片就是指多少個時鐘滴答,比如,若priority為20,則分配給該進程的時間片就為20個時鐘滴答,也就是20*10ms=200ms。Linux中某個進程的調度策略(policy)、優先級(priority)等可以作為參數由用戶自己決定,具有相當的靈活性。内核創建新進程時分配給進程的時間片缺省為200ms(更準确的,應為210ms),用戶可以通過系統調用改變它。

對于實時進程,Linux采用了兩種調度策略,即FIFO(先來先服務調度)和RR(時間片輪轉調度)。因為實時進程具有一定程度的緊迫性,所以衡量一個實時進程是否應該運行,Linux采用了一個比較固定的标準。實時進程的counter隻是用來表示該進程的剩餘時間片,并不作為衡量它是否值得運行的标準,這和普通進程是有區别的。上面已經看到,每個進程有兩個優先級,實時優先級就是用來衡量實時進程是否值得運行的。

這一切看來比較麻煩,但實際上Linux中的實現相當簡單。Linux用函數goodness()來衡量一個處于可運行狀态的進程值得運行的程度。該函數綜合了上面提到的各個方面,給每個處于可運行狀态的進程賦予一個權值(weight),調度程序以這個權值作為選擇進程的唯一依據。

Linux根據policy的值将進程總體上分為實時進程和普通進程,提供了三種調度算法:一種傳統的Unix調度程序和兩個由POSIX.1b(原名為POSIX.4)操作系統标準所規定的“實時”調度程序。但這種實時隻是軟實時,不滿足諸如中斷等待時間等硬實時要求,隻是保證了當實時進程需要時一定隻把CPU分配給實時進程。

非實時進程有兩種優先級,一種是靜态優先級,另一種是動态優先級。實時進程又增加了第三種優先級,實時優先級。優先級是一些簡單的整數,為了決定應該允許哪一個進程使用CPU的資源,用優先級代表相對權值-優先級越高,它得到CPU時間的機會也就越大。

?靜态優先級(priority)-不随時間而改變,隻能由用戶進行修改。它指明了在被迫和其他進程競争CPU之前,該進程所應該被允許的時間片的最大值(但很可能的,在該時間片耗盡之前,進程就被迫交出了CPU)。

?動态優先級(counter)-隻要進程擁有CPU,它就随着時間不斷減小;當它小于0時,标記進程重新調度。它指明了在這個時間片中所剩餘的時間量。

?實時優先級(rt_priority)-指明這個進程自動把CPU交給哪一個其他進程;較高權值的進程總是優先于較低權值的進程。如果一個進程不是實時進程,其優先級就是0,所以實時進程總是優先于非實時進程的(但實際上,實時進程也會主動放棄CPU)。

當policy分别為以下值時:

(1)SCHED_OTHER:這是普通的用戶進程,進程的缺省類型,采用動态優先調度策略,選擇進程的依據主要是根據進程goodness值的大小。這種進程在運行時,可以被高goodness值的進程搶先。

(2)SCHED_FIFO:這是一種實時進程,遵守POSIX1.b标準的FIFO(先入先出)調度規則。它會一直運行,直到有一個進程因I/O阻塞,或者主動釋放CPU,或者是CPU被另一個具有更高rt_priority的實時進程搶先。在Linux實現中,SCHED_FIFO進程仍然擁有時間片-隻有當時間片用完時它們才被迫釋放CPU。因此,如同POSIX1.b一樣,這樣的進程就象沒有時間片(不是采用分時)一樣運行。Linux中進程仍然保持對其時間片的記錄(不修改counter)主要是為了實現的方便,同時避免在調度代碼的關鍵路徑上出現條件判斷語句 if (!(current->;;policy&;;SCHED_FIFO)){...}-要知道,其他大量非FIFO進程都需要記錄時間片,這種多餘的檢測隻會浪費CPU資源。(一種優化措施,不該将執行時間占10%的代碼的運行時間減少到50%;而是将執行時間占90%的代碼的運行時間減少到95%。0.9+0.1*0.5=0.95>;;0.1+0.9*0.9=0.91)

(3)SCHED_RR:這也是一種實時進程,遵守POSIX1.b标準的RR(循環round-robin)調度規則。除了時間片有些不同外,這種策略與SCHED_FIFO類似。當SCHED_RR進程的時間片用完後,就被放到SCHED_FIFO和SCHED_RR隊列的末尾。

隻要系統中有一個實時進程在運行,則任何SCHED_OTHER進程都不能在任何CPU運行。每個實時進程有一個rt_priority,因此,可以按照rt_priority在所有SCHED_RR進程之間分配CPU。其作用與SCHED_OTHER進程的priority作用一樣。隻有root用戶能夠用系統調用sched_setscheduler,來改變當前進程的類型(sys_nice,sys_setpriority)。

此外,内核還定義了SCHED_YIELD,這并不是一種調度策略,而是截取調度策略的一個附加位。如同前面說明的一樣,如果有其他進程需要CPU,它就提示調度程序釋放CPU。特别要注意的就是這甚至會引起實時進程把CPU釋放給非實時進程。

主要函數

真正執行調度的函數是schedule(void),它選擇一個最合适的進程執行,并且真正進行上下文切換,使得選中的進程得以執行。而reschedule_idle(struct task_struct*p)的作用是為進程選擇一個合适的CPU來執行,如果它選中了某個CPU,則将該CPU上當前運行進程的need_resched标志置為1,然後向它發出一個重新調度的處理機間中斷,使得選中的CPU能夠在中斷處理返回時執行schedule函數,真正調度進程p在CPU上執行。在schedule()和reschedule_idle()中調用了goodness()函數。goodness()函數用來衡量一個處于可運行狀态的進程值得運行的程度。此外,在schedule()函數中還調用了schedule_tail()函數;在reschedule_idle()函數中還調用了reschedule_idle_slow()。這些函數的實現對理解SMP的調度非常重要,下面一一分析這些函數。先給出每個函數的主要流程圖,然後給出源代碼,并加注釋。

goodness()函數分析

goodness()函數計算一個處于可運行狀态的進程值得運行的程度。一個任務的goodness是以下因素的函數:正在運行的任務、想要運行的任務、當前的CPU。goodness返回下面兩類值中的一個:1000以下或者1000以上。1000或者1000以上的值隻能賦給“實時”進程,從0到999的值隻能賦給普通進程。實際上,在單處理器情況下,普通進程的goodness值隻使用這個範圍底部的一部分,從0到41。在SMP情況下,SMP模式會優先照顧等待同一個處理器的進程。不過,不管是UP還是SMP,實時進程的goodness值的範圍是從1001到1099。

goodness()函數其實是不會返回-1000的,也不會返回其他負值。由于idle進程的counter值為負,所以如果使用idle進程作為參數調用goodness,就會返回負值,但這是不會發生的。

goodness()是個簡單的函數,但是它是linux調度程序不可缺少的部分。運行隊列中的每個進程每次執行schedule時都要調度它,因此它的執行速度必須很快。

//在/kernel/sched.c中

static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)

{ int weight;

if (p->;;policy != SCHED_OTHER) {/*如果是實時進程,則*/

weight = 1000 + p->;;rt_priority;

goto out;

}

/* 将counter的值賦給weight,這就給了進程一個大概的權值,counter中的值表示進程在一個時間片内,剩下要運行的時間.*/

weight = p->;;counter;

if (!weight) /* weight==0,表示該進程的時間片已經用完,則直接轉到标号out*/

goto out;

#ifdef __SMP__

/*在SMP情況下,如果進程将要運行的CPU與進程上次運行的CPU是一樣的,則最有利,因此,假如進程上次運行的CPU與當前CPU一緻的話,權值加上PROC_CHANGE_PENALTY,這個宏定義為20。*/

if (p->;;processor == this_cpu)

weight += PROC_CHANGE_PENALTY;

#endif

if (p->;;mm == this_mm) /*進程p與當前運行進程,是同一個進程的不同線程,或者是共享地址空間的不同進程,優先選擇,權值加1*/

weight += 1;

weight += p->;;priority; /* 權值加上進程的優先級*/

out:

return weight; /* 返回值作為進程調度的唯一依據,誰的權值大,就調度誰運行*/

}

schedule()函數分析

schedule()函數的作用是,選擇一個合适的進程在CPU上執行,它僅僅根據'goodness'來工作。對于SMP情況,除了計算每個進程的加權平均運行時間外,其他與SMP相關的部分主要由goodness()函數來體現。

流程:

①将prev和next設置為schedule最感興趣的兩個進程:其中一個是在調用schedule時正在運行的進程(prev),另外一個應該是接着就給予CPU的進程(next)。注意:prev和next可能是相同的-schedule可以重新調度已經獲得cpu的進程.

②中斷處理程序運行“下半部分”.

③内核實時系統部分的實現,循環調度程序(SCHED_RR)通過移動“耗盡的”RR進程-已經用完其時間片的進程-到隊列末尾,這樣具有相同優先級的其他RR進程就可以獲得CPU了。同時,這補充了耗盡進程的時間片。

④由于代碼的其他部分已經決定了進程必須被移進或移出TASK_RUNNING狀态,所以會經常使用schedule,例如,如果進程正在等待的硬件條件已經發生,所以如果必要,這個switch會改變進程的狀态。如果進程已經處于TASK_RUNNING狀态,它就無需處理了。如果它是可以中斷的(等待信号),并且信号已經到達了進程,就返回TASK_RUNNING狀态。在所以其他情況下(例如,進程已經處于TASK_UNINTERRUPTIBLE狀态了),應該從運行隊列中将進程移走。

⑤将p初始化為運行隊列的第一個任務;p會遍曆隊列中的所有任務。

⑥c記錄了運行隊列中所有進程最好的“goodness”-具有最好“goodness”的進程是最易獲得CPU的進程。goodness的值越高越好。

⑦遍曆執行任務鍊表,跟蹤具有最好goodness的進程。

⑧這個循環中隻考慮了唯一一個可以調度的進程。在SMP模式下,隻有任務不在cpu上運行時,即can_schedule宏返回為真時,才會考慮該任務。在UP情況下,can_schedule宏返回恒為真.

⑨如果循環結束後,得到c的值為0。說明運行隊列中的所有進程的goodness值都為0。goodness的值為0,意味着進程已經用完它的時間片,或者它已經明确說明要釋放CPU。在這種情況下,schedule要重新計算進程的counter;新counter的值是原來值的一半加上進程的靜态優先級(priortiy),除非進程已經釋放CPU,否則原來counter的值為0。因此,schedule通常隻是把counter初始化為靜态優先級。(中斷處理程序和由另一個處理器引起的分支在schedule搜尋goodness最大值時都将增加此循環中的計數器,因此由于這個原因計數器可能不會為0。顯然,這很罕見。)在counter的值計算完成後,重新開始執行這個循環,找具有最大goodness的任務。

⑩如果schedule已經選擇了一個不同于前面正在執行的進程來調度,那麼就必須挂起原來的進程并允許新的進程運行。這時調用switch_to來進行切換。

相關詞條

相關搜索

其它詞條