CPU調度 (CPU scheduling):多個進程同時處於內存,當一個進程必須等待時,OS從該進程拿走CPU使用權交給其他進程。
進程執行從一個IO區間(I/O burst)開始,隨后進入一個CPU區間(CPU burst)並反復,進程循環地在CPU執行和I/O等待兩個狀態間切換,直到通過系統請求終止最后一個CPU burst。

CPU burst的長度隨進程和計算機的不同而變化,通常具有大量短CPU burst和少量長CPU burst。I/O約束程序通常具有很多短CPU burst,CPU約束程序可能有少量長CPU burst。
每當CPU空閑時,OS就使用短期調度程序(short-term scheduler) (或CPU scheduler)從就緒隊列中選擇一個能夠執行的進程並為它分配CPU。
就緒隊列不一定是FIFO隊列。就緒隊列中的記錄通常為PCB。
搶占&非搶占
CPU調度決策發生在4種情況下:
1) 進程從運行(running)狀態切換到等待(waiting)狀態;
2) 進程從運行(running)狀態切換到就緒(ready)狀態;
3) 進程從等待(waiting)狀態切換到就緒(ready)狀態;
4) 進程終止
非搶占(nonpreemptive)調度方案:a.k.a. 協作(cooperative)調度方案,一旦CPU分配給一個進程,該進程會一直使用CPU直到進程終止或切換到等待狀態,該方案中調度只發生在1、4兩種情況下。
否則稱為搶占(preemptive)調度方案。
分派程序
分派程序(dispatcher):每次進程切換時都要使用的一個模塊,用於將CPU控制交給由short-term scheduler 所選的進程。功能包括1)切換上下文;2)切換到用戶模式;3)跳轉用戶程序的合適位置來重啟程序。
分派延遲(dispatch latency):dispatcher停止一個進程並啟動另一個進程所用的時間。
調度准則
用於分析比較CPU調度算法的准則可包括
·CPU使用率(CPU utilization):理論上為0%~100%,真實系統一般為40%~90%。
·吞吐量(Throughput):一個時間單位內所完成進程的數量。
·周轉時間(Turnaround time):一個進程從提交到完成的所用時間。
·等待時間(Waiting time):進程在就緒隊列中等待所用時間之和。
·響應時間(response time):從提交請求到產生第一響應的所用時間。
需要使CPU utilization和throughput最大化,turnaround time、waiting time和response time最小化。絕大多數情況下需要優化平均值,有些情況下需要優化最大值、最小值或response time方差等。
調度算法
FCFS (最簡單,但會讓短進程等待時間非常長)
先到先服務調度算法(first-come, first-served (FCFS) scheduling algorithm):先請求CPU的進程先分配到CPU,通常用FIFO隊列實現。 [nonpreemptive]
FCFS策略平均等待時間通常較長,不適用於time-sharing系統。
護航效果(convoy effect):所有其他進程都等待一個大進程釋放CPU,相比讓較短進程最先執行的情況,CPU和設備的使用率更低。
SJF (提供最短平均等待時間)
最短作業有限調度算法(shortest-job-first (SJF) scheduling algorithm):每個進程與其下一個CPU burst關聯。當CPU空閑時,將它分配給具有最短CPU burst的進程。[preemptive or nonpreemptive]
更適當的術語表示應該是 “the shortest-next-CPU-burst algorithm”,命名為SJF主要是因為慣例。
難點:如何知道下一CPU burst的長度。
1) 對於(batch system中的)long-term job scheduling,可將用戶提交job時所指定的process time limit作為長度。
2) 對於short-term CPU scheduling,下一CPU burst的長度無法知道。可對下一CPU burst長度進行預測,選擇具有最短預測CPU burst的進程。
下一CPU burst通常預測為以前CPU burst區間測量長度的指數平均(exponential average)。
τn+1 = αtn + (1-α) τn
tn – 第n個CPU burst的長度(記錄最近信息);
τn – 第n個CPU burst的預測值(記錄過去歷史);
τn+1 – 下一CPU burst的預測值;
α – 控制最近和過去歷史在預測中的相對加權,0≤α≤1。
preemptive的SJF調度 – 最短剩余時間優先調度(shortest-remaining-time-first scheduling),如果到達就緒隊列的新進程有比當前運行進程更短的CPU burst,可搶占CPU。
Nonpreemptive的SJF調度 – 允許當前運行的進程先完成。
優先級調度
優先級調度算法(priority scheduling algorithm):每個進程都與一個優先級(priority)關聯,CPU被分配給具有最高priority的進程,相同priority的進程按FCFS順序調度。[preemptive or nonpreemptive]
SJF是優先級調度的一個特例,其priority為下一CPU burst的倒數。
Priority通常為固定區間的數字,有的系統用小數字表示低priority,有的系統則用小數字表示高priority。
Priority可通過內部或外部定義。
內部priority通過對進程的測量數據(e.g. 時間極限、內存要求、打開文件數量、平均I/O burst和平均CPU burst之比)計算來定義;
外部priority通過OS之外的准則(e.g. 進程重要性、使用計算機的費用類型和數量、贊助單位等因素)。
Preemptive的priority調度 – 如果到達就緒隊列的新進程有比當前運行進程更高的priority,可搶占CPU。
Nonpreemptive的priority調度 – 將新進程加到就緒隊列的頭部。
優先級調度和SJF調度會產生飢餓,可用老化技術解決。
Problem – 無窮阻塞(indefinite blocking)/飢餓(starvation):某個低priority進程可能會無窮等待CPU。
Solution — 老化(aging):逐漸增加在系統中等待很長時間的進程的priority,使得最終該進程擁有最高priority並能執行。
RR (適合分時/交互系統)
時間片(time quantum, a.k.a. time slice):一個較小的時間單元,通常為10~100ms。
輪轉法調度算法(round-robin (RR) scheduling algorithm):專門為time-sharing系統設計,CPU調度程序循環就緒隊列,為每個進程分配不超過一個time quantum的CPU。[preemptive]
實現
CPU調度程序每次從FIFO就緒隊列中選擇第一個進程,設置定時器在一個time quantum后終端,再dispatch該進程。
1) 如果當前運行進程的CPU burst短於time quantum,則CPU由進程自動釋放,調度程序再處理下一個進程;
2) 如果當前運行進程的CPU burst長於time quantum,則到點后定時器向OS發送中斷,執行上下文切換,進程被加入到就緒隊列尾部,CPU調度程序再處理下一個進程。(被搶占)
采用RR的平均waiting time通常較長。
主要問題:選擇時間片。RR算法的性能非常依賴於time quantum的大小。
如果time quantum太大,則與FCFS一樣;
如果time quantum很小,則因上下文切換而引起的調度開銷過大。
Time quantum應該比context switch時間長。
處理器共享(processor sharing):如果time quantum很小,則RR可產生“n個進程都有它自己的處理器,各自速度都為真正處理器速度的1/n”的效果。
多級隊列調度(允許多個不同算法用於各種類型的進程)
多級隊列調度算法(multilevel queue scheduling algorithm):將就緒隊列划分成多個獨立隊列,每個隊列有自己的調度算法。進程根據自身屬性被永久分配到對應的一個隊列。
常用模型:前台交互隊列使用RR,和后台批處理隊列使用FCFS。
隊列之間必須有調度。可采用
1) 固定優先級搶占調度(fixed-priority preemptive scheduling) – 每個隊列相比更低層隊列有絕對的priority。對於一個隊列,只有高層隊列都為空時該隊列內進程才可運行;如果有新進程進入高層隊列則CPU會被搶占。[通常采用]
2) 在隊列間划分time-slice – 每個隊列有固定的CPU時間,在自己的CPU時間內可調度隊列內進程。
多級反饋隊列調度
對比多級隊列調度 –允許進程在隊列之間移動,相比開銷更大,更靈活。
多級反饋隊列調度算法(multilevel feedback queue scheduling algorithm):根據CPU burst的特點區分進程。如果進程使用過多CPU時間則轉移到更低隊列,在低priority隊列中等待時間過長的進程可被轉移到高priority隊列(aging的一種形式)。
可由以下參數定義:
1) 隊列數量;
2) 每個隊列的調度算法;
3) 用於確定何時升級到更高priority隊列的方法;
4) 用於確定何時降級到更低priority隊列的方法;
5) 用於確定進程需要服務時應進入哪個隊列的方法。
e.g.
隊列0中,不能在8ms內完成的進程會被搶占,移到隊列1尾部;
隊列1中,不能在16ms內完成的進程會被搶占,移到隊列2尾部;
只有隊列0和1為空時,隊列2內進程才可根據FCFS運行。

