課設(shè):FCFS作業(yè)調(diào)度、RR進程調(diào)度與內(nèi)存管理聯(lián)動)
簡介一份面向計算機相關(guān)專業(yè)學(xué)生的操作系統(tǒng)課程設(shè)計源碼與報告包圍繞作業(yè)調(diào)度、內(nèi)存管理、進程調(diào)度與進程阻塞喚醒四個核心模塊展開。項目用隨機數(shù)初始化10個作業(yè)進入后備隊列采用先來先服務(wù)FCFS完成作業(yè)調(diào)度內(nèi)存中最多駐留5個進程處于阻塞狀態(tài)的進程仍占用內(nèi)存就緒和運行狀態(tài)進程以時間片輪轉(zhuǎn)RR算法調(diào)度首次適應(yīng)FF算法負(fù)責(zé)內(nèi)存分配進程結(jié)束時回收并合并空閑分區(qū)整體串成結(jié)構(gòu)清晰、模塊劃分明確的可直接運行的控制臺演示程序。壓縮包共8個文件以4個Java源文件為主可直接導(dǎo)入IDE調(diào)試另附PDF與Word課設(shè)報告、Markdown說明和License文件便于閱讀設(shè)計、整理文檔。包體約1.28MB體量緊湊已有約259人學(xué)習(xí)下載適合操作系統(tǒng)課設(shè)參考算法組合、代碼框架與報告寫法。1. 基于 Java 的操作系統(tǒng)課設(shè)作業(yè)調(diào)度、內(nèi)存管理與進程調(diào)度怎么串成一條線操作系統(tǒng)課設(shè)最怕的不是某個算法寫不出來而是作業(yè)調(diào)度、內(nèi)存管理、進程調(diào)度各寫各的最后拼不到一起。這份基于 Java 的控制臺課設(shè)把 FCFS 作業(yè)調(diào)度、RR 時間片輪轉(zhuǎn)、FF 首次適應(yīng)內(nèi)存分配和進程阻塞喚醒全部串在一條主流程里10 個隨機作業(yè)從后備隊列一步步走進內(nèi)存、上 CPU、被阻塞、再被喚醒整個過程在控制臺就能看到。適合正在做操作系統(tǒng)課設(shè)、特別是“老師要求多個算法聯(lián)動而不是單獨演示”的 Java 學(xué)生。我拆完源程序和報告后發(fā)現(xiàn)它最大的價值不是某個算法寫得漂亮而是把“作業(yè)調(diào)度 → 內(nèi)存分配 → 進程調(diào)度 → 阻塞喚醒 → 內(nèi)存回收”這個完整閉環(huán)講清楚了。接下來按模塊拆開講每個部分都能直接照抄進你自己的課設(shè)。2. FCFS 作業(yè)調(diào)度與后備隊列10 個作業(yè)怎么排隊進內(nèi)存2.1 作業(yè)調(diào)度和進程調(diào)度為什么必須分開很多同學(xué)會把作業(yè)調(diào)度和進程調(diào)度混成同一個東西這是第一個認(rèn)知坎。作業(yè)調(diào)度也叫高級調(diào)度它決定的是“哪個作業(yè)能從磁盤后備隊列進入內(nèi)存”進程調(diào)度也叫低級調(diào)度它決定的是“內(nèi)存里已經(jīng)就緒的進程下一個誰上 CPU”。這份課設(shè)的流程是先用隨機數(shù)生成 10 個作業(yè)放進后備隊列作業(yè)調(diào)度采用 FCFS先來先服務(wù)每次從后備隊列頭部取一個作業(yè)但這個作業(yè)能不能進內(nèi)存還要看內(nèi)存里并發(fā)進程數(shù)是否達到上限。上限是 5 個。這里有個關(guān)鍵設(shè)定阻塞狀態(tài)的進程依然占著內(nèi)存也就是說就緒、運行、阻塞三種狀態(tài)的進程數(shù)加起來最多是 5。我當(dāng)初第一次看這個設(shè)定覺得奇怪后來想明白了——這是在模擬“內(nèi)存空間被占用”的現(xiàn)實約束不是每個進程都能立刻進內(nèi)存。所以作業(yè)調(diào)度觸發(fā)時機有兩個一是初始時內(nèi)存為空按 FCFS 依次調(diào)入前 5 個作業(yè)二是某個進程運行結(jié)束后釋放內(nèi)存再從后備隊列頭部補一個進來。2.2 后備隊列的數(shù)據(jù)結(jié)構(gòu)與隨機作業(yè)生成作業(yè)的數(shù)據(jù)結(jié)構(gòu)直接決定后續(xù)所有模塊怎么寫。先看作業(yè)實體類的核心字段public class Job { private int jobId; // 作業(yè)編號 private int arrivalTime; // 到達時間 private int serviceTime; // 需要服務(wù)的總時間 private int memorySize; // 所需內(nèi)存大小 private int status; // 作業(yè)狀態(tài)0-后備 1-就緒 2-運行 3-阻塞 4-完成 private int remainingTime; // 剩余服務(wù)時間RR調(diào)度要用 private int memoryStart; // 在內(nèi)存中的起始地址內(nèi)存分配后寫入 }隨機生成 10 個作業(yè)的代碼邏輯通常長這樣Random random new Random(); ListJob backlogQueue new ArrayList(); for (int i 1; i 10; i) { Job job new Job(); job.setJobId(i); job.setArrivalTime(i); // 簡化處理第i個作業(yè)第i時刻到達 job.setServiceTime(random.nextInt(20) 5); // 服務(wù)時間 5~24 job.setMemorySize(random.nextInt(80) 20); // 內(nèi)存需求 20~99 job.setStatus(0); // 后備狀態(tài) backlogQueue.add(job); } // 按到達時間排序保證FCFS的隊列順序 backlogQueue.sort(Comparator.comparingInt(Job::getArrivalTime));參數(shù)說明serviceTime 范圍設(shè) 5~24 是為了保證在時間片輪轉(zhuǎn)下能觀察到多次調(diào)度切換如果服務(wù)時間太短進程一兩個時間片就跑完了輪轉(zhuǎn)效果不明顯。memorySize 范圍 20~99 是配合內(nèi)存總大小設(shè)計的如果內(nèi)存總量是 512這個區(qū)間能保證產(chǎn)生多種分配情況偶爾出現(xiàn)碎片。arrivalTime 用 i 而不是隨機值是為了讓 FCFS 的順序確定方便驗證算法正確性——你不想在調(diào)試時分不清是調(diào)度算法錯了還是隨機數(shù)導(dǎo)致的現(xiàn)象不穩(wěn)定。2.3 FCFS 調(diào)度主循環(huán)的寫法作業(yè)調(diào)度的主循環(huán)和進程調(diào)度是嵌套關(guān)系外層循環(huán)負(fù)責(zé)從后備隊列調(diào)作業(yè)進內(nèi)存內(nèi)層循環(huán)負(fù)責(zé)內(nèi)存中進程的時間片輪轉(zhuǎn)。核心邏輯while (!backlogQueue.isEmpty() || !runningProcesses.isEmpty()) { // 1. 檢查內(nèi)存中的進程是否結(jié)束結(jié)束則回收內(nèi)存并嘗試調(diào)入新作業(yè) for (int i 0; i runningProcesses.size(); i) { Process proc runningProcesses.get(i); if (proc.getRemainingTime() 0) { memoryManager.release(proc); runningProcesses.remove(i); // 有進程結(jié)束從后備隊列按FCFS調(diào)入新作業(yè) if (!backlogQueue.isEmpty()) { Job nextJob backlogQueue.remove(0); loadJobToMemory(nextJob); } } } // 2. 時間片輪轉(zhuǎn)調(diào)度內(nèi)存中的進程 Process current getNextProcessByRR(runningProcesses); if (current ! null) { current.setStatus(2); // 運行 current.setRemainingTime(current.getRemainingTime() - timeSlice); memoryManager.allocate(current); // 3. 可能觸發(fā)阻塞條件模擬I/O等待 if (shouldBlock(current)) { current.setStatus(3); // 阻塞 blockedQueue.add(current); } } }這里有個很關(guān)鍵的細(xì)節(jié)進程結(jié)束釋放內(nèi)存后不是立即從后備隊列調(diào)入作業(yè)而是要先做內(nèi)存回收、再觸發(fā)調(diào)入。順序不能反——先釋放內(nèi)存才能知道空閑分區(qū)有多大否則首次適應(yīng)算法拿到的分區(qū)表是過期的。另外注意 loadJobToMemory 內(nèi)部還要再做一次內(nèi)存分配檢查如果內(nèi)存不夠作業(yè)應(yīng)該留在后備隊列頭部而不是丟棄這就是所謂的“重新排隊”。2.4 作業(yè)調(diào)度模塊的驗證方法調(diào)試作業(yè)調(diào)度最容易翻車的地方是“調(diào)入時機”和“并發(fā)數(shù)控制”。我給個自查清單初始時是否正確調(diào)入了 5 個作業(yè)內(nèi)存中進程結(jié)束一個后后備隊列是否只補一個后備隊列為空時循環(huán)是否還在跑內(nèi)存里的進程作業(yè)調(diào)入時并發(fā)數(shù)是否嚴(yán)格不超過 5。建議在每次作業(yè)調(diào)入或調(diào)出時打印一行日志格式類似[調(diào)度] 作業(yè)3 調(diào)入內(nèi)存當(dāng)前并發(fā)數(shù): 4/5這樣一眼就能看出調(diào)度節(jié)奏對不對。3. 時間片輪轉(zhuǎn)與進程狀態(tài)流轉(zhuǎn)阻塞是怎么插入調(diào)度周期的3.1 RR 調(diào)度中的就緒隊列與阻塞隊列劃分時間片輪轉(zhuǎn)算法的關(guān)鍵是你得維護的就緒隊列順序。這份課設(shè)里內(nèi)存中的進程分成三態(tài)就緒、運行、阻塞。運行態(tài)在同一時刻只有一個就是當(dāng)前拿到 CPU 時間片的進程阻塞態(tài)進程在等待 I/O 或某個事件不參與輪轉(zhuǎn)就緒態(tài)排隊等 CPU。我建議用兩個隊列分別維護就緒和阻塞而不是用一個狀態(tài)字段硬扛否則輪轉(zhuǎn)一圈回來你都不知道誰該被跳過。RR 調(diào)度器的核心實現(xiàn)public Process getNextProcessByRR(ListProcess processes) { // 按上次運行時間排序保證輪轉(zhuǎn)順序穩(wěn)定 processes.sort((p1, p2) - { if (p1.getLastRunTime() ! p2.getLastRunTime()) { return p1.getLastRunTime() - p2.getLastRunTime(); } return p1.getProcessId() - p2.getProcessId(); }); for (Process p : processes) { if (p.getStatus() 1) { // 就緒 p.setLastRunTime(System.currentTimeMillis()); return p; } } return null; // 沒有就緒進程時CPU空轉(zhuǎn) }注意這個排序邏輯lastRunTime 記錄的是進程上一次被調(diào)度的時間用當(dāng)前時間戳作為排序鍵能保證輪轉(zhuǎn)公平性。但如果是純控制臺模擬用時間戳?xí)屆看芜\行結(jié)果都不一樣難以復(fù)現(xiàn) bug。我實際調(diào)試時改成計數(shù)器自增——每輪調(diào)度全局計數(shù)器 1lastRunTime 記錄的是第幾輪這樣日志可復(fù)現(xiàn)排查問題方便得多。這是課設(shè)里值得注意的一個細(xì)節(jié)。3.2 阻塞與喚醒的模擬策略阻塞在這份課設(shè)里是個重點它把進程調(diào)度和作業(yè)調(diào)度連接起來了。常見的模擬方式是給每個進程一個隨機阻塞概率運行到某個時間點就進入阻塞阻塞若干個時間片后自動喚醒回到就緒隊列private boolean shouldBlock(Process p) { // 運行了隨機時長后以一定概率模擬I/O阻塞 if (p.getStatus() 2 p.getRunTicks() p.getBlockAfter()) { return random.nextInt(100) 30; // 30%概率阻塞 } return false; }喚醒邏輯我建議寫在 RR 主循環(huán)的固定位置每輪調(diào)度先檢查阻塞隊列中是否有進程阻塞時間已到到了就喚醒并放入就緒隊列尾部public void wakeUpBlockedProcesses() { IteratorProcess it blockedQueue.iterator(); while (it.hasNext()) { Process p it.next(); p.setBlockTime(p.getBlockTime() - 1); if (p.getBlockTime() 0) { p.setStatus(1); // 就緒 readyQueue.add(p); it.remove(); } } }這里有個隱藏坑喚醒的進程應(yīng)該放到就緒隊列尾部還是頭部教材上通常說喚醒后進入就緒隊列但沒說隊頭還是隊尾。按時間片輪轉(zhuǎn)的公平性應(yīng)該放尾部否則剛被喚醒的進程插隊到老進程前面輪轉(zhuǎn)就亂套了。我在這個點上栽過跟頭日志里看到某個進程連續(xù)兩次拿 CPU 才發(fā)現(xiàn)是喚醒位置寫錯了。3.3 總運行時間片與切換次數(shù)的統(tǒng)計課設(shè)報告里一般要求分析調(diào)度算法的性能指標(biāo)。我在這份源碼的進程類里加了一組統(tǒng)計字段首次運行時間、完成時間、等待時間、周轉(zhuǎn)時間、帶權(quán)周轉(zhuǎn)時間。等待時間累加在調(diào)度主循環(huán)里for (Process p : runningProcesses) { if (p.getStatus() ! 2) { // 非運行態(tài)等待時間1 p.setWaitTime(p.getWaitTime() 1); } }等待時間的統(tǒng)計邏輯是每輪調(diào)度只給非運行態(tài)進程累加。注意阻塞時間算不算等待時間不同課設(shè)要求不一樣——有的老師算有的不算。我建議單獨統(tǒng)計阻塞等待時間和就緒等待時間報告里可以分開列。這樣答辯時老師問“阻塞對性能的影響”你能直接拿出數(shù)據(jù)。4. 首次適應(yīng)內(nèi)存分配與回收空閑分區(qū)鏈的邊界處理4.1 內(nèi)存分區(qū)表的數(shù)據(jù)結(jié)構(gòu)與 FF 算法實現(xiàn)內(nèi)存管理模塊要維護一個空閑分區(qū)表和一個已分配分區(qū)表。首次適應(yīng)First Fit算法的核心是空閑分區(qū)按地址從小到大排序分配時從頭掃描找到第一個足夠大的分區(qū)就切割分配。代碼實現(xiàn)public MemoryBlock allocate(int size) { for (int i 0; i freeBlocks.size(); i) { MemoryBlock block freeBlocks.get(i); if (block.getSize() size) { // 分配從空閑塊中切出size大小 MemoryBlock allocated new MemoryBlock(block.getStart(), size); int remaining block.getSize() - size; if (remaining 0) { // 更新空閑塊為剩余部分 block.setStart(block.getStart() size); block.setSize(remaining); } else { // 正好用完直接從空閑鏈表移除 freeBlocks.remove(i); } return allocated; } } return null; // 內(nèi)存不足 }分區(qū)表用 ArrayList 保存空閑塊每次分配后需要重新按起始地址排序。注意“正好用完”這個分支很多實現(xiàn)會忽略恰好相等的情況導(dǎo)致空閑鏈表里殘留大小為 0 的空塊后面合并時出現(xiàn)下標(biāo)越界。這個邊界問題你遲早會遇到不如現(xiàn)在就把 if/else 寫完整。4.2 內(nèi)存回收與相鄰空閑分區(qū)合并進程結(jié)束時回收內(nèi)存具體要做兩件事把已分配塊轉(zhuǎn)換成空閑塊加入空閑鏈表檢查該塊與相鄰空閑塊是否連續(xù)連續(xù)則合并。這里的相鄰不只是物理相鄰而是地址相鄰具體實現(xiàn)public void release(MemoryBlock block) { freeBlocks.add(new MemoryBlock(block.getStart(), block.getSize())); // 按起始地址排序后檢查相鄰 freeBlocks.sort(Comparator.comparingInt(MemoryBlock::getStart)); for (int i 0; i freeBlocks.size() - 1; i) { MemoryBlock current freeBlocks.get(i); MemoryBlock next freeBlocks.get(i 1); if (current.getStart() current.getSize() next.getStart()) { // 合并 current.setSize(current.getSize() next.getSize()); freeBlocks.remove(i 1); i--; // 繼續(xù)檢查新的相鄰關(guān)系 } } }合并邏輯里有個細(xì)節(jié)合并后需要把游標(biāo) i 減回去否則連續(xù)三個相鄰空閑塊合并時只處理了前兩個第三個漏掉。這是內(nèi)存回收最容易出的 bug——不是合并邏輯看不懂而是循環(huán)游標(biāo)處理不到位。我調(diào)試時打印過每一輪的 freeBlocks 狀態(tài)用地址連續(xù)性檢查腳本對比才發(fā)現(xiàn)這個問題。4.3 首次適應(yīng)相比最佳適應(yīng)壞適應(yīng)的取舍為什么這份課設(shè)選首次適應(yīng)而不是最佳適應(yīng)Best Fit首次適應(yīng)的優(yōu)點是分配速度快、偏向保留高地址大塊空間缺點是低地址區(qū)域會積累小碎片。最佳適應(yīng)相反分配時找最小夠用塊內(nèi)存利用率高但速度慢且產(chǎn)生大量難以利用的微小碎片。對于課設(shè)場景首次適應(yīng)實現(xiàn)簡單、現(xiàn)象直觀更不容易在答辯時被追問到死角。如果老師問“FF 的缺點是什么”你可以回答“低地址區(qū)域的碎片化”——這個回答能顯示你真的理解而不是背代碼。當(dāng)然如果你學(xué)有余力我建議在源碼里再加一個 Best Fit 的實現(xiàn)用 boolean 變量切換兩種算法報告里對比分析碎片率。這屬于加分項一般老師看到這個會說“這個同學(xué)有思考”。4.4 內(nèi)存狀態(tài)的可視化輸出控制臺程序怎么展示內(nèi)存分配做一個簡單的字符畫內(nèi)存映像圖public void printMemoryStatus() { char[] mem new char[MEMORY_SIZE]; Arrays.fill(mem, .); // . 表示空閑 for (MemoryBlock block : allocatedBlocks) { for (int i block.getStart(); i block.getStart() block.getSize(); i) { mem[i] #; } } System.out.println(new String(mem)); }輸出一行 512 個字符的二進制圖一眼就能看出內(nèi)存碎片的情況。這個輸出放在每次進程調(diào)入調(diào)出之后。對照著看首次適應(yīng)算法下低地址區(qū)域的碎片化累積過程非常直觀——你會發(fā)現(xiàn)碎片越來越多新的作業(yè)越來越難找到大的連續(xù)空閑區(qū)。這也是報告里最有說服力的截圖素材。5. 實操避坑從參數(shù)設(shè)置到并發(fā)控制這 7 個坑我已經(jīng)替你踩過了5.1 并發(fā)進程數(shù)超過 5 個作業(yè)提前調(diào)入導(dǎo)致狀態(tài)錯亂現(xiàn)象控制臺輸出里出現(xiàn)了 6 個甚至更多進程同時在內(nèi)存中運行幾個時間片后數(shù)據(jù)開始亂跳。原因作業(yè)調(diào)入內(nèi)存的檢查條件寫錯位置。常見錯誤是把并發(fā)數(shù)檢查寫在 FCFS 調(diào)度器的開頭而不是每次調(diào)入前都檢查。還有一種情況是進程結(jié)束回收內(nèi)存后忘了把進程從 runningProcesses 里移除導(dǎo)致已釋放內(nèi)存的進程還在占用名額。解決在 loadJobToMemory 方法入口處強制校驗if (runningProcesses.size() MAX_CONCURRENCY) return;同時進程結(jié)束回收時必須同時從 runningProcesses、memoryManager 的已分配表兩個地方移除。我建議把并發(fā)數(shù)的檢查收口到一個方法里所有調(diào)入邏輯都走這個方法不旁路。5.2 時間片輪轉(zhuǎn)出現(xiàn)連續(xù)兩個時間片分配給同一進程現(xiàn)象日志顯示進程 A 運行完一個時間片后沒切走下一個時間片還是 A。原因調(diào)度器在選擇下一個進程時把當(dāng)前運行進程的狀態(tài)改成了就緒但排序時 lastRunTime 沒有更新導(dǎo)致按時間戳排序后它還是排在最前面又被打回運行態(tài)。解決進程被調(diào)度器選中時立刻更新 lastRunTime然后在每次調(diào)度前把運行態(tài)的進程改為就緒態(tài)。如果是用計數(shù)器而不是真實時間戳要保證計數(shù)器在每次選擇前自增。這個坑排查起來非常隱蔽因為偶發(fā)情況下看不出來多跑幾個時間片就會發(fā)現(xiàn)某個進程饑餓了。5.3 進程結(jié)束內(nèi)存回收后空閑分區(qū)沒有合并碎片越來越多現(xiàn)象內(nèi)存中空閑空間總值足夠但連續(xù)空閑空間很少新作業(yè)調(diào)入時總是“內(nèi)存不足”。原因回收邏輯里只添加了新空閑塊沒有觸發(fā)相鄰合并或者合并循環(huán)里游標(biāo)處理有問題連續(xù)三個相鄰空閑塊只合并了前兩個。解決每次 release 后都強制走一遍完整的合并循環(huán)并且考慮把相鄰合并提取成公共方法分配和回收時都可以調(diào)用。驗證方法連續(xù)讓多個進程結(jié)束后打印空閑分區(qū)表檢查所有空閑塊的起始地址是否嚴(yán)格遞增、是否還存在相鄰塊未合并。我在 4.2 節(jié)里寫的 i-- 那行就是從坑里爬出來后補上的。5.4 就緒隊列為空時 CPU 空轉(zhuǎn)的無限循環(huán)現(xiàn)象所有進程都進入阻塞態(tài)但程序還在空轉(zhuǎn)控制臺刷屏飛快。原因RR 調(diào)度器返回 null 時主循環(huán)沒有處理這種情況還是繼續(xù)執(zhí)行導(dǎo)致空轉(zhuǎn)死循環(huán)。解決調(diào)度器返回 null 時直接 continue同時每次循環(huán)至少執(zhí)行一次阻塞隊列的檢查——因為只有阻塞進程的阻塞時間遞減才能喚醒進程。更穩(wěn)妥的做法是加一個“全阻塞退出”判斷如果就緒隊列為空且阻塞隊列也即將為空說明所有進程都完成了跳出主循環(huán)。5.5 隨機數(shù)種子不固定同一個 Bug 沒辦法復(fù)現(xiàn)現(xiàn)象第一次跑崩了第二次跑又正常沒法定位問題。原因每次運行 random 生成的作業(yè)參數(shù)不同操作系統(tǒng)課設(shè)不像 Web 開發(fā)那樣輸入確定隨機的數(shù)據(jù)讓排查難上加難。解決給 Random 構(gòu)造器傳入固定種子比如new Random(42)每次跑出來的 10 個作業(yè)完全相同。調(diào)試定位完問題后再改成不傳種子的版本觀察多組數(shù)據(jù)的表現(xiàn)。這個方法只對調(diào)試有用最終提交的版本建議用系統(tǒng)時間做種子否則答辯老師發(fā)現(xiàn)每次都是同一組數(shù)據(jù)會懷疑你的代碼是不是寫死了。5.6 阻塞時間參數(shù)設(shè)置不合理進程永遠醒不過來現(xiàn)象某個進程進入阻塞隊列后再也沒被喚醒其他進程正常輪轉(zhuǎn)系統(tǒng)整體速度緩慢。原因阻塞時間設(shè)置得太長或者喚醒檢查只在特定條件下執(zhí)行阻塞進程被漏掉。常見是把阻塞時間設(shè)為固定大數(shù)或者喚醒邏輯寫在“就緒隊列非空”的分支里阻塞隊列一旦為空就跳過檢查。解決把阻塞隊列的檢查放到主循環(huán)的最前面無論就緒隊列是否為空每輪都先檢查阻塞進程的剩余時間。阻塞時間建議用random.nextInt(3) 11~3 個時間片就夠了太長會導(dǎo)致進程長時間不能參與調(diào)度太短可能進程運行兩下又醒了看不出阻塞現(xiàn)象。5.7 進程 ID 混亂回收后 ID 復(fù)用引發(fā)統(tǒng)計錯誤現(xiàn)象報告里的周轉(zhuǎn)時間統(tǒng)計數(shù)據(jù)明顯不對某個進程的等待時間變成了負(fù)數(shù)。原因進程結(jié)束后從隊列移除但新的作業(yè)調(diào)入時沿用了同樣的編號統(tǒng)計數(shù)組里的歷史數(shù)據(jù)被覆蓋。解決作業(yè) ID 從后備隊列生成時保持唯一進程結(jié)束只標(biāo)記為完成狀態(tài)不重新分配 ID。如果想把 ID 復(fù)用需要在復(fù)用時檢查該 ID 的歷史統(tǒng)計是否已歸檔。最省事的方式是進程對象不復(fù)用結(jié)束就丟棄新作業(yè)進內(nèi)存時新建進程對象這個對象與原作業(yè)共享 jobId 但統(tǒng)計字段獨立。這是我自己在寫報告時加統(tǒng)計功能踩到的坑數(shù)據(jù)對不上特別耗時。6. 從模塊拼裝到完整復(fù)現(xiàn)一份可以照用的驗證清單和調(diào)試套路課設(shè)拿到手先不要急著跑我建議按下面這個順序走一遍20 分鐘內(nèi)能確認(rèn)每個模塊工作正常。第一步打開 README看清楚它定義的運行入口和參數(shù)常量——內(nèi)存總大小、時間片長度、最大并發(fā)數(shù)這三個值決定了整個模擬的行為。如果時間片設(shè)得太大RR 退化成了 FCFS如果內(nèi)存總大小設(shè)得太小10 個作業(yè)可能一個都調(diào)不進去。第二步把隨機數(shù)種子固定跑一次完整流程記錄每一步的控制臺輸出。第三步手動算一遍前 2~3 個作業(yè)的流轉(zhuǎn)過程和控制臺輸出對照確認(rèn)調(diào)度順序沒錯。驗證清單我整理成這樣作業(yè)調(diào)度是否嚴(yán)格 FCFS即后備隊列頭部永遠是最先到達的未調(diào)入作業(yè)并發(fā)進程數(shù)峰值是否≤5RR 輪轉(zhuǎn)順序是否按就緒隊列從頭到尾循環(huán)阻塞進程是否不參與輪轉(zhuǎn)阻塞喚醒后是否進入就緒隊列尾部內(nèi)存回收后空閑分區(qū)表中無相鄰可合并塊所有進程結(jié)束后后備隊列為空、內(nèi)存全部空閑。每一條都可以用一條日志輸出對一次不用猜。我在按這份課設(shè)改寫成自己的版本時把上面的每條驗證做成了一個checkStatus()方法跑完一步就自動檢查一遍。這套思路做完之后它成了我每次操作系統(tǒng)實驗的通用模板后來做存儲管理實驗時換了一組數(shù)據(jù)就繼續(xù)用。復(fù)盤這套課設(shè)我感覺最重要的收獲不是某個算法本身而是理解了“并發(fā)上限為 5 個進程”這個約束是如何把作業(yè)調(diào)度、內(nèi)存分配、進程調(diào)度串成閉環(huán)的——沒有這個上限作業(yè)調(diào)度和進程調(diào)度就是兩套獨立的演示代碼有了它每個模塊之間才真正耦合起來。從那以后我每次拿到一個仿真的課設(shè)都先畫出“狀態(tài)流轉(zhuǎn)圖 資源約束條件”再動手寫代碼這個習(xí)慣讓我少走了很多彎路。希望幫到你。本文還有配套的精品資源點擊獲取