使用Rust從零寫操作系統 (1) —— 獨立式可執行程序

本系列博客系轉載,出處: 知乎專欄:從零開始寫 OS

所有代碼都在:https://github.com/LearningOS/rcore_step_by_step

本小節代碼對應 commit :bc429d04d479da60fc4bd05824a124d18c5e33ac

概要

由於我們的目標是編寫一個操作系統,所以首先我們需要創建一個獨立於操作系統的可執行程序,又稱 獨立式可執行程序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函數 。由於我們當前目標只是寫一個可執行的文件,所以將其刪除即可:

// in main.rs

#![no_std]

fn main() {}

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

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

// in 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 runtimecrt0 ,所以需要重寫覆蓋 crt0 入口點:


#![no_std] // don't 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] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
    // this function is the entry point, since the linker looks for a function
    // named `_start` by default
    loop {}
}

適用於 Linux ,在其他系統請 點擊這裏 。暫時無法編譯也沒關係,因爲下一小節會重寫 _start 函數。

這裏 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=-nostartfiles
Compiling xy_os v0.1.0 (/mnt/xy_os)
Finished dev [unoptimized + debuginfo] target(s) in 0.21s

適用於 Linux ,在其他系統請 點擊這裏

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

此處應有掌聲

預告

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章