多處理器調度(multuiple-processor scheduling)
同構(homogeneous):processor功能相同。同構系統中,可將任何processor用於運行隊列中的任何進程。
當前許多計算機都支持multiprocessing,並允許每個processor獨立調度自己。通常每個processor維護自己私有的進程或線程隊列。
多處理器調度的方法
1) 非對稱多處理(asymmetric multiprocessing)方法:讓單獨的一個processor (master server) 處理所有調度決策、I/O處理和其他系統活動,其它processor只執行用戶代碼。(簡單,只有一個processor訪問系統數據結構,減輕數據共享的需要)
2) 對稱多處理(symmetric multiprocessing, SMP)方法:每個processor是self-scheduling的,每個processor檢查就緒隊列並選擇一個進程來執行。(所用進程可能處於一個共同的就緒隊列中,或每個processor都有私有的就緒隊列。)
在絕大多數支持SMP的當代操作系統中,每個processor都有私有的就緒隊列。
幾乎現代操作系統都支持SMP。
處理器親和性
處理器親和性(Processor affinity):進程對其運行所在的processor的親和性,盡量使一個進程在同一個processor上運行,避免將進程在processors之間遷移。
由於在processors之間遷移進程的代價(1. 使原processor的cache無效 2. 重新構建新processor的cache)高,大多數SMP系統都具有processor affinity。
優點:進程可以利用它在原processor的cache中的數據。
Processor affinity通常有兩種形式:
軟親和性(soft affinity):OS具有讓一個進程保持在同一個processor上的運行策略,但不能做任何保證,進程有可能移動。
硬親和性(hard affinity):允許進程指定它不能遷移至其他processor上。E.g. Linux提供
負載平衡
負載平衡(Load balancing):設法將工作負載平均分配到SMP系統的所有processor上。
對於“擁有自己私有的就緒隊列”的processor來說是必需的,“具有共同就緒隊列”的系統通常不需要。
Load balancing通常有兩種方法:
Push migration:一個特定的task周期性地檢查每個processor上的負載,如果發現不平衡就把進程從過載的processor push到空閑或不太忙的processor上。
Pull migration:空閑的processor從忙碌的processor上pull走一個waiting task。
Push migration和pull migration可以並行實現,不需要互斥。
e.g. Linux scheduler, ULE scheduler for FreeBSD.
Load balancing通常會抵消掉processor affinity的好處,關於何種方式更好並沒有絕對的規則。有些系統中只有當不平衡達到一定額度后才會移動進程。
SMT
對稱多線程(Symmetric multithreading, SMT):提供多個logical processors來實現多線程同時運行的策略。在intel processors中也稱為超線程(hyperthreading)技術。
SMT在同一physical processor上生成多個logical processor,向OS呈現一個“多個logical processors”的視圖。
每個logical processor都有自己的架構狀態(architecture state),包括general-purpose和machine-state registers。
每個logical processor負責自己的中斷處理。

