課設代碼包實戰(zhàn)指南:從進程調度到文件系統(tǒng)的完整拆解)
簡介這份資源是華中科技大學操作系統(tǒng)實驗與課設的完整代碼合集面向計算機專業(yè)學生及希望深入理解操作系統(tǒng)原理的自學者覆蓋進程管理、內存管理、文件系統(tǒng)、設備管理、死鎖避免、線程調度、網絡編程與系統(tǒng)調用等核心模塊適合課程實驗、課設參考與考研復習等場景。壓縮包共249個文件約41.66MB以class與jar編譯產物、xml配置、c與java源碼為主另含makefile構建腳本、nc與ihex等硬件相關文件、out與exe可運行結果及少量圖片和文檔便于對照源碼與運行效果。目前已有169人學習下載。通過調度器、文件系統(tǒng)、設備驅動等具體實驗任務讀者可獲取可編譯運行的參考實現(xiàn)、構建腳本與排錯思路并借助清晰的目錄結構快速定位各知識點代碼為系統(tǒng)方向開發(fā)打下實踐基礎。1. 操作系統(tǒng)課設代碼包到底能給你省下多少時間如果你正在準備操作系統(tǒng)課程設計大概率經歷過這樣的場景對著課本上進程調度、內存管理、文件系統(tǒng)的理論描述腦子里能理解個大概但一旦要動手寫代碼就不知道從哪下手。調度算法到底怎么組織數(shù)據(jù)結構頁面置換的命中率怎么統(tǒng)計文件系統(tǒng)的目錄樹用什么結構存這些問題課本不會給你答案但一份完整的課設代碼可以。華中科技大學操作系統(tǒng)實驗與課設的代碼包在課程設計圈子里流傳很廣原因很直接它的實驗覆蓋了操作系統(tǒng)課程的核心模塊從進程管理、線程調度、內存分配、頁面置換到文件系統(tǒng)實現(xiàn)每個實驗都有可運行的代碼和對應的實驗報告。對于正在做課設的人來說這份代碼的價值不在于照抄而在于它提供了一個可運行的參照系——你能看到每個模塊的完整實現(xiàn)路徑知道調度隊列怎么建、信號量怎么封裝、磁盤塊怎么映射。這篇文章會從代碼結構拆解開始逐步講清楚每個實驗模塊的實現(xiàn)思路、關鍵參數(shù)怎么調、編譯運行時會遇到哪些坑以及怎么在它的基礎上改出自己的版本。2. 先看清代碼包的目錄結構和實驗模塊劃分2.1 拿到壓縮包后的第一件事目錄樹和構建入口很多人拿到代碼包之后直接找 main 函數(shù)開始讀這是效率最低的做法。操作系統(tǒng)課設的代碼通常按實驗編號分目錄每個目錄是一個獨立的小型系統(tǒng)或模擬器。正確的第一步是先把目錄樹打印出來看清楚模塊邊界和構建方式。# 解壓后先看頂層結構不要急著進子目錄 unzip operating-system-codes.zip -d os-course cd os-course # 打印兩層目錄樹快速定位每個實驗的入口 find . -maxdepth 2 -type d | sort # 找所有 Makefile 和 CMakeLists確認構建方式 find . -name Makefile -o -name CMakeLists.txt | sort這段命令的邏輯很直接find -maxdepth 2限制遞歸深度避免目錄太多刷屏第二步找構建文件是為了確認每個實驗是獨立編譯還是統(tǒng)一構建。常見的組織方式是每個實驗一個子目錄各自帶 Makefile互不依賴。如果發(fā)現(xiàn)只有一個頂層 Makefile說明所有實驗共用一套構建規(guī)則這時候要重點看 Makefile 里的SRCS和TARGET變量確認每個實驗的源文件列表。參數(shù)方面-maxdepth 2可以根據(jù)實際情況調整。如果目錄層級特別深改成 3 層如果頂層就很清晰保持 2 層足夠。關鍵是要在動手改代碼之前先建立一張模塊地圖。2.2 各實驗模塊的功能邊界和依賴關系典型的操作系統(tǒng)課設代碼包會包含以下幾類實驗每類的功能邊界和依賴關系不同實驗模塊核心功能依賴典型源文件數(shù)進程調度模擬 PCB 管理、調度算法無3-5內存管理分區(qū)分配、頁面置換無4-6文件系統(tǒng)目錄樹、文件讀寫、磁盤塊管理內存管理6-10同步互斥信號量、生產者消費者進程調度2-4設備管理磁盤調度、緩沖池無3-5這張表的作用是幫你判斷閱讀順序。進程調度和內存管理通常是最獨立的模塊適合先讀文件系統(tǒng)往往依賴內存管理的抽象如果先讀文件系統(tǒng)會卡在底層數(shù)據(jù)結構上。同步互斥實驗如果用了自己實現(xiàn)的線程庫需要先理解進程調度的 PCB 結構。我一般會建議按「進程調度 → 同步互斥 → 內存管理 → 文件系統(tǒng) → 設備管理」的順序讀。前兩個模塊建立對 PCB、狀態(tài)轉換、等待隊列的直覺后三個模塊建立對地址空間、頁表、磁盤塊映射的直覺。每個模塊讀完之后先編譯運行一遍看輸出是否符合預期再進入下一個。2.3 編譯環(huán)境和工具鏈的版本確認操作系統(tǒng)課設代碼最常見的翻車點不是邏輯錯誤而是編譯環(huán)境不匹配。很多代碼包是在特定版本的 GCC 和 Linux 內核下寫的換一個環(huán)境就可能報錯。# 確認編譯器版本很多老代碼在 GCC 10 上會有警告變錯誤的問題 gcc --version # 確認系統(tǒng)架構32 位和 64 位代碼在指針長度上不兼容 uname -m # 嘗試編譯第一個實驗先不加任何修改 cd experiment-01-scheduler make 21 | tee build.log編譯日志一定要用tee保存下來因為操作系統(tǒng)課設的代碼經常有大量警告刷屏之后你找不到第一個錯誤在哪。如果編譯失敗先看build.log里第一個error而不是最后一個后面的錯誤往往是第一個錯誤引發(fā)的連鎖反應。常見做法是在 Makefile 里加-w關掉警告但我建議不要這么做。操作系統(tǒng)代碼里的警告經常指向真實問題比如指針截斷、未初始化變量、隱式類型轉換。先讓編譯通過再逐個看警告比直接關掉警告要安全。3. 進程調度與同步互斥實驗的代碼拆解3.1 PCB 數(shù)據(jù)結構和調度隊列的組織方式進程調度實驗的核心是 PCB進程控制塊的設計。代碼包里通常會用結構體或類來表示 PCB關鍵字段包括進程 ID、狀態(tài)、優(yōu)先級、時間片、等待時間、周轉時間等。// 典型的 PCB 結構定義字段命名可能不同但核心信息一致 typedef struct PCB { int pid; // 進程標識符 int state; // 狀態(tài)就緒/運行/阻塞/完成 int priority; // 優(yōu)先級數(shù)值越小優(yōu)先級越高或反之 int burst_time; // 需要占用的 CPU 時間 int remaining_time; // 剩余執(zhí)行時間用于時間片輪轉 int wait_time; // 累計等待時間 int turnaround_time; // 周轉時間 完成時間 - 到達時間 struct PCB *next; // 隊列指針 } PCB; // 就緒隊列通常用鏈表或數(shù)組實現(xiàn) typedef struct { PCB *head; PCB *tail; int count; } ReadyQueue;這段代碼的關鍵在于state字段和next指針的配合。調度算法的本質就是操作這兩個字段把 PCB 從就緒隊列摘下、改狀態(tài)為運行、時間片用完再掛回就緒隊列。如果你要改調度算法改的就是「從就緒隊列選哪個 PCB」和「運行多久后觸發(fā)重新調度」這兩個邏輯。參數(shù)方面priority的語義要特別注意。有的代碼里數(shù)值越小優(yōu)先級越高有的反過來。讀代碼時先找到調度函數(shù)里的比較邏輯確認方向否則你改優(yōu)先級的時候會得到完全相反的結果。burst_time和remaining_time的關系也要看清楚如果調度算法不支持搶占remaining_time可能不會被更新如果支持時間片輪轉每次調度后必須扣減。3.2 時間片輪轉和優(yōu)先級調度的實現(xiàn)差異時間片輪轉和優(yōu)先級調度在代碼結構上的差異主要體現(xiàn)在兩個地方就緒隊列的排序方式和調度觸發(fā)條件。// 時間片輪轉就緒隊列按到達順序排列每次取隊頭 PCB* rr_schedule(ReadyQueue *q, int time_slice) { if (q-count 0) return NULL; PCB *p q-head; q-head p-next; if (q-head NULL) q-tail NULL; q-count--; // 執(zhí)行一個時間片 if (p-remaining_time time_slice) { p-remaining_time - time_slice; p-state STATE_READY; enqueue(q, p); // 沒執(zhí)行完放回隊尾 } else { p-state STATE_DONE; p-turnaround_time current_time - p-arrival_time; } return p; } // 優(yōu)先級調度就緒隊列按優(yōu)先級排序每次取優(yōu)先級最高的 PCB* priority_schedule(ReadyQueue *q) { if (q-count 0) return NULL; // 遍歷找優(yōu)先級最高的 PCB PCB *prev NULL, *best q-head, *best_prev NULL; PCB *cur q-head; while (cur ! NULL) { if (cur-priority best-priority) { best cur; best_prev prev; } prev cur; cur cur-next; } // 從隊列中摘除 best if (best_prev NULL) q-head best-next; else best_prev-next best-next; if (best q-tail) q-tail best_prev; q-count--; return best; }兩段代碼的差異很清晰時間片輪轉的隊列是 FIFO調度邏輯在出隊之后優(yōu)先級調度的隊列需要遍歷找最優(yōu)調度邏輯在出隊之前。如果你要把時間片輪轉改成優(yōu)先級調度不能只改調度函數(shù)還要改入隊邏輯——優(yōu)先級調度需要在入隊時就按優(yōu)先級插入否則每次調度都要遍歷整個隊列效率很低。參數(shù)方面time_slice的取值直接影響調度效果。取值太小會導致上下文切換開銷占比過高取值太大會退化成先來先服務。常見做法是取所有進程平均 burst_time 的 1/2 到 1/3。如果你在實驗報告里要畫調度序列圖建議把time_slice設成 1、2、4 各跑一遍對比周轉時間和等待時間的變化趨勢。3.3 信號量實現(xiàn)里的原子性保證和常見錯誤同步互斥實驗的核心是信號量的 P/V 操作。代碼包里通常會用簡單的整型變量加忙等待來實現(xiàn)但這里有一個容易被忽略的問題P 操作里的判斷和減一必須是原子的。// 有問題的實現(xiàn)判斷和減一之間存在競態(tài)窗口 void P_bad(Semaphore *s) { while (s-value 0) { // 忙等待 } s-value--; // 這里可能被中斷打斷 } // 改進方案用關中斷或原子指令保護臨界區(qū) void P_good(Semaphore *s) { while (1) { disable_interrupts(); // 進入臨界區(qū) if (s-value 0) { s-value--; enable_interrupts(); break; } enable_interrupts(); // 讓出 CPU 或忙等待 } }這段代碼的差異在于臨界區(qū)的保護。在單核模擬環(huán)境里如果 P 操作執(zhí)行到一半被時鐘中斷打斷另一個進程可能看到value還是正數(shù)也進入臨界區(qū)導致兩個進程同時訪問共享資源。disable_interrupts和enable_interrupts是常見的保護手段但要注意關中斷的時間不能太長否則會影響系統(tǒng)響應。參數(shù)方面信號量的初值決定了允許多少個進程同時進入臨界區(qū)?;コ庑盘柫砍踔禐?1同步信號量初值根據(jù)資源數(shù)量設定。如果你在實驗里看到進程卡死先檢查信號量初值是否合理再檢查 P/V 是否配對。常見的錯誤是 P 操作執(zhí)行了但 V 操作沒執(zhí)行或者 V 操作執(zhí)行了兩次導致信號量值溢出。4. 內存管理與文件系統(tǒng)實驗的實現(xiàn)要點4.1 分區(qū)分配算法的數(shù)據(jù)結構選擇內存管理實驗通常要求實現(xiàn)首次適應、最佳適應、最壞適應等分區(qū)分配算法。代碼包里會用空閑分區(qū)表或空閑分區(qū)鏈來管理內存。// 空閑分區(qū)表數(shù)組實現(xiàn)適合分區(qū)數(shù)量固定的場景 typedef struct { int start_addr; // 分區(qū)起始地址 int size; // 分區(qū)大小 int is_free; // 是否空閑 } Partition; Partition memory[MAX_PARTITIONS]; int partition_count 0; // 首次適應從頭遍歷找到第一個滿足大小的空閑分區(qū) int first_fit(int request_size) { for (int i 0; i partition_count; i) { if (memory[i].is_free memory[i].size request_size) { return i; // 返回分區(qū)索引 } } return -1; // 分配失敗 } // 最佳適應遍歷所有空閑分區(qū)找滿足大小的最小分區(qū) int best_fit(int request_size) { int best_idx -1; int best_size INT_MAX; for (int i 0; i partition_count; i) { if (memory[i].is_free memory[i].size request_size) { if (memory[i].size best_size) { best_size memory[i].size; best_idx i; } } } return best_idx; }首次適應和最佳適應的代碼差異很小但性能差異很大。首次適應分配快但容易產生外部碎片最佳適應碎片少但每次分配都要遍歷整個表。如果你在實驗里要對比算法效果建議用同一組請求序列跑不同算法統(tǒng)計分配失敗次數(shù)和碎片大小。參數(shù)方面MAX_PARTITIONS決定了系統(tǒng)能管理的最大分區(qū)數(shù)。如果設得太小頻繁分配釋放后分區(qū)表會滿設得太大遍歷開銷增加。常見做法是設成 100 到 200。另外要注意分區(qū)的合并邏輯釋放一個分區(qū)后如果它和前后的空閑分區(qū)相鄰必須合并否則碎片會越來越多。4.2 頁面置換算法的命中率統(tǒng)計和對比方法頁面置換實驗的核心是統(tǒng)計不同算法的命中率。代碼包里通常會模擬一個固定大小的物理內存和一組頁面訪問序列然后分別用 FIFO、LRU、OPT 等算法跑一遍。# 頁面置換模擬的通用框架 def simulate(pages, frame_count, algorithm): frames [] hits 0 for page in pages: if page in frames: hits 1 if algorithm LRU: frames.remove(page) frames.append(page) # LRU命中后移到末尾 else: if len(frames) frame_count: frames.append(page) else: if algorithm FIFO: frames.pop(0) # 淘汰最早進入的 elif algorithm LRU: frames.pop(0) # 淘汰最久未使用的 elif algorithm OPT: # 淘汰未來最長時間不被訪問的 farthest 0 victim 0 for i, f in enumerate(frames): try: next_use pages[pages.index(page)1:].index(f) except ValueError: next_use float(inf) if next_use farthest: farthest next_use victim i frames.pop(victim) frames.append(page) return hits / len(pages)這段代碼的關鍵在于frames列表的維護方式。FIFO 和 LRU 在代碼上看起來很像都是pop(0)但語義完全不同F(xiàn)IFO 淘汰的是最早進入的頁面LRU 淘汰的是最久未被訪問的頁面。LRU 在命中時需要把頁面移到列表末尾FIFO 不需要。如果你把這兩個算法搞混命中率統(tǒng)計會完全錯誤。參數(shù)方面frame_count決定了物理內存大小。通常實驗會要求跑 3、4、5 個頁框各一遍觀察命中率隨頁框數(shù)增加的變化。pages序列的長度建議至少 1000 次訪問太短的序列統(tǒng)計波動太大。如果你要對比 OPT 和 LRU 的差距OPT 的命中率是理論上限LRU 通常比它低 10% 到 20%如果差距超過 30%檢查你的 LRU 實現(xiàn)是否正確。4.3 文件系統(tǒng)目錄樹和磁盤塊映射的實現(xiàn)文件系統(tǒng)實驗通常是難度最高的部分因為它需要同時管理目錄樹、文件控制塊、磁盤塊分配和空閑塊管理。// 目錄項結構文件名 inode 編號 typedef struct { char name[MAX_FILENAME]; int inode_id; } DirEntry; // inode 結構文件元信息 數(shù)據(jù)塊指針 typedef struct { int id; int size; int type; // 文件或目錄 int block_ptrs[MAX_BLOCKS]; // 直接塊指針 int indirect_ptr; // 間接塊指針 } Inode; // 創(chuàng)建文件分配 inode在父目錄中添加目錄項 int create_file(int parent_dir_inode, const char *name) { int new_inode alloc_inode(); if (new_inode 0) return -1; // 在父目錄的數(shù)據(jù)塊中寫入目錄項 DirEntry entry; strncpy(entry.name, name, MAX_FILENAME); entry.inode_id new_inode; add_dir_entry(parent_dir_inode, entry); return new_inode; }這段代碼的核心是 inode 和目錄項的分離。inode 存文件的元信息和數(shù)據(jù)塊指針目錄項只存文件名和 inode 編號。這種設計的好處是目錄項大小固定方便在磁盤塊中順序存儲缺點是查找文件需要先讀目錄項再讀 inode多一次磁盤訪問。參數(shù)方面MAX_BLOCKS決定了單個文件的最大直接塊數(shù)量。如果文件很大需要用到間接塊。間接塊的實現(xiàn)是文件系統(tǒng)實驗里最容易出錯的地方一級間接塊存的是數(shù)據(jù)塊指針二級間接塊存的是間接塊指針。如果你在實驗里遇到大文件讀寫失敗先檢查間接塊的分配和釋放邏輯。5. 避坑與排查課設代碼跑不起來時先看這幾條5.1 編譯報錯頭文件缺失和類型沖突現(xiàn)象make時報fatal error: xxx.h: No such file or directory或者error: conflicting types for xxx。原因代碼包可能依賴特定版本的庫或頭文件或者在不同源文件里對同一個函數(shù)/變量的聲明不一致。操作系統(tǒng)課設代碼經常有多個源文件共享全局變量如果頭文件里聲明了extern int current_time;但某個源文件里定義成了static int current_time;就會報類型沖突。解決先看報錯的頭文件名確認系統(tǒng)里是否安裝了對應的開發(fā)包。如果是類型沖突用grep -rn xxx .找到所有聲明和定義的位置統(tǒng)一類型。常見做法是把所有全局變量聲明集中到一個公共頭文件里其他源文件只包含這個頭文件。5.2 運行時崩潰段錯誤和死循環(huán)現(xiàn)象程序編譯通過但運行時報Segmentation fault或者卡住不動。原因段錯誤通常是空指針解引用或數(shù)組越界。操作系統(tǒng)課設代碼里常見的空指針場景包括從空隊列取 PCB、訪問未初始化的頁表項、目錄項指針為 NULL 時直接解引用。死循環(huán)常見于信號量的忙等待邏輯如果 P 操作的條件永遠不滿足進程會一直循環(huán)。解決用gdb跑一遍崩潰時看bt輸出定位到具體行。如果是死循環(huán)用CtrlC中斷后看where輸出。常見做法是在隊列操作和信號量操作里加斷言比如assert(q-count 0)讓問題盡早暴露。5.3 結果不對調度序列和預期不一致現(xiàn)象程序能跑但輸出的調度序列、命中率、分配結果和預期不符。原因最常見的是參數(shù)方向搞反了。比如優(yōu)先級調度里代碼認為數(shù)值越大優(yōu)先級越高但你按數(shù)值越小優(yōu)先級越高來構造測試數(shù)據(jù)。另一個常見原因是時間統(tǒng)計的基準點不對比如周轉時間應該用完成時間減到達時間但代碼里用了當前時間減到達時間。解決先打印中間狀態(tài)比如每次調度前后的就緒隊列內容、每次頁面訪問后的頁框內容。對比手工模擬的結果找到第一個不一致的位置。常見做法是在關鍵函數(shù)入口加printf輸出輸入?yún)?shù)和返回值跑一個小規(guī)模測試用例逐步縮小范圍。5.4 性能異常算法耗時遠超預期現(xiàn)象小規(guī)模測試正常但數(shù)據(jù)量一大就慢得離譜。原因通常是數(shù)據(jù)結構選擇不當。比如優(yōu)先級調度用鏈表每次遍歷找最優(yōu)頁面置換用數(shù)組每次線性查找文件系統(tǒng)用線性掃描找空閑塊。這些操作在小規(guī)模下看不出來規(guī)模一大就變成瓶頸。解決把線性查找改成有序插入或哈希表。比如優(yōu)先級調度可以在入隊時按優(yōu)先級插入出隊時直接取隊頭空閑塊管理可以用位圖或空閑鏈表。常見做法是先保證正確性再用time命令或clock()函數(shù)測量耗時確認瓶頸在哪個函數(shù)再針對性優(yōu)化。5.5 環(huán)境差異換一臺機器就編譯失敗現(xiàn)象在自己的機器上能跑換到實驗室機器或同學機器上就報錯。原因編譯器版本、系統(tǒng)架構、庫版本不一致。比如代碼里用了gets這種在新版本 GCC 里被移除的函數(shù)或者用了 32 位特有的指針轉換。解決在 Makefile 里顯式指定-stdc99或-stdc11避免編譯器默認標準不一致。指針轉換用intptr_t而不是int。常見做法是在項目根目錄放一個README寫清楚編譯命令和依賴版本換機器時先按 README 跑一遍。6. 怎么在課設代碼基礎上改出自己的版本6.1 從「能跑」到「能改」先加日志再動邏輯很多人拿到代碼包后直接改核心邏輯結果改完之后跑不起來也說不清楚哪里出了問題。我的習慣是先在關鍵路徑上加日志確認自己完全理解了代碼的執(zhí)行流程再動手改。// 在調度函數(shù)入口加日志輸出當前狀態(tài) PCB* schedule(ReadyQueue *q) { printf([DEBUG] before schedule: queue_count%d, current_time%d\n, q-count, current_time); PCB *p pick_next(q); printf([DEBUG] picked pid%d, remaining%d, priority%d\n, p-pid, p-remaining_time, p-priority); return p; }日志的作用是建立反饋回路。你改了一行代碼跑一遍看日志輸出是否符合預期。如果不符合你能立刻知道問題出在哪個環(huán)節(jié)。沒有日志的情況下你只能看最終結果排查范圍太大。參數(shù)方面日志的粒度要適中。太粗看不到細節(jié)太細刷屏。我一般會在調度、分配、置換這三個關鍵操作上加日志其他輔助函數(shù)不加。日志格式統(tǒng)一用[DEBUG]前綴方便用grep過濾。6.2 改調度算法從時間片輪轉到多級反饋隊列如果你要把時間片輪轉改成多級反饋隊列改動集中在三個地方就緒隊列的組織、時間片的分配、進程降級邏輯。改動點時間片輪轉多級反饋隊列就緒隊列單個 FIFO 隊列多個優(yōu)先級隊列時間片固定值隨優(yōu)先級降低而增大入隊邏輯直接放隊尾按優(yōu)先級放入對應隊列降級邏輯無時間片用完降一級實現(xiàn)時先定義多個隊列和對應的時間片數(shù)組然后改pick_next函數(shù)從最高優(yōu)先級隊列開始找找到就取隊頭如果高優(yōu)先級隊列為空降級到下一級。進程用完時間片后如果還有剩余執(zhí)行時間從當前隊列移到下一級隊列的隊尾。常見坑是降級邏輯的邊界條件最低一級隊列的進程用完時間片后是放回原隊列還是繼續(xù)降級通常是放回原隊列否則進程永遠無法完成。另一個坑是時間片的計算如果時間片按 2 的冪次增長要防止溢出。6.3 改頁面置換從 LRU 到 Clock 算法LRU 的缺點是每次訪問都要移動頁面開銷大。Clock 算法用循環(huán)隊列和訪問位來近似 LRU開銷小很多。# Clock 算法循環(huán)掃描訪問位為 1 則清零并跳過為 0 則淘汰 def clock_replace(frames, access_bits, hand, new_page): while True: if access_bits[hand] 0: victim frames[hand] frames[hand] new_page access_bits[hand] 1 hand (hand 1) % len(frames) return victim else: access_bits[hand] 0 hand (hand 1) % len(frames)這段代碼的關鍵是hand指針和access_bits數(shù)組的配合。每次訪問頁面時把對應的訪問位置 1需要淘汰時從hand開始掃描遇到訪問位為 1 的就清零并繼續(xù)遇到 0 的就淘汰。這樣被訪問過的頁面有第二次機會不會被立即淘汰。參數(shù)方面access_bits的長度和frames一致初值全為 0。hand初值可以設為 0。如果你要對比 Clock 和 LRU 的命中率Clock 通常比 LRU 低 5% 到 10%但每次訪問的開銷小很多。在實驗報告里可以統(tǒng)計總訪問次數(shù)和總掃描次數(shù)量化這個 trade-off。6.4 驗證方法用已知結果反推實現(xiàn)是否正確改完代碼后怎么確認自己的實現(xiàn)是對的我的做法是構造小規(guī)模測試用例手工算出預期結果再和程序輸出對比。比如頁面置換實驗構造訪問序列[1, 2, 3, 4, 1, 2, 5, 1, 2, 3]頁框數(shù)為 3手工模擬 FIFO 和 LRU 的命中次數(shù)。FIFO 的命中次數(shù)是 3LRU 的命中次數(shù)是 4。如果程序輸出和這個不一致說明實現(xiàn)有問題。調度實驗同理構造 3 個進程到達時間分別為 0、1、2burst_time 分別為 5、3、2手工畫出甘特圖算出平均周轉時間。程序輸出和手工結果對比誤差應該在浮點精度范圍內。這種驗證方法的優(yōu)點是反饋快、定位準。你不需要等大規(guī)模測試跑完小規(guī)模用例幾秒鐘就能出結果。缺點是覆蓋不全所以小規(guī)模驗證通過后還要跑一組隨機數(shù)據(jù)檢查有沒有崩潰或異常輸出。6.5 一個我踩過的坑別在最后一天改核心數(shù)據(jù)結構血淚經驗課設截止前一天我把 PCB 的存儲方式從數(shù)組改成鏈表結果所有依賴下標訪問的代碼全部報錯改到凌晨三點才跑通。后來我給自己定了一條規(guī)矩核心數(shù)據(jù)結構在課設開始的第一周就定下來后面只改算法邏輯不改數(shù)據(jù)結構。如果你確實需要改數(shù)據(jù)結構先寫一個適配層把舊接口映射到新結構上保證上層代碼不用動。比如從數(shù)組改成鏈表可以保留get_pcb_by_index函數(shù)內部遍歷鏈表找到第 i 個元素。這樣上層代碼不用改你只需要改適配層的實現(xiàn)。另一個習慣是每次改完代碼后立刻git commit寫清楚改了什么。課設代碼經常需要回退到之前的版本沒有版本控制的話你只能靠記憶恢復很容易出錯。希望幫到你。本文還有配套的精品資源點擊獲取