上一篇

小源:從零開始寫 OS (0) —— 隨便聊聊?

zhuanlan.zhihu.com圖標

概要

由於我們的目標是編寫一個操作系統,所以首先我們需要創建一個獨立於操作系統的可執行程序,又稱 獨立式可執行程序(freestanding executable)裸機程序(bare-metal executable) 。這意味著所有依賴於操作系統的庫我們都不能使用。比如 std 中的大部分內容(io, thread, file system, etc.)都需要操作系統的支持,所以這部分內容我們不能使用。

但是,不依賴與操作系統的 rust 的語言特性 我們還是可以繼續使用的,比如:迭代器、模式匹配、字元串格式化、所有權系統等。這使得 rust 依舊可以作為一個功能強大的高級語言,幫助我們編寫操作系統。

本章我們將介紹:

  1. 安裝 rust(nightly 版本)
  2. 創建可執行的 rust 項目。
  3. 將創建的 rust 項目修改為 freestanding rust binary ,這包括 禁用 std 庫 並解決由此產生的一系列問題。

安裝 nightly rust

rust 包含:stable、beta、nightly 三個版本。默認情況下我們安裝的是 stable 。由於在編寫操作系統時需要使用 rust 的一些不穩定的實驗功能,所以請根據 rust 官方教程安裝 rust nightly 。

安裝成功後使用 rustc --version 可以查看當前 rust 的版本,版本號的最後應該為 -nightly

如果未能成功切換 rust 版本,請查看 how to switch rust toolchain

創建 rust binary 項目

首先利用 cargo 創建一個新的 rust binary 項目:

cargo new xy_os --bin --edition 2018

xy_os 是項目的名稱,--bin 表示我需要創建一個 binary 項目,--edition 2018 指定使用 2018 版本的 rust 。

添加 no_std 屬性

因為我們的目標是編寫一個操作系統,所以我們不能使用任何依賴於操作系統的庫。項目默認是鏈接標準庫的,我們需要顯示的將其禁用:

// main.rs

#![no_std]

fn main() {
println!("Hello, world!");
}

如果此時執行 cargo build 構建項目,會產生以下兩個錯誤:

error: cannot find macro `println!` in this scope
--> src/main.rs:6:5
|
6 | println!("Hello, world!");
| ^^^^^^^

error: `#[panic_handler]` function required, but not found

現在我們來依次解決這兩個問題。

error: cannot find macro println! in this scope

println 宏屬於標準庫,所以禁用標準庫後自然不能再使用 println 。由於我們當前目標只是寫一個可執行的文件,所以將其刪除即可:

// main.rs

#![no_std]

fn main() {}

error: #[panic_handler] function required, but not found

在程序發生 panic 時需要調用相應函數。標準庫有對應函數,但是由於我們使用了 no_std 屬性,所以接下來我們需要自己實現一個函數:

// main.rs

use core::panic::PanicInfo;

// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}

由於程序 panic 後就應該結束,所以用 -> ! 表示該函數不會返回。由於目前的 OS 功能還很弱小,所以只能無限循環。

解決了上述兩個 error 後,再次執行 cargo build ,結果出現了新的 error:

error: language item required, but not found: `eh_personality`

error: language item required, but not found: eh_personality

eh_personality 語義項(language item)用於標記函數:該函數在 堆棧展開(stack unwinding) 時被調用。當程序發生 panic 時,rust 會調用 堆棧展開 析構堆棧中的所有生存變數,達到釋放內存的目的。但是這是一個複雜的過程,而且依賴於一些其他的庫文件。所以我們只是簡單的將其禁用:

# Cargo.toml

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

將 dev (use for cargo build) 和 release (use for cargo build --release) 的 panic 的處理策略設為 abort ,這樣我們便不再依賴 eh_personality 語義項。

再次運行 cargo build ,不出所料,又出現了新的 error :

error: requires `start` lang_item

error: requires start lang_item

對於大多數語言,他們都使用了 運行時系統(runtime system) ,這導致 main 並不是他們執行的第一個函數。以 rust 語言為例:一個典型的 rust 程序會先鏈接標準庫,然後運行 C runtime library 中的 crt0(C runtime zero) 設置 C 程序運行所需要的環境(比如:創建堆棧,設置寄存器參數等)。然後 C runtime 會調用 rust runtime 的 入口點(entry point) 。rust runtime 結束之後才會調用 main 。由於我們的程序無法訪問 rust runtime 和 crt0 ,所以需要重寫覆蓋 crt0 入口點:

#![no_std] // dont link the Rust standard library
#![no_main] // disable all Rust-level entry points

use core::panic::PanicInfo;

// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}

#[no_mangle]
pub extern "C" fn main() -> ! {
loop {}
}

適用於 MacOS ,在其他系統請 點擊這裡

這裡 pub extern "C" fn main 就是我們需要的 start 。 #[no_mangle] 屬性用於防止改名稱被混淆。由於 start 只能由操作系統或引導載入程序直接調用,不會被其他函數調用,所以不能夠返回。如果需要離開該函數,應該使用 exit 系統調用。但是由於我們的操作系統還沒有實現 exit 系統調用,所以暫時使用無限循環防止函數返回。由於 start 函數無法返回或退出,自然也就不會調用 main 。所以將 main 函數刪除,並且增加屬性標籤 #![no_main]

再次執行 cargo build ,很不幸,又出現了 error:

linking with `cc` failed: exit code: 1

但幸運的是,這是我們本章所需要處理的最後一個 error!

linking with cc failed: exit code: 1

在鏈接 C runtime 時,會需要一些 C 標準庫(libc)的內容。由於 #![no_std] 禁用了標準庫,所以我們需要禁用常規的 C 啟動常式:

> cargo rustc -- -C link-arg=-lSystem
Compiling xy_os v0.1.0 (/Users/.../xy_os)
Finished dev [unoptimized + debuginfo] target(s) in 0.25s

適用於 MacOS ,在其他系統請 點擊這裡

歷經千辛萬苦,我們終於成功構建了一個 Freestanding Rust Binary !!!

此處應有掌聲

預告

下一章,我們將在 Freestanding Rust Binary 的基礎上,創建 最小內核 ,將其和 bootloader 鏈接成為可以被 qemu 載入的 bootimage 。並且將能夠在屏幕上列印 Hello World !

下一篇

小源:從零開始寫 OS (2) —— 最小化內核?

zhuanlan.zhihu.com圖標
推薦閱讀:

相关文章