SMT是由硬件(而不是軟件)提供的。硬件應提供每個logical processor的架構狀態的表示以及中斷處理方法,OS不需要特殊設計。
線程調度(Thread Scheduling)
對於支持多線程的OS,OS調度的是內核線程,而不是進程。
競爭范圍
用戶線程和內核線程所用調度方法的不同
進程競爭范圍(Process-contention scope, PCS):在實現多對一或多對多模型的系統中,線程庫調度用戶級線程到可用的LWP上。i.e. CPU競爭發生在“屬於相同進程的線程”之間。
系統競爭范圍(System-contention scope, SCS):OS將內核線程調度到物理CPU上,該競爭發生在OS的所有線程中。采用一對一模型的OS的線程調度只采用SCS。
PCS通常根據priority來完成調度 [preemptive] ,但在具有相同priority的線程間並不保證有time slicing。
Pthread線程調度
POSIX Pthread API允許在線程生成中指定是PCS或SCS。競爭范圍值PTHREAD_SCOPE_PROCESS表示“采用PCS調度”,PTHREAD_SCOPE_SYSTEM表示“采用SCS調度”。
函數pthread_attr_setscope(pthread attr_t *attr, int scope)、pthread_attr_getscope(pthread_attr_t *attr, int *scope) 用於獲取及設置競爭范圍。
某些OS只允許特定的競爭范圍值。E.g. Linux, Mac OS X只允許PTHREAD_SCOPE_SYSTEM。
OS實例
對於支持內核級線程的OS,必須調度線程(而不是進程)來執行。
以下3種OS通常偏愛交互進程而不是批處理進程或CPU-bound進程。
Solaris的內核線程調度(搶占、基於優先級、支持實時線程)
傳統Solaris使用多對多模型,Solaris 9使用一對一模型。
Solaris按照優先級排序有4種調度類型: 實時(real time)、系統(System)、分時(Time sharing)和交互(Interactive),每種類型有不同的priority和調度算法。
Scheduler將特定類的priority轉換為全局priority,再選擇全局priority最高的線程來執行,直到該線程阻塞、用完time quantum或被更高priority的線程搶占。如果多個線程的priority相同,則采用循環隊列。
Solaris 9引入2種新的調度類型:
1) 固定優先級(fixed priority) – 線程的priority與time sharing類型范圍相同,但不能動態調節。
2) 公平共享(fair share) – 用CPU shares代替priority來做調度決策。
CPU shares:表明可用CPU資源的權利,並被分配到一個project(進程集)。

