進程調(diào)度模擬器:時間片輪轉(zhuǎn)與短作業(yè)優(yōu)先全解析)
做操作系統(tǒng)課程設(shè)計的時候進程調(diào)度模擬器幾乎是繞不開的一道坎。時間片輪轉(zhuǎn)RR和短作業(yè)優(yōu)先SJF這兩個調(diào)度算法理論課上聽老師講覺得挺簡單真到動手用C寫一個能跑起來的模擬系統(tǒng)才發(fā)現(xiàn)里面藏著不少坑。這篇博文就按我實際完成這個項目的思路來寫——從算法原理、數(shù)據(jù)結(jié)構(gòu)和C實現(xiàn)細節(jié)到測試數(shù)據(jù)設(shè)計、結(jié)果分析和報告整理完整過一遍給正在做類似作業(yè)的同學一個可以直接參考的路線。我當時拿到的題目要求是模擬單CPU環(huán)境下的進程調(diào)度至少實現(xiàn)時間片輪轉(zhuǎn)和SJF兩種調(diào)度算法支持自定義進程的到達時間和服務(wù)時間輸出調(diào)度順序甘特圖、進程完成時間、周轉(zhuǎn)時間、等待時間等關(guān)鍵指標最后提交設(shè)計源文件加一份詳細的課程報告并錄制講解。聽起來不復(fù)雜但真正動手以后發(fā)現(xiàn)要做到調(diào)度過程嚴謹、統(tǒng)計結(jié)果準確、兩種算法對比有說服力需要處理很多邊界情況。下面就是我完成這個項目的完整復(fù)盤按設(shè)計思路 → 數(shù)據(jù)結(jié)構(gòu) → 編碼實現(xiàn) → 測試分析 → 排錯經(jīng)驗 → 報告整理的順序展開。1. 整體設(shè)計思路與調(diào)度策略選擇1.1 兩種調(diào)度算法到底在解決什么問題先把兩個算法的本質(zhì)說清楚。**時間片輪轉(zhuǎn)Round RobinRR**屬于搶占式調(diào)度核心思想是公平每個就緒進程輪流獲得一個固定長度的時間片時間片用完后即使進程還沒執(zhí)行完也必須讓出CPU回到就緒隊列尾部重新排隊。它的優(yōu)點是響應(yīng)時間短、不會出現(xiàn)進程餓死交互式系統(tǒng)里大量使用這種策略。缺點是頻繁的上下文切換會帶來額外開銷而且從平均周轉(zhuǎn)時間來看并不占優(yōu)。**短作業(yè)優(yōu)先Shortest Job FirstSJF**則是非搶占式調(diào)度核心思想是讓短作業(yè)先跑。每次CPU空閑時從就緒隊列里挑選服務(wù)時間最短的進程執(zhí)行直到該進程執(zhí)行完畢才切換。理論早已證明在所有非搶占式調(diào)度算法中SJF能給出最小的平均等待時間。但它的隱患也很明顯——長作業(yè)可能長期得不到執(zhí)行出現(xiàn)饑餓現(xiàn)象。這兩種算法放在同一個項目里做對比本身就是課程設(shè)計里很經(jīng)典的組合一個代表公平優(yōu)先一個代表效率優(yōu)先。用同一組測試數(shù)據(jù)分別跑兩種算法從平均周轉(zhuǎn)時間和平均等待時間兩個指標上做量化對比就能很直觀地看到不同調(diào)度策略對系統(tǒng)性能的影響。1.2 為什么把兩種算法放進同一個系統(tǒng)有人可能會問直接分開寫兩個獨立的小程序不就行了為什么非要放在一個系統(tǒng)里我的設(shè)計是做一個統(tǒng)一的調(diào)度模擬框架內(nèi)部維護一份就緒隊列通過一個調(diào)度模式參數(shù)枚舉類型或布爾標志在RR和SJF之間切換。這樣做的理由有三個第一代碼復(fù)用度高。進程創(chuàng)建、狀態(tài)管理、統(tǒng)計輸出、時間推進這些邏輯跟具體算法無關(guān)寫一遍就行。真正需要切換的只有從就緒隊列中選下一個進程這一小段邏輯用一個switch分支就能搞定。第二對比實驗更有說服力。課程報告里需要做性能對比如果兩個算法用兩套不同的代碼和數(shù)據(jù)結(jié)構(gòu)讀者很難相信數(shù)據(jù)是公平的。放在同一個框架里只有選進程的策略不同其他條件完全一致這樣得出的對比結(jié)論才是站得住腳的。第三便于擴展。如果后續(xù)想加優(yōu)先級調(diào)度、多級反饋隊列只需要新增一個調(diào)度模式方法主循環(huán)不用大幅改動。老師如果追問能不能再擴展一個算法這個架構(gòu)能直接接住。1.3 模擬系統(tǒng)的總體運行流程整個模擬器的工作流程我設(shè)計成這樣讀取進程參數(shù)到達時間、服務(wù)時間、數(shù)量、時間片大小 → 初始化就緒隊列 → 推進系統(tǒng)時鐘 → 按當前調(diào)度算法選擇進程執(zhí)行 → 更新進程狀態(tài)和統(tǒng)計變量 → 重復(fù)直到所有進程完成 → 輸出甘特圖和統(tǒng)計報表。這里有個關(guān)鍵的思路模擬系統(tǒng)里要自己維護一個遞增的系統(tǒng)時鐘變量。每執(zhí)行一個時間單位就檢查是否有新進程到達是新進程就把它加入就緒隊列。時鐘推進的粒度決定了模擬精度我的實現(xiàn)里以時間片為最小推進單位每輪調(diào)度結(jié)算一次這樣邏輯更清晰輸出甘特圖也方便。2. 核心數(shù)據(jù)結(jié)構(gòu)與系統(tǒng)架構(gòu)設(shè)計2.1 進程控制塊PCB的設(shè)計模擬系統(tǒng)的核心是對現(xiàn)實操作系統(tǒng)進程的建模。真實系統(tǒng)里進程控制塊PCB包含的信息很多這里只需要保留調(diào)度實驗關(guān)心的字段。我用C定義了一個結(jié)構(gòu)體大概長這樣struct PCB { int pid; // 進程編號從1開始 int arriveTime; // 到達時間單位ms模擬 int serveTime; // 服務(wù)時間總CPU突發(fā)時間 int remainTime; // 剩余服務(wù)時間調(diào)度過程中實時更新 int finishTime; // 完成時間進程結(jié)束時記錄 int startTime; // 首次開始執(zhí)行的時間用于計算響應(yīng)時間 bool isStarted; // 是否已經(jīng)至少執(zhí)行過一次用于計算響應(yīng)時間 };字段設(shè)計有幾個細節(jié)值得注意remainTime是調(diào)度過程中的核心變量。在RR模式下每次執(zhí)行后要減去消耗的時間片在SJF模式下由于進程是連續(xù)執(zhí)行完的它更多用于理論校驗。isStarted和startTime主要用來計算響應(yīng)時間首次開始執(zhí)行時間減去到達時間。這個指標能體現(xiàn)RR在交互式場景下的優(yōu)勢報告里可以補充說明。finishTime在進程執(zhí)行完畢的那個時刻寫入之后才能計算周轉(zhuǎn)時間。我還額外加了一個PState 枚舉類型表示進程當前狀態(tài)未到達NOT_ARRIVED、就緒READY、運行RUNNING、完成FINISHED。狀態(tài)轉(zhuǎn)換是模擬器正確性的關(guān)鍵我在后面編碼部分會重點說。2.2 就緒隊列與時間推進模型就緒隊列選用C標準庫的dequePCB*雙端隊列來模擬。為什么不用vector或者queue因為RR模式下需要頻繁在隊首取元素、在隊尾插入元素deque兩端操作都是常數(shù)時間復(fù)雜度而且支持隨機訪問SJF模式下需要對隊列按剩余時間排序隨機訪問能力正好用得上。queue容器適配器不支持遍歷排序直接排除。時間推進模型上我采用了事件驅(qū)動 時間片步進的混合思路。外層while循環(huán)檢查所有進程是否完成內(nèi)層每次調(diào)度可以做三件事把到達時間小于等于當前時刻的新進程加入就緒隊列按調(diào)度算法從就緒隊列選出下一個執(zhí)行的進程推進時鐘并更新狀態(tài)。2.3 統(tǒng)計模塊的組織方式調(diào)度模擬器光能跑還不夠關(guān)鍵是要輸出準確的統(tǒng)計數(shù)據(jù)。我單獨設(shè)計了一個SchedulerStats結(jié)構(gòu)體來累計總周轉(zhuǎn)時間所有進程周轉(zhuǎn)時間之和總等待時間所有進程等待時間之和調(diào)度切換次數(shù)便于分析上下文切換開銷每個進程的完成時刻明細用于輸出表格這些統(tǒng)計量在進程完成的時刻實時更新最后統(tǒng)一計算平均值。這里有一個我踩過的坑等待時間不能簡單等同為服務(wù)時間減執(zhí)行時間必須用完成時間 - 到達時間 - 服務(wù)時間來計算或者用累計等待累計器在每個時間片結(jié)算時給非運行進程統(tǒng)一累加。第一種方式更直接我最終采用這種方式。我會計算 [...] 以便在報告里對兩種算法的性能進行量化分析。實際上模擬結(jié)束后手動驗算一遍統(tǒng)計結(jié)果是排查統(tǒng)計模塊bug最有效的手段。3. C實現(xiàn)關(guān)鍵環(huán)節(jié)全解析3.1 調(diào)度主循環(huán)的框架設(shè)計主循環(huán)是整個模擬器的發(fā)動機我把它設(shè)計成下面這個結(jié)構(gòu)void runScheduler(RRMode mode, int timeSlice) { int currentTime 0; int finishedCount 0; int n processList.size(); while (finishedCount n) { // 1. 檢查是否有新進程到達 for (auto p : processList) { if (p.arriveTime currentTime) { readyQueue.push_back(p); } } // 2. 就緒隊列為空時直接推進時鐘 if (readyQueue.empty()) { currentTime; continue; } // 3. 按調(diào)度算法選擇下一個執(zhí)行進程 PCB* current nullptr; if (mode RR) { current readyQueue.front(); readyQueue.pop_front(); } else if (mode SJF) { current selectShortestJob(); } // 4. 執(zhí)行與時間推進 int runTime 0; if (mode RR) { runTime min(timeSlice, current-remainTime); current-remainTime - runTime; if (!current-isStarted) { current-startTime currentTime; current-isStarted true; } currentTime runTime; outputGanttSegment(current-pid, currentTime - runTime, currentTime); if (current-remainTime 0) { readyQueue.push_back(current); // 未完成排到隊尾 } else { finishProcess(current, currentTime); finishedCount; } } else { runToCompletion(current, currentTime); // SJF 連續(xù)執(zhí)行完 outputGanttSegment(current-pid, ..., ...); finishProcess(current, currentTime); finishedCount; currentTime current-finishTime; current-remainTime 0; } } }這段邏輯里容易忽略的是就緒隊列為空的處理。比如第一個進程還沒到達或者所有到達的進程都已經(jīng)執(zhí)行完、但下一批進程還沒到來中間會出現(xiàn)CPU空閑。處理方式是直接把當前時間推進到下一個進程的到達時刻而不是傻傻地讓時鐘走一步發(fā)現(xiàn)隊列還是空的再走一步。我在代碼里用了一個優(yōu)化提前掃描進程列表找到下一個最小到達時間直接把currentTime跳過去。這樣模擬過程更高效輸出甘特圖也干凈不會出現(xiàn)一個空輸出的時間片。另一個容易錯的地方是RR模式下的初始響應(yīng)時間標記。一個進程可能第一次被調(diào)度時只執(zhí)行了一個很短的時間片隨后被排到隊尾之后又經(jīng)歷多輪才再次輪到。所以首次開始執(zhí)行時間必須在它第一次真正獲得CPU時立即記錄不能等進程完成后再回溯。3.2 時間片輪轉(zhuǎn)的切換邏輯RR的實現(xiàn)難點不在選進程而在時間片的精確結(jié)算。我用一個runTime變量表示本次實際執(zhí)行時長取值是時間片大小和剩余服務(wù)時間中的較小值如果進程剩余時間不足一個時間片就讓它執(zhí)行完直接結(jié)束否則執(zhí)行滿一個時間片后壓回隊尾。這里有一個重要的邊界問題需要想清楚如果進程到達的瞬間就緒隊列為空它需要立即運行嗎答案是肯定的。比如P1在t0到達當前時間也是0那就應(yīng)該讓P1在0時刻直接開始執(zhí)行而不是先把時間推進到第一個時間片結(jié)束。也就是說主循環(huán)里的檢查新進程到達必須在選擇執(zhí)行進程之前完成并且要用arriveTime currentTime而不是 currentTime來判斷否則就會漏掉跨時間片到達的進程。我在實際編碼時還加了一個記錄調(diào)度切換次數(shù)的計數(shù)器。每次進程切換、每次時間片用盡重新排隊都自增。這個數(shù)據(jù)在報告里有大用處能說明RR算法雖然響應(yīng)快但上下文切換開銷明顯高于SJF。這也呼應(yīng)了理論課上講的時間片過小會增大切換開銷這一結(jié)論。3.3 SJF的選優(yōu)邏輯與邊界處理SJF的選優(yōu)邏輯相對簡單遍歷就緒隊列選出remainTime最小的進程。注意由于SJF是非搶占式的一旦選中就讓它一直運行到結(jié)束中途不需要檢查是否有更短的新進程到達——這正是它區(qū)別于SRTF最短剩余時間優(yōu)先搶占式版本的地方。很多同學在這里搞混把SJF實現(xiàn)成了搶占式雖然也能跑但和題意的短作業(yè)優(yōu)先就不一致了。我的selectShortestJob()實現(xiàn)如下PCB* selectShortestJob() { auto minIt readyQueue.begin(); for (auto it readyQueue.begin(); it ! readyQueue.end(); it) { if ((*it)-remainTime (*minIt)-remainTime) { minIt it; } } PCB* selected *minIt; readyQueue.erase(minIt); return selected; }SJF還有一個容易踩的坑當有多個進程剩余服務(wù)時間相同時怎么選我的做法是選pid最小的也就是后到先比較。這一點看起來不起眼但會直接影響甘特圖的輸出結(jié)果甚至影響后續(xù)的手動驗算。報告里也應(yīng)該說明這個選擇規(guī)則體現(xiàn)設(shè)計的一致性。還有一個邊界條件如果系統(tǒng)是非搶占式SJF當一個進程正在執(zhí)行時新到達了一個更短的進程當前進程不會被中斷。所以在主循環(huán)里SJF分支不需要在每次時間步進時重新選擇進程只要當前進程沒執(zhí)行完就一直跑直到它完成或內(nèi)部時間片推進邏輯自然結(jié)束。我在這里加了一個if (current ! nullptr current-remainTime 0)的循環(huán)保護避免在SJF分支里重復(fù)選進程。3.4 進程狀態(tài)轉(zhuǎn)換機的實現(xiàn)狀態(tài)轉(zhuǎn)換是這類模擬器里最容易被忽視、卻又最容易出bug的地方。我的狀態(tài)轉(zhuǎn)換規(guī)則很簡單未到達 → 就緒當currentTime arriveTime時把進程插入就緒隊列。就緒 → 運行調(diào)度器選中進程后切換狀態(tài)為運行中。運行 → 就緒RR模式下時間片用完且remainTime 0進程回到隊列尾部。運行 → 完成remainTime 0記錄完成時間統(tǒng)計指標。我在實現(xiàn)狀態(tài)轉(zhuǎn)換時專門定義了一個updateState(PCB*, PState)函數(shù)內(nèi)部用switch檢查合法轉(zhuǎn)換非法轉(zhuǎn)換直接打印錯誤。比如進程還沒到達就從就緒隊列里被選中了說明到達判斷有bug運行中的進程狀態(tài)被改成未到達也說明時間推進邏輯有問題。這種防御式編程在調(diào)試階段幫我省了不少時間。3.5 甘特圖輸出模塊甘特圖是課程報告里最有說服力的可視化內(nèi)容也是老師打分時的加分項。我用字符串拼接的方式實現(xiàn)了一個簡單的控制臺甘特圖每執(zhí)行完一段調(diào)度記錄一個片段格式如下P1 |############| 0 ~ 4 P2 |#### | 4 ~ 8 P3 |#### | 8 ~ 12 P4 |#### | 12 ~ 16 P1 |#### | 16 ~ 20 P3 |#### | 20 ~ 24 P4 |# | 24 ~ 25 P3 |# | 25 ~ 26實現(xiàn)方式是在主循環(huán)的每次調(diào)度段結(jié)束后調(diào)用outputGanttSegment(pid, startTime, endTime)它負責把時間段追加到一個vectorstring里最后統(tǒng)一輸出。這樣即使進程數(shù)量多控制臺輸出也不會亂。如果你想輸出更美觀的甘特圖可以按進程為行、時間為列用二維數(shù)組填充但控制臺字符寬度有限進程多了容易換行錯位反而不如這種分段式輸出直觀。4. 測試用例設(shè)計與調(diào)度結(jié)果對比分析4.1 測試數(shù)據(jù)怎么設(shè)計才有說服力課程設(shè)計報告里的測試數(shù)據(jù)不是隨便填幾個數(shù)字就完事要能體現(xiàn)出兩種算法的性能差異。我建議準備三組數(shù)據(jù)第一組全部進程同一時刻到達。這樣SJF直接按服務(wù)時間排序RR按到達順序輪轉(zhuǎn)對比最干凈。第二組進程錯峰到達。例如P1在0時刻到達服務(wù)時間8P2在1時刻到達服務(wù)時間4P3在2時刻到達服務(wù)時間9P4在3時刻到達服務(wù)時間5。這種錯峰場景最能暴露兩種算法的本質(zhì)區(qū)別我在報告里用的就是這組數(shù)據(jù)。第三組極端場景。比如一個超長進程和多個短進程混合用來驗證SJF可能讓長作業(yè)等待時間變長的饑餓問題這是報告里一個很好的討論點。下面我以第二組數(shù)據(jù)為例完整展示對比分析過程。這組數(shù)據(jù)手動算一遍能幫你驗證程序的正確性P1到達時間0服務(wù)時間8P2到達時間1服務(wù)時間4P3到達時間2服務(wù)時間9P4到達時間3服務(wù)時間5時間片大小設(shè)為4。4.2 從輸出結(jié)果看兩種算法的性能差異RR時間片4的調(diào)度過程0~4P1執(zhí)行剩余44~8P2執(zhí)行剩余0完成8~12P3執(zhí)行剩余512~16P4執(zhí)行剩余116~20P1繼續(xù)執(zhí)行剩余0完成20~24P3執(zhí)行剩余124~25P4執(zhí)行剩余0完成25~26P3執(zhí)行剩余0完成完成時間P120P28P326P425。平均周轉(zhuǎn)時間 (2082625)/4 19.75。平均等待時間 (1231517)/4 11.75。SJF非搶占式的調(diào)度過程0~8P1執(zhí)行唯一到達8~12P2執(zhí)行剩余時間最短412~17P4執(zhí)行此時剩余時間5比P3的9短17~26P3執(zhí)行最后執(zhí)行完成時間P18P212P417P326。平均周轉(zhuǎn)時間 (8121726)/4 15.75。平均等待時間 (07915)/4 7.75。結(jié)果非常直觀這組測試數(shù)據(jù)下SJF的平均周轉(zhuǎn)時間比RR少了4個單位平均等待時間少了整整4個單位。原因是SJF優(yōu)先執(zhí)行P2和P4兩個短作業(yè)而RR為了維持公平讓P1和P3這兩個長作業(yè)交叉執(zhí)行整體拖慢了短作業(yè)的完成。這就是SJF能最小化平均等待時間這一理論結(jié)論在實際數(shù)據(jù)中的體現(xiàn)。但你也會發(fā)現(xiàn)一個有趣的現(xiàn)象P1在RR下的完成時間是20在SJF下是8。長作業(yè)在SJF下反而更早完成因為它在SJF里是第一個被執(zhí)行的這組數(shù)據(jù)沒有體現(xiàn)出饑餓現(xiàn)象。所以我在報告里額外補充了第三組極端測試P1服務(wù)時間100P2服務(wù)時間2P3服務(wù)時間3三者同時到達。此時SJF下P1要等P2和P3全部完成才能開始執(zhí)行完成時間被拖到105而在RR下P1最多等兩個時間片就能開始執(zhí)行。這就是饑餓問題的量化體現(xiàn)老師看到這個細節(jié)就知道你真的理解了這個算法。4.3 時間片大小對RR性能的影響這是報告里另一個有價值的擴展分析。保持測試數(shù)據(jù)不變把時間片從4改到2、從4改到8重新跑一遍程序會發(fā)現(xiàn)時間片越小上下文切換次數(shù)越多平均周轉(zhuǎn)時間通常越長但不絕對。時間片越大RR越接近FCFS先來先服務(wù)公平性下降但切換開銷降低。我實際測試的結(jié)果是時間片2時P1和P3的執(zhí)行被切成了更多段平均周轉(zhuǎn)時間從19.75上升到23.5左右時間片8時實際效果和SJF在這個數(shù)據(jù)上接近但等待時間和周轉(zhuǎn)時間依然不是最優(yōu)。這個對比可以放在報告的參數(shù)影響分析小節(jié)里用一張表格呈現(xiàn)不同時間片下的平均周轉(zhuǎn)時間和平均等待時間說服力拉滿。5. 調(diào)試過程中的常見坑與排錯實錄5.1 時間片切換時丟進程的經(jīng)典bug我在調(diào)試RR時遇到過一個非常隱蔽的問題某個進程在被壓回隊尾后再也沒被調(diào)度到導(dǎo)致整個模擬器死循環(huán)。排查了很久最后發(fā)現(xiàn)原因是——進程執(zhí)行完一個完整時間片后我忘記把它重新壓入就緒隊列。// 錯誤寫法 if (current-remainTime 0) { // 忘了 push_back(current) 這一行 // 進程直接丟失回不去了 }這種bug在單核模擬器里不會立刻報錯只會讓就緒隊列慢慢變空最后進程數(shù)量對不上。排查方法是打印每秒的就緒隊列內(nèi)容如果發(fā)現(xiàn)某個進程從隊列里消失且remainTime 0基本就是這個原因。我建議大家寫代碼時把未完成進程重新入隊和完成進程釋放放在同一個分支的兩側(cè)邏輯上形成互斥一眼就能檢查到位。5.2 統(tǒng)計指標對不上賬的問題輸出報告的周轉(zhuǎn)時間和手算結(jié)果不一致這也是常見問題。大部分原因出在時間推進精度上。比如SJF分支里我用currentTime runTime推進時鐘但如果一個進程執(zhí)行了5個單位時間我卻把這5個單位平均分配到了多個模擬步里那么中間就可能插入其他到達的進程導(dǎo)致順序錯亂。解決思路是SJF分支里一旦選中進程就直接推進到它的完成時刻期間不檢查新到達進程而RR分支嚴格按時間片粒度推進。這兩種推進策略的粒度不同但都要保證在推進之后把當前時間內(nèi)所有到達的進程都加入就緒隊列。我在每次推進時鐘后統(tǒng)一調(diào)用一次addArrivingProcess(currentTime)函數(shù)保證不漏不重。還有一個統(tǒng)計口徑的問題進程完成時間是它結(jié)束的那一刻而不是它被調(diào)度器檢查到的那一刻。比如P1在16時刻就已經(jīng)運行完了但因為主循環(huán)是16時刻之后才檢查完成狀態(tài)很容易把finishTime記成20。解決方法是進程執(zhí)行結(jié)束時立即記錄完成時間而不是等到主循環(huán)的下一輪再統(tǒng)一結(jié)算。這個立即結(jié)算的原則貫穿了我整個統(tǒng)計模塊。5.3 新增算法擴展時對既有邏輯的影響老師可能會要求擴展優(yōu)先級調(diào)度我實際操作中有個體會給系統(tǒng)加新算法時最安全的方式是新寫一個調(diào)度分支方法而不是改原有的RR和SJF邏輯。因為原有邏輯已經(jīng)在調(diào)試中穩(wěn)定下來動它容易引入新的回歸bug。比如我后來加了一個簡單的優(yōu)先級調(diào)度只需要新增一個selectByPriority()方法把所有進程按優(yōu)先級排序返回最高者完全不影響已有代碼。從架構(gòu)角度看這其實就體現(xiàn)了一個好的調(diào)度模擬器設(shè)計原則調(diào)度策略與調(diào)度框架解耦。算法是插拔式的框架是穩(wěn)定的這樣的代碼無論是調(diào)試還是寫報告思路都會清晰很多。5.4 報告與講解環(huán)節(jié)的經(jīng)驗提醒課程設(shè)計除了代碼還要交報告和做講解這兩樣同樣影響最終分數(shù)。我的報告是按需求分析 → 概要設(shè)計 → 詳細設(shè)計 → 系統(tǒng)實現(xiàn) → 測試與分析 → 總結(jié)的經(jīng)典結(jié)構(gòu)寫的其中測試與分析部分占了最大篇幅。為了說清楚調(diào)用的過程我還截了幾張程序運行的控制臺輸出圖截圖前先把終端窗口寬度調(diào)大保證甘特圖每一段都在一行內(nèi)顯示。甘特圖在頁面上按比例縮放老師看得清楚講解時也能指著圖說流程。講解環(huán)節(jié)我被老師問到過為什么SJF在平均等待時間上優(yōu)于RR但實際操作系統(tǒng)里卻很少用純SJF這個問題。我的回答思路是SJF需要預(yù)知進程的服務(wù)時間這在真實系統(tǒng)里很難準確獲得而且它會導(dǎo)致長作業(yè)饑餓交互式場景下用戶體驗差。所以實際系統(tǒng)更多用多級反饋隊列這類折中的算法。這個回答能讓老師知道你不僅會寫代碼還能把理論結(jié)合實際。6. 項目源碼結(jié)構(gòu)與配套資料的組織方式6.1 源代碼模塊劃分一個規(guī)范的課程設(shè)計項目源碼結(jié)構(gòu)應(yīng)該清晰。我項目的目錄結(jié)構(gòu)是這樣的SchedulerSim/ ├── main.cpp // 程序入口參數(shù)讀取與整體流程控制 ├── scheduler.h // 調(diào)度器類聲明 ├── scheduler.cpp // 調(diào)度器實現(xiàn) ├── pcb.h // 進程控制塊定義 ├── algorithm/ │ ├── rr.cpp // 時間片輪轉(zhuǎn)實現(xiàn) │ └── sjf.cpp // 短作業(yè)優(yōu)先實現(xiàn) └── utils/ ├── gantt.cpp // 甘特圖輸出 └── stats.cpp // 統(tǒng)計指標計算與輸出這樣模塊劃分的好處是每個文件職責單一調(diào)試時能快速定位問題。如果你完全把所有邏輯塞在main.cpp里雖然也能跑但到寫報告階段你會發(fā)現(xiàn)很難截圖展示優(yōu)秀的模塊化設(shè)計。而模塊化設(shè)計通常是課程報告里要求明確寫出的內(nèi)容。6.2 參數(shù)輸入與UI交互設(shè)計為了讓測試靈活我的程序支持兩種輸入方式交互式錄入和文件批處理。交互式錄入適合演示每輸入一個進程就詢問是否繼續(xù)文件批處理適合反復(fù)調(diào)試數(shù)據(jù)格式一行一個進程0 8 1 4 2 9 3 5 4前4行是到達時間 服務(wù)時間最后一行是時間片大小。程序啟動時讀取文件沒有文件就進入交互模式。每個算法跑完輸出一次統(tǒng)計數(shù)據(jù)最后統(tǒng)一打印兩張對比表周轉(zhuǎn)時間對比表平均等待時間對比表。6.3 講解視頻里我最想講清楚的兩個點配套的講解視頻或現(xiàn)場講解我最想講清楚的是兩個問題一是進程狀態(tài)機是怎么轉(zhuǎn)的二是兩種算法的甘特圖為什么長這樣。前者體現(xiàn)你對系統(tǒng)建模的理解后者體現(xiàn)你對算法的理解。我錄講解時會把程序跑一遍一邊跑一邊指著甘特圖說當前隊列里有哪些進程、為什么不選它、為什么讓它提前結(jié)束。這樣即使沒有華麗的PPT老師也能直觀感受到你確實親手實現(xiàn)了整個系統(tǒng)。我個人實際做完這個項目最大的體會是課程設(shè)計最有價值的不是最終那份能跑的代碼而是調(diào)試過程中逼著自己把每個細節(jié)想清楚的過程。比如RR在時間片用完和進程執(zhí)行完這兩種情況下進程的去向完全不同SJF在多個短作業(yè)并存時的選擇規(guī)則需要一致。只有親手踩過這些坑才能真正建立起對操作系統(tǒng)核心機制的直覺。以后面試遇到講講進程調(diào)度這類問題你腦子里浮現(xiàn)的是自己寫過的甘特圖和那幾行調(diào)試到半夜的代碼而不是課本上的干巴巴定義。這份項目值得認真做一遍。