內核實驗:從啟動到進程調度的完整實戰(zhàn)指南)
簡介上海交通大學頭歌實踐教學平臺Chcore操作系統(tǒng)實驗整理版docx資料定位為操作系統(tǒng)課程配套學習材料適合正在完成內存管理、系統(tǒng)調用與缺頁異常實驗的本科生與自學者。文檔以實驗二內存管理和實驗三系統(tǒng)調用與缺頁異常為主線系統(tǒng)梳理分頁機制、頁表管理、內存分配與回收、內存保護、換頁流程、系統(tǒng)調用實現(xiàn)、中斷與異常處理、缺頁異常處理流程、頁替換策略及緩存預讀取等核心知識點并給出評測可通的關鍵命令提示方便對照環(huán)境操作和排錯。壓縮包內共1個docx文檔整體大小583KB內容集中適合直接閱讀或打印。目前已有5031人瀏覽學習適合借助具體實驗深入理解操作系統(tǒng)底層機制快速完成實驗并提升系統(tǒng)編程能力。 如果你正在學操作系統(tǒng)、但又不想只看書刷題那我強烈建議你去“頭歌”上把 ChCore 操作系統(tǒng)實驗完整做一遍。這個實驗不是那種給一個虛擬教學系統(tǒng)寫兩行代碼就算完事的作業(yè)而是讓你真正把一個教學操作系統(tǒng)內核從啟動到進程調度跑起來。ChCore 是上海交大軟件學院設計的教學操作系統(tǒng)和很多學校用的 xv6、uCore 系列相比它的代碼結構更貼近現(xiàn)代內核的組織方式實驗題目的梯度也安排得比較合理。做完一遍之后你對 Linux 內核里“進程”“虛擬內存”“系統(tǒng)調用”“調度器”這些概念的理解會完全不一樣。這篇文章我會從一個實際做完這套實驗的視角把 ChCore 實驗的整體思路、核心知識點、實操流程和踩坑記錄全部梳理一遍。不管你是剛開始接觸操作系統(tǒng)實驗的本科生還是想補內核基礎的在職開發(fā)者只要愿意花時間跟著動手這套實驗都能給你帶來實打實的提升。1. 實驗整體設計與思路拆解1.1 ChCore 實驗到底在做什么ChCore 實驗的目標很明確讓你從零開始逐步實現(xiàn)一個可運行的操作系統(tǒng)內核。整個實驗基于 ARM64 架構這和大部分學校教的 x86 平臺不太一樣但 ARM64 在現(xiàn)代移動設備和服務器領域已經是絕對主流所以學這個并不吃虧。實驗被拆成多個 Lab每個 Lab 對應內核的一個核心模塊。按目前常見的版本大致包括啟動與內核初始化、物理內存管理、虛擬內存與頁表、異常與系統(tǒng)調用、進程管理與調度、進程間通信等。每個 Lab 內部又分為若干個小任務任務之間有明顯的遞進關系前一個任務沒做好后面的實驗基本跑不起來。這里我想先解釋一個很多人困惑的問題為什么操作系統(tǒng)實驗不直接讓大家去讀 Linux 源碼因為 Linux 源碼體量太大一個初學者扎進去很容易迷失在復雜的驅動和兼容性代碼里。ChCore 的做法是給出一個精簡但五臟俱全的內核骨架把核心機制用清晰的文件組織起來然后在關鍵位置留空給你填。這樣既能讓你理解真實內核的工作方式又不會因為代碼量過大而勸退。1.2 為什么這套實驗值得認真做我自己做過不少教學內核實驗橫向對比下來ChCore 有幾個特點很突出。第一它的代碼結構非常接近真實工程。ChCore 不是把幾個 .c 文件堆在一起的教學玩具而是有相對完整的構建系統(tǒng)、Makefile、鏈接腳本和內核加載流程。做完實驗你會對“一個內核是怎么被編譯、鏈接、引導起來的”有一個整體認識這種認知在只做算法題或者寫應用層代碼時很難獲得。第二實驗里涉及的概念都是操作系統(tǒng)課程的核心考點。頁表、異常向量表、上下文切換、調度隊列、鎖這些既是面試常問的內容也是理解現(xiàn)代操作系統(tǒng)不能繞開的東西。你在 ChCore 里親手寫過一遍之后再去看到 Linux 里對應的實現(xiàn)會有一種“原來如此”的貫通感。第三頭歌平臺本身做了很好的實驗流程封裝。你不需要手動搭建交叉編譯環(huán)境也不用愁怎么把內核燒到開發(fā)板上平臺已經把編譯器和運行環(huán)境都準備好了你只需要在線編寫代碼、提交驗證。對很多被實驗環(huán)境折騰到放棄的同學來說這一點非常友好。2. 核心知識點解析與關鍵技術點2.1 內核啟動與初始化第一次接觸鏈接腳本和啟動代碼ChCore 實驗的第一個攔路虎通常是 Lab1 的啟動部分。這里涉及兩個關鍵內容鏈接腳本和啟動匯編代碼。鏈接腳本linker script的作用是告訴鏈接器內核的各個段應該放在什么地址。ChCore 運行在 ARM64 平臺內核一般會被加載到高地址。如果你不理解鏈接腳本就會出現(xiàn)一種很詭異的現(xiàn)象代碼燒進去后PC程序計數(shù)器跳到一個不存在的地址然后整個內核靜默死掉。啟動代碼則是用匯編寫的。ARM64 的啟動過程要設置棧指針、清零 BSS 段、設置異常向量表最后跳轉到 C 語言的入口函數(shù)。這些步驟聽起來簡單但順序錯了或者寄存器用錯了都會導致內核無法啟動。我個人的學習經驗是這里不要急著跳過去哪怕你把代碼抄一遍也必須搞清楚每一行匯編在做什么。因為后面異常處理、上下文切換里還會出現(xiàn)大量匯編代碼啟動階段的基礎沒打好后面會越看越懵。2.2 物理內存與虛擬內存看清地址轉換的本質內存管理是操作系統(tǒng)實驗的重頭戲ChCore 在這里的設計也很有代表性。物理內存管理模塊負責把物理頁分配出去。你會實現(xiàn)一個頁分配器管理哪些物理頁是空閑的、哪些已經被占用。這里往往要求用鏈表或者位圖來記錄頁的狀態(tài)。聽起來不太難但要做到分配效率高、碎片少還是需要花點心思的。虛擬內存部分則涉及頁表。ARM64 使用多級頁表你需要理解頁表項的格式、地址翻譯的過程以及如何在內核里操作頁表。ChCore 會讓你實現(xiàn)地址映射、取消映射、權限設置這些操作。寫這部分代碼的時候你會對“虛擬地址和物理地址是怎么一一對應的”有非常直觀的體會。再往后你會遇到“缺頁異常”。當一個程序訪問了還沒有映射到物理頁的虛擬地址時CPU 會觸發(fā)缺頁異常內核需要在這個異常處理函數(shù)里完成頁的分配和映射。這個過程要是實現(xiàn)得不對用戶程序就會莫名其妙地崩潰。排這類問題往往是實驗里最痛苦但也最有收獲的環(huán)節(jié)。2.3 異常處理與系統(tǒng)調用內核態(tài)和用戶態(tài)的橋梁ChCore 的異常處理實驗會讓你實現(xiàn)異常向量表處理來自用戶態(tài)的系統(tǒng)調用。系統(tǒng)調用的流程是用戶程序通過一條特殊的指令陷入內核CPU 跳到異常向量表中對應的入口然后內核保存現(xiàn)場、執(zhí)行系統(tǒng)調用處理函數(shù)、恢復現(xiàn)場最后返回用戶態(tài)。這套機制看起來很標準但實際寫的時候問題很多。比如怎么區(qū)分異常來源是內核態(tài)還是用戶態(tài)怎么正確的保存和恢復寄存器系統(tǒng)調用號和參數(shù)怎么傳遞返回值怎么寫給用戶程序這些細節(jié)任何一個出錯用戶程序都會拿不到正確的結果。調試這個階段的問題通常需要結合 GDB 或者打印日志來看。我建議你從一開始就給關鍵函數(shù)加上清晰的打印內核崩潰的時候至少能看出來是死在哪個函數(shù)、哪個地址不然光靠猜真的會瘋掉。2.4 進程管理與調度讓多個程序輪流跑起來進程管理實驗的目標是讓內核能夠創(chuàng)建、切換和調度多個進程。這里最核心的一段代碼是上下文切換函數(shù)通常叫switch_to或者context_switch。它負責保存當前進程的寄存器現(xiàn)場恢復下一個進程的寄存器現(xiàn)場然后跳轉到下一個進程的指令流里。在這個階段你會實現(xiàn)進程控制塊PCB用一個結構體來記錄進程的狀態(tài)、棧、調度信息等。然后實現(xiàn)調度器從就緒隊列里挑一個進程來運行。ChCore 里通常會有時間片輪轉或者優(yōu)先級調度的要求你需要選擇合適的調度策略并保證正確性。調度實驗的難點在于并發(fā)。多個進程共用一個 CPU稍不注意就會互相踩內存、破壞棧出現(xiàn)各種奇奇怪怪的錯誤。很多你在應用層寫代碼時不會遇到的內存破壞問題在這里會集中爆發(fā)。3. 實操過程從環(huán)境準備到逐關完成3.1 環(huán)境準備與工程結構如果你是在頭歌平臺做實驗環(huán)境問題基本被解決了大半。你只需要登錄平臺進入對應的 ChCore 實驗課程打開在線代碼倉庫就能直接開始寫代碼。平臺通常會提供一個倉庫地址和一個遠程代碼提交入口。你可以在線編輯代碼也可以把倉庫下載到本地開發(fā)再推送到遠程去驗證。我個人的習慣是下載到本地用 IDE 寫因為本地代碼補全、跳轉和調試都更方便。ChCore 工程的目錄結構大致如下kernel/ common/ 公共工具如鏈表、日志 mm/ 內存管理相關代碼 arch/ 架構相關代碼啟動、異常、匯編 process/ 進程管理和調度 syscall/ 系統(tǒng)調用實現(xiàn) ipc/ 進程間通信 CMakeLists.txt 構建配置 Makefile 編譯腳本你的任務就是把這些目錄里標識了 TODO 或者留空的地方補全。每個實驗的題目會明確指出你要在哪個文件里完成什么函數(shù)照著要求一步步做就行。3.2 實驗 1啟動與基礎輸出Lab1 一般要求你完成內核的鏈接腳本配置讓內核能夠被正確加載到高地址然后建立基本的輸出能力。這里你可能需要修改的是鏈接腳本里的內存布局以及在匯編啟動代碼里初始化串口輸出。我第一次做的時候就在這里栽了跟頭。鏈接腳本里某個段的加載地址和執(zhí)行地址沒區(qū)分開導致內核雖然被加載到了正確位置但運行時訪問的地址不對整個系統(tǒng)直接卡死。后來我把鏈接腳本里AT和運行地址的區(qū)別搞明白之后才真正理解“加載地址”和“運行地址”不是一回事。3.3 實驗 2/3內存管理代碼實戰(zhàn)內存管理相關的實驗建議按照“先物理內存、再虛擬內存、后缺頁異?!钡捻樞騺碜?。物理內存分配器寫起來相對簡單關鍵是理解頁的數(shù)據(jù)結構設計。ChCore 用了一個經典的思路用一個全局的struct page數(shù)組來管理所有物理頁每個頁結構里用鏈表指針把空閑頁串起來。寫虛擬內存相關代碼的時候頁表操作要非常小心。ARM64 的頁表項里有很多位段比如類型位、有效位、訪問權限位等。你在設置映射的時候必須按照規(guī)范把這些位填好否則訪問內存就會觸發(fā)異常。這里分享一個提升效率的技巧你可以先實現(xiàn)一個簡單的地址打印函數(shù)在映射完成后把虛擬地址和物理地址的對齊關系打印出來肉眼比對一下是否和預期一致。這比直接用 GDB 打斷點要直觀得多。3.4 實驗 4/5系統(tǒng)調用與進程調度實現(xiàn)系統(tǒng)調用實驗的核心是實現(xiàn)syscall的派發(fā)邏輯和處理函數(shù)。你需要定義一個系統(tǒng)調用號到處理函數(shù)的映射表然后根據(jù)用戶傳入的系統(tǒng)調用號跳轉執(zhí)行。一個常見的設計是syscall總入口是一個匯編函數(shù)它把當前進程的寄存器現(xiàn)場壓入內核棧然后調用 C 語言的syscall_handler。在syscall_handler里你用 switch-case 或者函數(shù)指針表找到對應的處理函數(shù)執(zhí)行完成后再把返回值寫回到保存的寄存器里。調度器的實現(xiàn)則要看裝備的是哪種調度算法。如果要求時間片輪轉你就需要維護一個 FIFO 的就緒隊列每次定時器中斷觸發(fā)時把當前進程放到隊尾然后取出隊首進程來運行。寫到這里我想特別提醒上下文切換函數(shù)必須非常謹慎地處理棧指針和返回地址。很多調度器崩潰都是因為切換時棧沒有切換對導致返回到了錯誤的地址。我一般會在切換函數(shù)前后打印當前進程的 PID這樣一旦崩潰能快速定位是哪個進程切出去了。3.5 調試與驗證讓輸出成為你的朋友內核調試和普通應用調試很不一樣。在內核里你不能輕易用printf因為可能你連堆都還沒初始化好。ChCore 提供了一些日志接口你可以用它來輸出調試信息但要注意在關鍵路徑上輸出太多日志會影響時序反過來導致 bug 更難復現(xiàn)。我的經驗是盡量把日志打印放在“狀態(tài)變化”的位置而不是“每個循環(huán)”里。比如在進程狀態(tài)從就緒變?yōu)檫\行時打一條在頁表映射成功時打一條這樣既能追蹤流程又不會刷屏。如果平臺支持 GDB 遠程調試那是最好不過的。你可以在內核入口處打斷點單步執(zhí)行啟動流程觀察寄存器的變化。這種斷點級別的調試對理解底層代碼幫助巨大。4. 常見問題與排查技巧實錄4.1 內核啟動即崩潰沒有任何輸出這類問題十有八九是鏈接腳本、啟動匯編或者最基本的串口配置出了問題。排查順序建議是檢查鏈接腳本里的入口地址是否正確檢查啟動匯編里是否設置了棧指針檢查 BSS 段是否清空檢查串口的初始化代碼是否被編譯進內核。如果你用的是頭歌的在線環(huán)境可以試著干凈地重新編譯一次因為有時候是緩存導致的鏈接錯誤。4.2 分配物理頁后訪問出現(xiàn)異常物理頁分配好之后如果不做映射或者映射的權限不對訪問就會出問題。這里需要確認兩點一是頁的虛擬地址是否被正確映射到分配的物理地址二是頁表項里的權限位是否允許當前 CPU 模式訪問。一個容易忽略的地方是內核態(tài)訪問用戶態(tài)內存時權限位檢查的邏輯可能和你想的不一樣。ARM64 的權限控制里區(qū)分了 EL1內核和 EL0用戶態(tài)你得確保頁表項設置了AP[1]或類似允許內核訪問的位。4.3 系統(tǒng)調用返回后用戶程序依舊崩潰系統(tǒng)調用執(zhí)行完安全返回用戶態(tài)是一個很精密的環(huán)節(jié)。你可以檢查以下幾個方面廣東省保存和恢復的寄存器數(shù)量是否一致系統(tǒng)調用返回值是否寫到了保存的寄存器里返回地址是否被正確恢復到了發(fā)生異常時的 PC是否把spsr和elr也正確處理了。最經典的錯誤是在異常處理里用了一個臨時變量結果在恢復現(xiàn)場時把原本的寄存器給覆蓋了。這種問題盯著代碼看半天看不出來最好用 GDB 在異常返回的位置打一個斷點觀察每個寄存器的值是否符合預期。4.4 調度器切了幾次就死機調度器崩潰常見原因有兩個一個是就緒隊列指針被破壞另一個是上下文切換時棧搞錯了。你可以把就緒隊列的插入和刪除操作都加上日志看隊列長度是否正確同時在切換時打印“從進程 A 切到進程 B”如果發(fā)現(xiàn)進程號突然出現(xiàn)異常值就說明 PCB 的內存被污染了。另外調度器里面如果有臨界區(qū)資源要記得加鎖。兩個進程同時修改就緒隊列會造成鏈表環(huán)這種偶發(fā)問題特別難查。4.5 常見問題速查表現(xiàn)象優(yōu)先排查點補充說明啟動無輸出鏈接腳本地址、棧指針、串口初始化可以先用最小的串口輸出代碼逐一驗證訪問內存觸發(fā)異常頁表映射、權限位、地址對齊用地址打印函數(shù)驗證映射關系用戶程序崩潰系統(tǒng)調用返回值、現(xiàn)場恢復重點檢查 ELR、SPSR、通用寄存器調度幾次后死機隊列指針、棧切換、競態(tài)條件給隊列操作加日志確認 PCB 未被污染內核打印亂碼串口波特率、時鐘初始化檢查時鐘和外設的初始化順序5. 寫在最后的一點個人體會我把 ChCore 實驗完整做完之后最大的感受是操作系統(tǒng)里那些抽象概念真的是“紙上得來終覺淺”。你背十遍“頁表是多級映射的”不如實際寫一遍頁表操作來得深刻。那些在課堂上聽起來云里霧里的術語比如“陷入內核”“上下文切換”“就緒隊列”在親手敲完代碼之后突然就變成了非常具體的東西。如果你是自學的建議嚴格按照 Lab 順序走不要跳關。前面的啟動和內存管理沒搞懂后面的進程調度會舉步維艱。同時一定要學會利用頭歌平臺的驗證機制每完成一個小任務就及時提交看結果拖到最后一次性排一堆 bug那是真的絕望。最后再分享一個小技巧如果你在某個實驗里卡了很久不妨把題目要求重新讀一遍然后把你已經實現(xiàn)的部分全部注釋掉從頭再按自己的思路實現(xiàn)一遍。很多時候你認為“這里應該這么寫”和題目真正要求的“你應該在這里做什么”之間存在一個被忽略的差距。重寫一遍往往能讓你意識到自己思路里的盲區(qū)。本文還有配套的精品資源點擊獲取