:從下載到跑通全流程)
簡介這是一份面向操作系統(tǒng)內(nèi)核研究者和Windows驅(qū)動開發(fā)學(xué)習(xí)者的ReactOS 0.3.15源碼包可用于分析類Windows系統(tǒng)架構(gòu)、對比開源實現(xiàn)并開展內(nèi)核調(diào)試實驗。包內(nèi)共2000個文件以C語言源代碼551個.c和頭文件1376個.h為主輔以少量txt說明文檔和1個cpp文件壓縮后約83.32MB。描述提到該版本在VS2012環(huán)境下可成功編譯出ntoskrnl.exe與ntoskrnl.pdb意味著讀者能夠借助源碼完成有限度的內(nèi)核級調(diào)試適合具備一定C/C基礎(chǔ)并希望深入操作系統(tǒng)底層的人群。目前已有174人學(xué)習(xí)下載在同類開源系統(tǒng)源碼中具備一定參考價值。通過閱讀這份代碼可以理解Windows NT內(nèi)核的關(guān)鍵模塊劃分、系統(tǒng)調(diào)用流程以及驅(qū)動接口實現(xiàn)為后續(xù)參與操作系統(tǒng)移植或內(nèi)核安全研究打下基礎(chǔ)。 拿到這個 ReactOS-0.3.15-REL-src.zip 的時候相信不少人跟我一樣第一眼就被一長串縮寫弄得有點懵。其實拆分下來很直白ReactOS 是項目名0.3.15 是版本號REL 表示這是正式發(fā)布版src 是 source 的縮寫zip 就是最常見的壓縮格式。這整套組合在一起就是一款開源、號稱追求 Windows 兼容的操作系統(tǒng)的完整源代碼快照。我這次寫作前專門把這個老包又翻出來重新解壓、編譯、跑了幾遍中間踩了幾個歷史遺留的坑也把當(dāng)年一直沒太捋清的目錄關(guān)系重新理了一遍。這篇文章就把整個過程和核心思路寫透給想研究 ReactOS 源碼的讀者一份可以直接照著做的參考。1. 先搞清楚這個壓縮包到底值不值得下1.1 一句話定義一個追求 Windows 兼容的開源系統(tǒng)源碼ReactOS 這個項目跟 Linux 其實沒有直接關(guān)系。它是從零開始、以兼容 Windows 應(yīng)用程序和設(shè)備驅(qū)動為目標(biāo)的一整套獨立操作系統(tǒng)。注意這里說的是“兼容”不是“基于”也不是“使用”。整個系統(tǒng)的代碼都是重新寫的目的是讓 Windows 的 exe、dll、驅(qū)動等二進(jìn)制文件能在這套系統(tǒng)里以接近原生的方式運(yùn)行起來。那這個 zip 包里裝的就是 0.3.15 這個時間點上所有源碼的集合。解壓之后你能看到內(nèi)核、圖形子系統(tǒng)、驅(qū)動框架、系統(tǒng)工具、開發(fā)庫這些東西的完整實現(xiàn)。整套代碼規(guī)模不算小但比起現(xiàn)代操作系統(tǒng)動輒幾千萬行的體量它又足夠輕量適合一個人或者一個小團(tuán)隊通讀關(guān)鍵模塊。這也是它長期被當(dāng)作操作系統(tǒng)課程實踐素材的原因之一。下載這個包的人大概率是三類一類是學(xué)操作系統(tǒng)原理的學(xué)生想看看真正的內(nèi)核代碼長什么樣一類是研究 Windows 內(nèi)部機(jī)制的開發(fā)人員想通過一個可運(yùn)行、可修改、可斷點調(diào)式的開源系統(tǒng)來驗證自己的想法還有一類純粹好奇想看看號稱“Windows 克隆”的玩意兒到底做到了什么程度。無論你是哪一類0.3.15 都是一個很合適的入口版本。1.2 為什么特意去找 0.3.15 這個老版本你可能會問ReactOS 現(xiàn)在版本號都到 0.4.x 甚至更新了為什么還有人翻 0.3.15 這種老版本我當(dāng)初也是無意中在一個鏡像站點看到這個名字才下載的。事后復(fù)盤老版本有幾個不可替代的價值。第一0.3.15 處在 0.3 系列的中后段代碼結(jié)構(gòu)已經(jīng)相對穩(wěn)定但又沒有后來 0.4 系列那么大動干戈的重構(gòu)。對整個項目來說它更像是從“勉強(qiáng)能跑”到“初步可用”的一個過渡節(jié)點研究這個版本能清楚看到設(shè)計上的取舍。第二老版本代碼量更小編譯更快對硬件要求也更低普通虛擬機(jī)開 512M 內(nèi)存就能流暢跑起來用來學(xué)習(xí)內(nèi)核和子系統(tǒng)非常友好。第三它記錄了一個真實的歷史狀態(tài)你在閱讀代碼時能看到很多后來被替代掉的設(shè)計思路這種“考古”視角對新項目設(shè)計很有啟發(fā)。當(dāng)然如果你追求的是能運(yùn)行更多 Windows 軟件那肯定要去找更新版本。0.3.15 時代能穩(wěn)定運(yùn)行的基本局限在記事本、命令行、部分管理工具這類輕量程序復(fù)雜軟件還是會頻繁崩潰。所以選它的人目標(biāo)不是日用而是學(xué)習(xí)。2. 源碼包內(nèi)部結(jié)構(gòu)拆解那些目錄到底管什么2.1 頂層目錄地圖從哪一層看起把 zip 解壓之后首先面對的就是根目錄下十幾個一級目錄。我第一次打開的時候也感覺有點暈但理清之后會發(fā)現(xiàn)層次其實很清楚。大致會看到這些關(guān)鍵目錄ntoskrnl放的是內(nèi)核主體win32ss是 Win32 子系統(tǒng)drivers是各種驅(qū)動hal是硬件抽象層boot是啟動引導(dǎo)相關(guān)sdk是開發(fā)工具鏈和頭文件庫subsystems是環(huán)境子系統(tǒng)modules存放一些可選的應(yīng)用組件tools則是構(gòu)建輔助工具。另外還有reactos這個名字的目錄在部分版本里會出現(xiàn)通常存放應(yīng)用層程序比如桌面、控制面板、命令解釋器之類的用戶態(tài)組件??辞宄@些目錄之后讀代碼就有路線圖了。我習(xí)慣把整個系統(tǒng)拆成三層理解最底層是硬件抽象和內(nèi)核中間是系統(tǒng)服務(wù)和驅(qū)動最上面是 Win32 子系統(tǒng)和用戶態(tài)程序。源碼目錄正好也按這個邏輯排布所以你完全可以從下往上讀也可以反過來從上往下追調(diào)用鏈。有一點要提醒解壓時留意路徑長度問題。源碼里目錄層級很深Windows 系統(tǒng)默認(rèn)的 MAX_PATH 限制在某些機(jī)器上會觸發(fā)解壓報錯。我建議直接用 7-Zip 解壓并放到一個短路徑下比如D:\ros不要嵌套在超長目錄里否則后續(xù)編譯工具鏈容易莫名其妙出錯。2.2 內(nèi)核、Win32 子系統(tǒng)和 HAL 各管哪攤事我先說ntoskrnl這是整個系統(tǒng)的心臟負(fù)責(zé)內(nèi)存管理、進(jìn)程線程調(diào)度、對象管理、IRP 分發(fā)這些操作系統(tǒng)最核心的機(jī)制。0.3.15 的內(nèi)核已經(jīng)實現(xiàn)了不少 Windows 內(nèi)核語義比如進(jìn)程環(huán)境塊、線程環(huán)境塊、對象命名空間、系統(tǒng)服務(wù)分發(fā)表。你如果之前在 Windows 內(nèi)核編程的書上看到某些原語想看看開源實現(xiàn)長什么樣翻這個目錄就對了。再看win32ss這是 ReactOS 相對獨特的模塊也是它跟普通類 Unix 系統(tǒng)最不一樣的地方。這個子系統(tǒng)負(fù)責(zé)實現(xiàn) Win32 API 的用戶態(tài)和內(nèi)核態(tài)部分包含win32k這類內(nèi)核態(tài)圖形驅(qū)動以及 user32、gdi32 等用戶態(tài)庫的入口邏輯。簡單理解你在 Windows 上調(diào)用 CreateWindow、DrawText 這些 APIReactOS 里最終的實現(xiàn)源頭就在這個目錄。圖形輸出、窗口管理、消息循環(huán)這些機(jī)制都整合在這一塊代碼風(fēng)格和內(nèi)核部分有明顯差異讀起來需要點耐心。hal相對小眾一些但地位不低。它把硬件差異隔離在底層讓內(nèi)核上層的代碼不需要關(guān)心具體的主板芯片組和中斷控制器型號。0.3.15 里的 HAL 已經(jīng)支持多種硬件平臺配置編譯時可以針對不同機(jī)器形態(tài)選擇不同 HAL。對做嵌入式或硬件相關(guān)開發(fā)的朋友來說這個目錄是一個直觀的教材能學(xué)會怎么在操作系統(tǒng)層面抽象硬件能力。還有一個容易被忽略的sdk目錄里面是包括 NDK、DDK、WDK 風(fēng)格的頭文件和導(dǎo)入庫。我強(qiáng)烈建議初學(xué)者先花時間翻這個目錄下的頭文件它能讓你快速知道系統(tǒng)對外暴露了哪些接口相當(dāng)于整個系統(tǒng)的“API 地圖”。我在讀源碼時經(jīng)常遇到一個函數(shù)搞不清類型定義直接一查頭文件就通透了。3. 從源碼到可引導(dǎo)鏡像的完整構(gòu)建流程3.1 構(gòu)建環(huán)境準(zhǔn)備裝 RosBE 而不是自己拼工具鏈在 0.3.15 那個年代官方推薦的構(gòu)建環(huán)境是 RosBE全稱 ReactOS Build Environment。這個東西非常省心它把交叉編譯器、鏈接器、make、nasm 匯編器、必要的基礎(chǔ)庫都打包好了你只要按對應(yīng)平臺裝好再進(jìn)入它的命令行環(huán)境就能開始編譯。我當(dāng)時的做法是找一臺 Linux 虛擬機(jī)在純凈系統(tǒng)上裝 RosBE for Unix。如果你只有 Windows 機(jī)器也可以裝 RosBE for Windows原理一樣。關(guān)鍵點是不要圖省事直接用系統(tǒng)自帶的 gcc 去編因為 ReactOS 的代碼對編譯器版本和特定頭文件有強(qiáng)依賴RosBE 里的工具鏈?zhǔn)枪俜津炞C過的組合換成其他版本容易觸發(fā)各種莫名其妙的兼容問題。啟動 RosBE 環(huán)境后先建議跑一遍make -v和nasm -v確認(rèn)匯編器和 make 都可用。這個習(xí)慣很重要我后來很多次編譯失敗最后都發(fā)現(xiàn)是環(huán)境沒進(jìn)對壓根沒調(diào)用到 RosBE 里的工具。3.2 配置和編譯記住關(guān)鍵命令確認(rèn)環(huán)境沒問題之后進(jìn)入源碼根目錄先跑一下 configure 生成構(gòu)建系統(tǒng)需要的配置cd reactos-0.3.15 ./configure這個步驟會檢測宿主機(jī)環(huán)境、生成 Makefile并把構(gòu)建相關(guān)的配置參數(shù)保存下來。默認(rèn)配置適合大部分場景官方文檔一般還建議生成 bootcd 鏡像所以后面直接用默認(rèn)就行。接下來就是最核心的命令make bootcd這里要解釋一下bootcd是構(gòu)建目標(biāo)的名字意思是“生成一個可啟動 CD 鏡像”。整個流程會先編內(nèi)核再編各個子系統(tǒng)、驅(qū)動和應(yīng)用程序最后打包成bootcd.iso。我在老機(jī)器上實測全量編譯大概需要兩三個小時如果只改了局部代碼可以用make bootcd配合make -j參數(shù)并行加速。但要注意0.3.15 對并行編譯的兼容性一般我建議先老老實實單核跑一遍全流程確認(rèn)無誤后再用 2 線程或 4 線程增量編譯否則很容易在鏈接階段出現(xiàn)內(nèi)存不足或者詭異報錯。編譯過程中屏幕上會持續(xù)滾動大量輸出看到error:開頭的行就要停下來看。比較常見的有兩類一類是缺頭文件通常是你沒有用 RosBE 環(huán)境導(dǎo)致的另一類是內(nèi)存不足我在后面問題清單里會細(xì)說。3.3 用 QEMU 把系統(tǒng)跑起來鏡像生成之后接下來就是最激動人心的時刻把它跑起來。我強(qiáng)烈推薦用 QEMU它輕量、跨平臺、支持各種硬件模擬調(diào)試也比較方便。在 RosBE 環(huán)境外另開一個終端執(zhí)行qemu-system-i386 -m 512 -cdrom bootcd.iso -boot d這條命令的意思是分配 512M 內(nèi)存用 bootcd.iso 作為光驅(qū)并且從光驅(qū)引導(dǎo)。首次啟動你會看到 ReactOS 的引導(dǎo)畫面然后進(jìn)入圖形安裝界面也可以選擇從 LiveCD 方式直接進(jìn)入桌面。如果你的虛擬機(jī)上鼠標(biāo)鍵盤沒反應(yīng)別急著懷疑系統(tǒng)壞了。0.3.15 對 USB 設(shè)備的支持還不夠完善優(yōu)先模擬 PS/2 鍵鼠或者給 QEMU 加-usb -usbdevice mouse -usbdevice keyboard這類參數(shù)多數(shù)情況能解決。我最初測試時因為主機(jī)沒有 PS/2 接口又沒傳設(shè)備參數(shù)進(jìn)入桌面后鼠標(biāo)完全卡死折騰了好一陣才排查清楚這個坑值得提前記住。跑起來之后你可以試試打開記事本、命令行也可以在系統(tǒng)里看一下系統(tǒng)屬性里顯示的版本號。0.3.15 的桌面已經(jīng)很有早期 Windows 的影子了雖然遠(yuǎn)談不上流暢但一個從零實現(xiàn)的系統(tǒng)能走到這一步還是挺讓人興奮的。4. 編譯與運(yùn)行中的常見坑和排查技巧4.1 編譯期問題速查表為了讓你少走彎路我把實際操作中容易踩的編譯期問題整理成了表格現(xiàn)象常見原因處理方法找不到頭文件或工具鏈命令沒有進(jìn)入 RosBE 環(huán)境重新打開 RosBE 命令行確認(rèn) make/nasm 路徑鏈接時內(nèi)存耗盡宿主機(jī)內(nèi)存不足關(guān)閉多余程序增加 swap/虛擬內(nèi)存降低-j并行數(shù)zip 解壓報路徑錯誤解壓路徑過長或權(quán)限不足用 7-Zip 解壓到短路徑目錄下編譯到一半報語法錯誤源碼被非正常中斷過建議刪除源碼目錄重新解壓不要只做增量修復(fù)生成的 iso 無法引導(dǎo)構(gòu)建目標(biāo)不對或鏡像是舊的清理干凈后重新執(zhí)行make bootcd這里特別說下內(nèi)存不足的問題。0.3.15 的構(gòu)建過程雖然整體不夸張但鏈接器在鏈接內(nèi)核和幾個大模塊時會一次性加載大量中間文件。我試過在 2G 內(nèi)存的機(jī)器上用并行編譯結(jié)果好幾個模塊在鏈接階段直接報 out of memory。后來我把并行數(shù)調(diào)回 1并且打開交換分區(qū)問題就消失了。如果你看到類似virtual_alloc0: fatal error: out of memory這樣的輸出先別慌優(yōu)先懷疑的就是內(nèi)存資源不夠而不是代碼錯誤。4.2 啟動期問題黑屏、藍(lán)屏和慢啟動編譯成功只是第一步跑到系統(tǒng)里才是真正測功底。0.3.15 時代啟動階段的問題主要集中在黑屏和驅(qū)動加載失敗。如果是純黑屏連引導(dǎo)畫布都沒出現(xiàn)多半是 QEMU 的顯卡模擬和系統(tǒng)自帶的顯卡驅(qū)動不兼容。這時可以試試切換顯示方式比如加-vga std參數(shù)或者改用 VirtualBox 內(nèi)置的顯卡模擬往往會好很多。我曾經(jīng)在某個版本的 QEMU 上一直卡在黑屏換參數(shù)后一次就過了。如果能看到引導(dǎo)畫面但進(jìn)入系統(tǒng)后崩潰那就是驅(qū)動層面的問題。0.3.15 對老式 IDE 硬盤、Realtek 8139 網(wǎng)卡這類設(shè)備支持相對好一些對新型硬件則基本無力。我建議在虛擬機(jī)里把硬件模型盡量調(diào)舊比如硬盤接口用 IDE 而不是 SATA網(wǎng)卡模型選 rtl8139這樣可以顯著提高成功率。還有一個容易被忽略的點是啟動速度。在 0.3.15 上從引導(dǎo)到進(jìn)入桌面花兩三分鐘很正常如果配置了調(diào)試串口輸出還會更慢。不要因為界面長時間沒反應(yīng)就以為死機(jī)了可以把系統(tǒng)日志輸出到文件或者加-serial stdio觀察啟動過程能判斷系統(tǒng)是卡在哪個驅(qū)動上。4.3 源碼閱讀建議從哪里讀起效率最高拿到源碼之后不建議一頭扎進(jìn)ntoskrnl的內(nèi)核細(xì)節(jié)里那很容易被底層的鏈表、內(nèi)存描述符和各種旋鎖勸退。我個人的閱讀順序是先從sdk/include里的頭文件入手理解系統(tǒng)暴露出來的核心數(shù)據(jù)結(jié)構(gòu)和 API然后去win32ss看窗口和消息機(jī)制這部分直觀能建立一個“操作系統(tǒng)到底怎么跟程序打交道”的整體印象最后再回到ntoskrnl讀內(nèi)存管理和進(jìn)程調(diào)度這時候你會發(fā)現(xiàn)自己已經(jīng)有了上下文讀起來順暢很多。如果時間有限可以考慮只挑一個子系統(tǒng)精讀。比如只跟蹤C(jī)reateProcess這個 API 的完整調(diào)用鏈從用戶態(tài)的 kernel32 出發(fā)一路看到ntoskrnl里的進(jìn)程創(chuàng)建函數(shù)再看到win32ss里如何初始化進(jìn)程的窗口環(huán)境。這一條線走通你對一個操作系統(tǒng)的進(jìn)程管理、對象管理、環(huán)境子系統(tǒng)分工就能建立起很扎實的認(rèn)知框架。這一路的體會是ReactOS 這種項目最適合的用法就是“按圖索驥”。系統(tǒng)本身雖然做不到完全兼容 Windows但它把 Windows 的設(shè)計思路以一整套可編譯、可調(diào)試的開源代碼呈現(xiàn)出來了。隨便改一行窗口繪制代碼重新編譯就能在虛擬機(jī)里看到變化這種正反饋是讀任何紙面資料都給不了的。最后再分享一個小技巧調(diào)試 ReactOS 源碼時在 QEMU 里開啟調(diào)試輸出會讓效率提升不少。啟動參數(shù)里帶上-serial file:serial.log再把系統(tǒng)的調(diào)試信息配置打開代碼里所有 DPRINT 輸出都會落到文件中。我在追蹤某個窗口消息處理問題時就是靠這個日志直接定位到 win32k 里一條分支寫錯了參數(shù)省去了大量猜測的時間。3.0.15 雖然老但整套研究路徑放在今天依然值得復(fù)制。本文還有配套的精品資源點擊獲取