Time sharing類(默認調度類型)和Interactive類采用同樣的調度策略(多級反饋隊列),Priority和time quantum默認成反比。
通常Interactive進程的priority更高,CPU-bound進程的priority更低。
Interactive和time sharing類包括60個優先級,在其調度中:
時間片到期(Time quantum expired)(i.e. 用完其time-quantum而未堵塞)的線程將被認為是CPU-intensive的,並被降低優先級。
從睡眠中返回(Return from sleep)(e.g. 從等待I/O中返回)的線程優先級將被提高。
System類專門保留給內核使用,用於運行內核進程。System進程一旦創建,其priority就不再改變。(在內核模式下運行的用戶進程並不屬於system類。)
Real time類的進程具有最高priority,能在其他類型進程之前運行。通常只有少數進程屬於real time類。
Windows XP的內核線程調度(基於優先級、搶占、支持實時線程)
Dispatcher:Windows XP內核中用於處理調度的部分。(應該與前面所介紹的分派程序dispatcher不同)
Dispatcher選擇priority最高的線程來執行,直到該進程被更高priority的進程搶占、終止、用完time quantum或調用了blocking system call。
Dispatcher使用32級priority方案,priority分為兩大類型:
Priority 0的線程用於內存管理
Priority 1~15的線程屬於可變類型(variable class),此類線程的priority可以改變。
Priority 16~31的線程屬於實時類型(real-time class)
Dispatcher為每個priority使用一個隊列,並從高到低檢查隊列集,直到發現一個可以執行的線程。如果沒有找到,則執行一個稱為空閑線程(idle thread)的特別線程。
Win32 API定義了進程可能屬於的priority類型:
1) REALTIME_PRIORITY CLASS
2) HIGH_PRIORITY_CLASS
3) ABOVE_NORMAL_PRIORITY_CLASS
4) NORMAL_PRIORITY_CLASS
5) BELOW_NORMAL_PRIORITY_CLASS
6) IDLE_PRIORITY_CLASS
其中,REALTIME_PRIORITY_CLASS屬於real-time class,其它類型屬於variable class。
進程通常屬於NORMAL_PRIORITY_CLASS,除非其父進程為IDLE_PRIORITY_CLASS或在創建該進程時指定其他類型。
每個線程的priority都基於其所屬priority類型和它在該類型中的relative priority。Relative priority的值包括:
1) TIME_CRITICAL
2) HIGHEST
3) ABOVE_NORMAL
4) NORMAL
5) BELOW_NORMAL
6) LOWEST
7) IDLE

