在現代作業系統的底層,核心扮演著至關重要的角色,負責管理系統資源和提供各種服務。本文將引領讀者使用 Rust 語言,開發一個名為 FledgeOS 的基礎作業系統核心。首先,我們會設定開發環境,包括安裝必要的工具鏈和函式庫,接著逐步建構 FledgeOS-0,並在 QEMU 虛擬機器上進行測試。過程中將會深入探討核心的啟動流程、記憶體管理以及與硬體的互動方式,最終實作一個可在螢幕上顯示簡單圖形的核心。
這個 FledgeOS-0 專案相當精簡,核心功能僅在 src/main.rs
中實作。它利用 bootloader
crate 提供的進入點,並透過 x86_64
crate 與硬體互動。核心初始化後,會在螢幕左上角繪製一個淺藍色方塊,藉此驗證核心已成功載入並執行。此專案的程式碼結構清晰,方便後續功能擴充。其中,Cargo.toml
檔案管理專案依賴,而 fledge.json
和 .cargo/config.toml
檔案則負責設定核心編譯目標和建置環境。透過這些設定,我們可以將 Rust 程式碼編譯成適合在裸機上執行的核心映像。
處理任務結果
首先,我們需要定義如何處理每個任務的結果。這個過程使用 match
來根據 task
的狀態進行不同的處理:
- 如果任務傳回錯誤 (
Err(_)
),我們直接跳出迴圈 (break
)。 - 如果任務完成 (
Ok(Work::Finished)
),同樣跳出迴圈。 - 如果任務傳回了一個需要處理的 byte (
Ok(Work::Task((i, byte))
)), 我們解析這個 byte 並將結果與索引i
一起儲存為(i, parse_byte(byte))
。
let result = match task {
Err(_) => break,
Ok(Work::Finished) => break,
Ok(Work::Task((i, byte))) => (i, parse_byte(byte)),
};
然後,我們將這個結果傳送給 results
通道,確保工作執行緒可以接收到這個結果:
results.send(result).unwrap();
更新操作列表
在主執行緒中,我們建立了一個 ops
向量,用於儲存最終的操作列表。這個列表初始時包含 n_bytes
個 Noop(0)
元素:
let mut ops = vec![Noop(0); n_bytes];
接下來,我們迴圈 n_bytes
次,從 results_rx
通道接收結果,並根據接收到的索引 i
和操作 op
更新 ops
列表:
for _ in 0..n_bytes {
let (i, op) = results_rx.recv().unwrap();
ops[i] = op;
}
最終,ops
向量包含了所有從工作執行緒接收到的操作,按照正確的索引排列。
圖表翻譯:
flowchart TD A[開始] --> B[接收任務結果] B --> C[處理結果] C --> D[更新操作列表] D --> E[完成]
內容解密:
上述過程中,我們使用 match
來處理任務的不同傳回狀態,確保程式的穩健性和可靠性。透過向 results
通道傳送結果,我們實作了主執行緒和工作執行緒之間的同步通訊。最後,透過更新 ops
列表,我們得到了最終的操作序列,準備好進行後續的處理。
訊息通道的設計與實作
在設計訊息通道時,我們需要考慮到多個層面,包括訊息的結構、通道的建立以及 worker 執行緒的管理。以下是關於訊息通道設計的一些關鍵點:
訊息結構的定義
首先,我們需要定義訊息的結構。這包括了訊息的型別、內容以及其他相關的元資料。例如,當我們處理 byte 時,我們可能需要知道這個 byte 的位置,以便於後續的處理。因此,我們可以定義一個 tuple 型別,其中包含了 byte 的內容和其位置。
// 定義訊息結構
struct Message {
content: Vec<u8>,
position: usize,
}
通道的建立
接下來,我們需要建立通道以便於不同執行緒之間的溝通。Rust 的標準函式庫提供了 std::sync::mpsc
模組,允許我們建立多生產者、多消費者的通道。
// 建立一個通道
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
worker 執行緒的管理
當我們需要關閉 worker 緦程時,我們可以透過傳送一個特殊的訊息來通知 worker 執行緒。這個訊息可以是一個標誌,指示 worker 執行緒應該離開。
// 定義關閉訊息
enum ControlMessage {
Shutdown,
}
// 傳送關閉訊息
tx.send(ControlMessage::Shutdown).unwrap();
功能的抽取
為了簡化邏輯,我們可以抽取出 worker 執行緒需要執行的功能。這樣可以使得程式碼更加模組化和易於維護。
// 抽取出 worker 執行緒需要執行的功能
fn worker_task(rx: mpsc::Receiver<Message>) {
// 處理訊息的邏輯
}
通道的使用
最後,我們可以使用建立好的通道來傳送和接收訊息。
// 傳送訊息
tx.send(Message {
content: vec![1, 2, 3],
position: 0,
}).unwrap();
// 接收訊息
let msg = rx.recv().unwrap();
內容解密:
以上的程式碼片段展示瞭如何建立一個通道並使用它來傳送和接收訊息。其中,Message
結構體代表了我們要傳送的訊息,包含了 byte 的內容和其位置。ControlMessage
列舉代表了控制訊息,目前只有 Shutdown
一種型別,用於通知 worker 執行緒離開。worker_task
函式抽取出了 worker 執行緒需要執行的功能,使得程式碼更加模組化。透過使用 mpsc
通道,我們可以實作不同執行緒之間的溝通。
圖表翻譯:
以下是使用 Mermaid 語法繪製的流程圖,展示了訊息通道的工作流程:
flowchart TD A[主執行緒] -->|傳送訊息|> B[通道] B -->|接收訊息|> C[worker 執行緒] C -->|處理訊息|> D[結果] D -->|傳回結果|> A A -->|傳送關閉訊息|> C C -->|離開|> E[結束]
這個流程圖展示了主執行緒如何透過通道傳送訊息給 worker 執行緒,worker 執行緒如何接收和處理訊息,並傳回結果給主執行緒。當主執行緒傳送關閉訊息時,worker 執行緒會離開。
多執行緒任務管理機制
在多執行緒環境中,任務管理是一個非常重要的議題。為了確保多個執行緒之間的溝通和協調,需要建立一個高效的任務管理機制。這個機制需要能夠建立一個通道,讓解碼後的指令可以傳回給主執行緒,並且能夠填充任務佇列以供執行緒執行。
任務佇列管理
任務佇列是多執行緒任務管理的核心。它負責儲存和管理所有待執行的任務,並且需要能夠跟蹤任務的數量和狀態。當執行緒完成了一個任務後,需要將結果傳回給主執行緒,並且更新任務佇列的狀態。
通道分享
在多執行緒環境中,通道可以被複製並分享給不同的執行緒。這樣可以讓不同的執行緒之間進行通訊和協調。當一個執行緒完成了一個任務後,可以透過通道將結果傳回給主執行緒。
結果傳回機制
由於結果可以在任意順序傳回,需要初始化一個完整的 Vec<Command>
來儲存結果。這個向量可以被玄貓覆寫,以確保結果的正確性和完整性。使用向量而不是陣列是因為玄貓使用的是向量,並且不想對整個程式進行重構以適應新的實作。
執行緒終止訊號
當所有任務完成後,需要向每個執行緒傳送一個訊號,以通知它們可以終止了。這樣可以確保所有執行緒都能夠正常終止,並且不會導致資源洩漏或其他問題。
內容解密:
上述機制的實作需要使用到多個技術,包括通道、任務佇列和結果傳回機制。下面的程式碼示範瞭如何使用 Rust 實作這個機制:
use std::sync::mpsc;
use std::thread;
// 建立一個通道來傳回結果
let (tx, rx) = mpsc::channel();
// 建立一個任務佇列
let mut tasks = Vec::new();
// 向任務佇列中新增任務
tasks.push(Command::new());
// 啟動多個執行緒來執行任務
for _ in 0..10 {
let tx_clone = tx.clone();
thread::spawn(move || {
// 執行任務並傳回結果
let result = execute_task();
tx_clone.send(result).unwrap();
});
}
// 等待所有任務完成
for _ in 0..10 {
let result = rx.recv().unwrap();
// 處理結果
}
// 向每個執行緒傳送終止訊號
tx.send(Command::Terminate).unwrap();
圖表翻譯:
下面的 Mermaid 圖表示範了多執行緒任務管理機制的流程:
flowchart TD A[建立通道] --> B[建立任務佇列] B --> C[新增任務] C --> D[啟動執行緒] D --> E[執行任務] E --> F[傳回結果] F --> G[等待所有任務完成] G --> H[向每個執行緒傳送終止訊號]
這個圖表展示了多執行緒任務管理機制的主要步驟,包括建立通道、建立任務佇列、新增任務、啟動執行緒、執行任務、傳回結果、等待所有任務完成和向每個執行緒傳送終止訊號。
10.5 並發性和任務虛擬化
在這一節中,我們將探討並發性模型之間的差異。圖 10.5 顯示了一些權衡。
任務虛擬化的主要優點是隔離。所謂隔離是指任務之間不能相互幹擾。幹擾可以以多種形式出現,例如:損壞記憶體、飽和網路和儲存到磁碟時的擁塞。如果一個執行緒在等待控制檯輸出到螢幕時被阻塞,那麼該執行緒中的所有協程都無法進行。
隔離的任務不能在未經許可的情況下存取彼此的資料。同一程式中的獨立執行緒分享相同的記憶體地址空間,並且所有執行緒都可以存取該空間中的資料。然而,程式被禁止檢查彼此的記憶體。
隔離的任務不能使另一個任務當機。如果一個任務失敗,不應該導致其他系統當機。如果一個程式引起核心恐慌,所有程式都將被關閉。透過隔離,任務即使其他任務不穩定也可以繼續進行。
並發性和任務虛擬化
隔離是一個連續體。完全隔離是不切實際的,因為它意味著輸入和輸出是不可能的。此外,隔離通常是在軟體中實作的。執行額外的軟體意味著承擔額外的執行時開銷。
並發性術語小辭典
這個子領域充滿了術語。以下是對一些重要術語的簡要介紹和我們如何使用它們:
- 程式:程式或應用程式是一個品牌名稱。我們用它來指代一個軟體套件。當我們執行一個程式時,作業系統會建立一個程式。
- 可執行檔:可執行檔是一個可以載入記憶體並執行的檔案。執行可執行檔意味著建立一個程式和一個執行緒,然後將 CPU 的指令指標改為可執行檔的第一條指令。
- 任務:本章使用「任務」這個術語來表示抽象意義。其含義會根據抽象層次的不同而有所不同:
- 當討論程式時,任務是程式中的其中一個執行緒。
- 當指的是一個執行緒時,任務可能是一個函式呼叫。
- 當指的是作業系統時,任務可能是一個正在執行的程式,這個程式可能由多個程式組成。
- 程式:執行程式以程式形式執行。程式具有自己的虛擬地址空間、至少一個執行緒和由作業系統管理的許多書記工作。檔案描述符、環境變數和排程優先順序都是按程式管理的。程式具有虛擬地址空間、可執行程式碼、開啟的系統物件控制程式碼、安全內容、唯一程式識別碼、環境變數、優先順序類別、最小和最大工作集大小以及至少有一個執行緒。
- 執行緒:執行緒隱喻用於暗示多個執行緒可以共同工作。
- 執行緒:執行緒是一系列 CPU 指令,出現於序列中。多個執行緒可以同時執行,但序列中的指令是為了按順序執行而設計的。
- 協程:也稱為纖維、綠色執行緒和輕量級執行緒,協程表示線上程內切換的任務。切換任務成為程式本身的責任,而不是作業系統的責任。有兩個重要的理論概念需要區分:
- 並發性,即任何抽象層次的多個任務同時執行
- 平行性,即多個執行緒在多個 CPU 上同時執行
graph LR A[並發性] --> B[任務] B --> C[程式] C --> D[執行緒] D --> E[協程]
圖表翻譯:
圖 10.5 顯示了與不同形式的任務隔離相關的權衡。在一般情況下,增加隔離級別會增加開銷。
本文介紹了並發性模型之間的差異,並解釋了隔離的概念。我們還介紹了一些重要的術語,包括「程式」、「可執行檔」、「任務」、「程式」、「執行緒」、「執行緒」和「協程」。最後,我們使用 Mermaid 圖表展示了並發性和任務虛擬化之間的關係。
內容解密:
在這一節中,我們探討了並發性模型之間的差異,並解釋了隔離的概念。隔離是指任務之間不能相互幹擾,包括損壞記憶體、飽和網路和儲存到磁碟時的擁塞。我們還介紹了一些重要的術語,包括「程式」、「可執行檔」、「任務」、「程式」、「執行緒」、「執行緒」和「協程」。最後,我們使用 Mermaid 圖表展示了並發性和任務虛擬化之間的關係。
graph LR A[程式] --> B[可執行檔] B --> C[任務] C --> D[程式] D --> E[執行緒] E --> F[協程]
圖表翻譯:
圖表顯示了程式、可執行檔、任務、程式、執行緒和協程之間的關係。程式是一個品牌名稱,我們用它來指代一個軟體套件。可執行檔是一個可以載入記憶體並執行的檔案。任務是指任何抽象層次的多個任務同時執行。程式具有自己的虛擬地址空間、至少一個執行緒和由作業系統管理的許多書記工作。執行緒是一系列 CPU 指令,出現於序列中。協程表示線上程內切換的任務。
sequenceDiagram participant 程式 as "程式" participant 可執行檔 as "可執行檔" participant 任務 as "任務" participant 程式 as "程式" participant 執行緒 as "執行緒" participant 協程 as "協程" 程式->>可執行檔: 載入 可執行檔->>任務: 建立 任務->>程式: 建立 程式->>執行緒: 建立 執行緒->>協程: 切換
圖表翻譯:
圖表顯示了程式、可執行檔、任務、程式、執行緒和協程之間的關係。程式載入可執行檔,可執行檔建立任務,任務建立程式,程式建立執行緒,執行緒切換協程。
10.5 執行緒、程式和容器
10.5.1 執行緒
執行緒是作業系統(OS)理解的最低層級隔離單元。作業系統可以排程執行緒。較小的並發形式對作業系統是不可見的。你可能曾經遇到過如協程(coroutines)、纖維(fibers)和綠執行緒(green threads)等術語。
在這種情況下,任務之間的切換由玄貓管理。作業系統不知道程式正在處理多個任務。對於執行緒和其他形式的並發,需要進行內容切換。
10.5.2 什麼是內容切換?
在相同虛擬化層級之間切換任務稱為內容切換。要切換執行緒,需要清除 CPU 註冊器,可能需要重新整理 CPU 快取,並重置作業系統中的變數。隨著隔離度的增加,內容切換的成本也會增加。
CPU 只能序列執行指令。要執行多個任務,電腦需要能夠儲存當前狀態,切換到新任務,並在新任務的儲存點還原執行。CPU 會不斷切換任務,因為它有足夠的時間可供使用。程式通常需要存取記憶體、磁碟或網路中的資料。由於等待資料非常慢,因此通常有足夠的時間在等待資料到達的同時做其他事情。
10.5.3 程式
執行緒存在於程式中。程式的特徵是其記憶體獨立於其他程式。作業系統與 CPU 共同保護程式的記憶體不受其他程式的影響。
要在程式之間分享資料,Rust 通道和由 Mutex
保護的資料不足以滿足需求。你需要作業系統提供的支援。為此,重用網路通訊端是一種常見的做法。大多數作業系統提供了專門的程式間通訊(IPC)形式,這些形式更快,但也更少可移植。
10.5.4 WebAssembly
WebAssembly(WASM)是一種嘗試在程式邊界內隔離任務的技術。無法讓 WASM 模組中的任務存取其他任務可用的記憶體。WASM 起源於網頁瀏覽器,將所有程式碼視為潛在的惡意程式碼。如果你使用第三方依賴項,很可能你沒有驗證過所有程式碼的行為。
從某種意義上說,WASM 模組被授予了程式地址空間內的地址空間。WASM 地址空間被稱為 線性記憶體。執行時環境解釋任何對 線性記憶體 中資料的請求,並向實際虛擬記憶體發出自己的請求。WASM 模組中的程式碼不知道程式可存取的記憶體地址。
10.5.5 容器
容器是程式的擴充套件,透過玄貓提供了進一步的隔離。程式分享相同的檔案系統,而容器具有為其建立的檔案系統。同樣,對於其他資源,如網路,情況也是如此。與地址空間不同,這些其他資源的保護術語被稱為名稱空間。
10.5.6 為什麼需要使用作業系統?
可以將應用程式作為自己的作業系統執行。第 11 章提供了一個實作。對於不需要作業系統支援的應用程式,一般術語是將其描述為獨立應用程式,即不需要作業系統支援的應用程式。獨立二進位制檔案被玄貓使用。
使用獨立二進位制檔案可能涉及顯著限制。沒有作業系統,應用程式不再具有虛擬記憶體或多執行緒。所有這些問題都變成了應用程式自己的問題。為了達到中間立場,可以編譯unikernel。unikernel是一個最小化的作業系統,與單個應用程式配對。編譯過程刪除作業系統中未被玄貓使用的所有東西。
設定作業系統核心開發環境
為了開發一個作業系統核心,需要建立一個適合的開發環境。這個過程相當複雜,因為我們需要編譯核心程式碼,並將其轉換為可執行檔。以下是設定開發環境的步驟:
安裝必要工具
首先,需要安裝幾個工具來幫助我們完成這個過程。這些工具包括:
- QEMU:一種虛擬化技術,可以在任何機器上執行任何作業系統。
- bootimage crate:一個 Rust 套件,負責建立可啟動的映像檔。
- cargo-binutils:一個 Cargo 套件,提供了一系列工具來操作可執行檔。
安裝 bootimage 和相關工具
要安裝 bootimage 和相關工具,可以使用以下命令:
$ cargo install cargo-binutils
$ cargo install bootimage
這些命令會安裝必要的工具,包括 cargo-binutils
和 bootimage
。
安裝 Rust nightly 工具鏈
接下來,需要安裝 Rust nightly 工具鏈。這可以使用以下命令完成:
$ rustup toolchain install nightly
$ rustup default nightly
這些命令會安裝 Rust nightly 工具鏈,並將其設為預設工具鏈。
安裝其他必要元件
最後,需要安裝其他必要元件,包括 rust-src
和 llvm-tools-preview
:
$ rustup component add rust-src
$ rustup component add llvm-tools-preview
這些命令會安裝必要的元件,以便我們可以使用 Rust 和 LLVM 工具。
每個工具的角色
每個工具都扮演著重要的角色:
- cargo-binutils:允許 Cargo 直接操作可執行檔。
- bootimage:負責建立可啟動的映像檔。
- Rust nightly 工具鏈:提供了最新的 Rust 功能和錯誤修復。
- rust-src:提供了 Rust 的原始碼。
- llvm-tools-preview:提供了 LLVM 工具的預覽版本。
透過安裝和組態這些工具,我們可以建立一個適合的開發環境,以便開發和測試我們的作業系統核心。
建立 FledgeOS 環境
為了建立 FledgeOS 的開發環境,我們需要安裝幾個必要的工具和套件。首先,我們需要安裝 cargo-binutils
以避免版本不符的問題。接下來,我們需要安裝 bootimage
套件以便建立可直接在硬體上執行的 boot image。
此外,我們需要安裝 Rust 的 nightly 版本,以便使用一些尚未穩定的功能。同時,我們也需要安裝 rust-src
和 llvm-tools-preview
套件,以便編譯 Rust 的原始碼和使用 LLVM 工具。
驗證開發環境
在開始開發之前,讓我們先驗證一下開發環境是否正確安裝。首先,我們可以檢查 QEMU 是否已經安裝並且可以在 PATH 中找到。然後,我們可以檢查 cargo-binutils
是否已經安裝,並且可以使用 rust-strip
等工具。
接下來,我們可以檢查 bootimage
套件是否已經安裝,並且可以使用 cargo bootimage
命令建立 boot image。最後,我們可以檢查 LLVM 工具是否已經安裝,並且可以使用 llvm-*
命令。
建立 FledgeOS
現在,讓我們開始建立 FledgeOS。首先,我們需要克隆 FledgeOS 的原始碼倉函式庫。然後,我們可以切換到 FledgeOS 的目錄並執行 cargo +nightly run
命令以建立和執行 FledgeOS。
FledgeOS 是一個非常簡單的作業系統,目前它只能顯示一個淺藍色的方框在螢幕的左上角。但是,這是我們開始建立自己的作業系統的第一步。
在未來的章節中,我們將繼續開發 FledgeOS,新增更多的功能和特性。同時,我們也將深入探討作業系統的內部實作和原理,包括記憶體管理、程式管理、檔案系統等。
透過這個過程,我們將學習到如何建立一個完整的作業系統,並且瞭解作業系統的各個組成部分如何協同工作。同時,我們也將學習到如何使用 Rust 這個程式語言來建立作業系統,並且瞭解 Rust 的優缺點和適用場景。
建立 FledgeOS 專案
要開始建立 FledgeOS 專案,請按照以下步驟進行:
步驟 1:建立新專案
開啟終端機,執行以下命令:
cargo new fledgeos-0
這將建立一個新的 Cargo 專案,名為 fledgeos-0
。
步驟 2:安裝 cargo-edit
執行以下命令:
cargo install cargo-edit
這將安裝 cargo-edit
工具,用於管理 Cargo 專案的依賴項。
步驟 3:新增依賴項
切換到專案目錄:
cd fledgeos-0
建立一個新的 .cargo
目錄:
mkdir.cargo
新增 bootloader
和 x86_64
依賴項:
cargo add bootloader@0.9
cargo add x86_64@0.13
步驟 4:組態 Cargo.toml
在 Cargo.toml
檔案中新增以下內容:
[package.metadata.bootimage]
build-command = ["build"]
run-command = [
"qemu-system-x86_64",
"-drive",
"format=raw,file={}"
]
這將組態 bootimage
的建構和執行命令。
步驟 5:建立 fledge.json 檔案
建立一個新的 fledge.json
檔案,內容如下:
{
//...
}
您可以從 ch11/ch11-fledgeos-0/fledge.json
下載此檔案的內容。
步驟 6:組態.cargo/config.toml
建立一個新的 .cargo/config.toml
檔案,內容如下:
//...
您可以從 ch11/ch11-fledgeos-0/.cargo/config.toml
下載此檔案的內容。
步驟 7:替換 src/main.rs
替換 src/main.rs
檔案的內容如下:
//...
您可以從 ch11/ch11-fledgeos-0/src/main.rs
下載此檔案的內容。
完成以上步驟後,您就可以開始建立 FledgeOS 專案了。接下來,您可以使用 cargo build
和 cargo run
命令來建構和執行您的專案。
11.2.3 程式碼列表
FledgeOS 專案的程式碼(位於 code/ch11/ch11-fledgeos-*
)使用了一個稍微不同的結構。以下是其佈局的概覽,使用 fledgeos-0
作為代表性範例:
fledgeos-0 ├── Cargo.toml ├── fledge.json ├──.cargo │ └── config.toml └── src └── main.rs
專案中包含兩個額外的檔案:
- 專案根目錄中包含一個
fledge.json
檔案,這是編譯器目標的定義,bootimage
和相關工具將根據此定義進行編譯。 .cargo/config.toml
檔案提供額外的組態引數,告訴cargo
需要為此專案編譯std::core
模組,而不是依賴預先安裝的模組。
以下是專案的 Cargo.toml
檔案清單:
[package]
name = "fledgeos"
version = "0.1.0"
edition = "2018"
[dependencies]
bootloader = "0.9"
x86_64 = "0.13"
[package.metadata.bootimage]
build-command = ["build"]
run-command = [
"qemu-system-x86_64",
"-drive",
"format=raw,file={}",
]
這個 Cargo.toml
檔案稍微有些特殊,包含了一個新的表格 [package.metadata.bootimage]
,其中有一些可能令人困惑的指令。這個表格提供了指示給 bootimage
函式庫,這是一個 bootloader
的依賴項:
bootimage
:從 Rust 核心建立一個可啟動的磁碟映像。build-command
:指示bootimage
使用cargo build
命令而不是cargo xbuild
進行跨編譯。run-command
:替換預設的cargo run
行為,使用 QEMU 而不是直接呼叫可執行檔。
以下是核心目標定義的清單,位於 ch11/ch11-fledgeos-0/fledge.json
:
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"executables": true,
"features": "-mmx,-sse,+soft-float",
"disable-redzone": true,
"panic-strategy": "abort"
}
核心目標定義指定了它是一個 64 位元的作業系統,建置於 x86-64 CPU 上。這個 JSON 規範被玄貓理解。
提示:可以從「自訂目標」章節學習更多關於自訂目標的知識。
以下清單(位於 ch11/ch11-fledgeos-0/.cargo/config.toml
)提供了額外的組態,以便建置 FledgeOS。我們需要指示 cargo
編譯 Rust 語言以符合我們在前一清單中定義的編譯器目標。
[build]
target = "fledge.json"
[unstable]
內容解密:
以上程式碼列表展示了 FledgeOS 專案的結構和組態。Cargo.toml
檔案定義了專案的元資料和依賴項,而 fledge.json
檔案則定義了核心目標的規範。.cargo/config.toml
檔案提供了額外的組態,以便建置 FledgeOS。這些檔案共同合作,讓我們可以建立一個可啟動的磁碟映像並執行於 QEMU 上。
圖表翻譯:
graph LR A[Cargo.toml] -->|定義專案元資料|> B[fledge.json] B -->|定義核心目標規範|> C[.cargo/config.toml] C -->|提供額外組態|> D[FledgeOS] D -->|建立可啟動磁碟映像|> E[QEMU]
以上圖表展示了 FledgeOS 專案的組態流程。從 Cargo.toml
檔案開始,定義專案的元資料和依賴項。然後,fledge.json
檔案定義核心目標的規範。接著,.cargo/config.toml
檔案提供了額外的組態,以便建置 FledgeOS。最後,FledgeOS 建立了一個可啟動的磁碟映像,並執行於 QEMU 上。
核心核心開發:FledgeOS-0 的實作
#![no_std] // 不使用 Rust 標準函式庫
#![no_main] // 不使用 Rust 的 main 函式
use core::panic::PanicInfo; // 引入 PanicInfo 結構體
use bootloader::{BootInfo, entry_point}; // 引入 bootloader crate 中的 BootInfo 和 entry_point
entry_point!(kernel_main); // 定義程式入口點
fn kernel_main(boot_info: &'static BootInfo) -> ! {
// 程式入口點,接收 BootInfo 引數
// 清除螢幕
clear_screen();
// 在螢幕左上角繪製一個淺藍色方塊
draw_block();
// 無限迴圈
loop {}
}
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
// 定義 VGA 緩衝區的記憶體位置和顏色
static mut VGA_BUFFER: *mut u8 = 0xb8000 as *mut u8;
static LIGHT_BLUE: u8 = 0x1f; // 淺藍色
fn clear_screen() {
// 清除螢幕的函式
for i in 0..80 * 25 {
unsafe {
VGA_BUFFER.offset(i as isize * 2).write(b' ');
VGA_BUFFER.offset(i as isize * 2 + 1).write(LIGHT_BLUE);
}
}
}
fn draw_block() {
// 在螢幕左上角繪製一個淺藍色方塊的函式
for i in 0..5*5 {
unsafe {
VGA_BUFFER.offset(i as isize * 2).write(b'█');
VGA_BUFFER.offset(i as isize * 2 + 1).write(LIGHT_BLUE);
}
}
}
內容解密:
這個程式碼實作了一個非常基礎的作業系統核心,名為 FledgeOS-0。它完成了以下幾個關鍵任務:
入口點設定:
entry_point!(kernel_main)
將kernel_main
函式指定為核心的入口點。 bootloader crate 會在系統啟動後呼叫這個函式。BootInfo 接收:
kernel_main
函式接收一個BootInfo
引數,其中包含了 bootloader 傳遞的系統資訊。目前這個資訊還沒有被使用,但在後續開發中會非常重要。螢幕操作: 程式碼中包含了
clear_screen
和draw_block
兩個函式,分別用於清除螢幕和在螢幕左上角繪製一個淺藍色方塊。無限迴圈:
loop {}
構成了核心的主迴圈。由於目前核心沒有其他任務,它會一直停留在這個迴圈中。Panic 處理:
panic_handler
函式定義了在發生 panic 時的行為。目前,它也只是一個無限迴圈,但在後續開發中可以加入更 sophisticated 的錯誤處理機制。VGA 緩衝區操作: 程式碼直接操作 VGA 緩衝區來控制螢幕輸出。
VGA_BUFFER
指向 VGA 緩衝區的起始地址,LIGHT_BLUE
定義了淺藍色的顏色碼。
圖表翻譯:
graph LR A[Bootloader] --> B(kernel_main) B --> C{清除螢幕} C --> D{繪製方塊} D --> E{無限迴圈} B --> F[/Panic/] F --> G{無限迴圈}
結論:
FledgeOS-0 是一個極簡的作業系統核心,它展示瞭如何使用 Rust 和 bootloader crate 建立一個基礎的核心。它目前的功能非常有限,但它提供了一個很好的起點,可以在此基礎上逐步新增更多功能,例如記憶體管理、程式排程和硬體驅動等,最終構建一個功能完善的作業系統。 這個程式碼的重點在於展示核心初始化的基礎流程,以及如何直接操作硬體(VGA 螢幕)。後續的開發將在此基礎上逐步完善,新增更多功能和複雜性。