基礎優先級(base priority):每個線程都有的一個(在其所屬類型范圍內的)priority值,默認為該類型中relative priority為NORMAL的值。
線程的priority初始值通常為所屬進程的base priority。
線程的time quantum用盡時將被中斷,如果該線程屬於variable class則其priority將降低,但絕不會低於其base priority。
當屬於variable class的線程被從等待操作中釋放時,其priority將提升(提升多少取決於該線程等待的是什么)。
e.g. 等待I/O比等待磁盤操作得到的提升更大,縮短交互線程的響應時間。
為了給交互程序的進程提供優秀的性能,Windows XP對NORMAL_PRIORITY_CLASS的進程有一個特別調度規則 – 區分前台進程和后台進程,當進程進入前台時增加其time quantum的倍數(通常為3)。
Linux內核調度(基於優先級,搶占、提供實時支持)(Linux不區分進程和線程,使用task。)
2.5版本前運行傳統UNIX算法,存在問題:1) 對SMP系統沒有提供足夠支持 2) tasks數量增加時無法按比例調整。2.5版本提供了O(1)的調度算法和對SMP的支持。
Linux兩個獨立的priority range:
Real-time (0~99):被分配靜態priority。
Nice (100~140):具有動態priority,根據task的交互性決定+5還是-5。(交互性更強的任務通常-5,使priority更高)
這兩個ranges映射到global priority,數值越低priority越高。
Linux中分配給task的time quantum與priority成反比。(與Solaris、Windows XP相反)

runque:內核所維護的一個數據結構,記錄可運行tasks的列表。每個runque包括兩個priority arrays,每個priority array都有一個根據priority索引的tasks列表。
Active array包括所有在time quantum中還有剩余的tasks;
Expired array包括所有已到期(耗盡time quantum)的tasks。
因為支持SMP,每個processor都需維護自己的runque並自行調度。調度時,從active array中選擇priority最高的task來在CPU上執行。Task到期被移至expired array后將重新計算動態優先級。當所有tasks都到期(I.e. active array為空)時,兩個array互換。
調度算法評估
分析評估法(analytic evaluation):評估算法的一種主要方法,使用給定算法和系統負荷產生一個公式或數字來評估算法在該負荷下的性能。(使用數學分析)
確定模型
確定模型法(deterministic modeling):analytic evaluation的一種。使用預先確定的特定負荷,計算每個算法在該負荷下的性能。主要用於描述調度算法和提供例子。
缺點:要求輸入精確數字,並且答案只適用於給定情況。
排隊模型
對於許多系統,進程集合並不是靜態的但可以通過測量CPU burst分布和進程到達系統的時間分布來估計進程的到達率(arrival rates)和CPU的服務率(service rates)。
排隊網絡分析(queueing-network analysis):CPU可看做具有一個等待進程隊列的服務器,從進程的到達率和服務率可計算CPU使用率、平均隊列長度、平均等待時間等。主要用於比較調度算法。
缺點:隊列模型只是現實系統的近似,難以處理復雜算法或分布。
Little formula: (可用於根據3個變量中的2個來計算另外1個)
n – 平均隊列長度; W – 隊列平均等待時間; 𝜆 – 進程平均到達率;
如果系統處於穩定狀態,則 n = 𝜆 × W。
模擬(對代表性進程采用算法模擬並計算性能)
模擬(simulation):對計算機系統建模。隨着模擬程序執行,時鍾變量增加,模擬程序修改系統狀態來反映設備、進程和scheduler的活動,收集打印反映算法性能的統計資料。
缺點:精確模擬需要較多時間、跟蹤磁帶需要大量存儲空間、模擬程序編碼調試的工作。
生成驅動數據的方法
最常見 – 使用隨機數生成器,根據概率分布生成。分布可以數學定義或經驗定義,經驗定義需要對真實系統進行測量。
問題:由於頻率分布不能表示時間的發生順序,分布驅動的模擬可能不夠精確。
解決方案:使用跟蹤磁帶(trace tapes)記錄真實系統中時間的發生順序。
實現
在真實系統上實現算法並在真是環境中進行跟蹤。
缺點:代價高(編程、修改)、環境可能發生變化。
