移除运行时环境依赖

优质
小牛编辑
143浏览
2023-12-01

移除运行时环境依赖

运行时系统

对于大多数语言,他们都使用了运行时系统(Runtime System),这可能导致 main 函数并不是实际执行的第一个函数。

以 Rust 语言为例,一个典型的链接了标准库的 Rust 程序会首先跳转到 C 语言运行时环境中的 crt0(C Runtime Zero)进入 C 语言运行时环境设置 C 程序运行所需要的环境(如创建堆栈或设置寄存器参数等)。

然后 C 语言运行时环境会跳转到 Rust 运行时环境的入口点(Entry Point)进入 Rust 运行时入口函数继续设置 Rust 运行环境,而这个 Rust 的运行时入口点就是被 start 语义项标记的。Rust 运行时环境的入口点结束之后才会调用 main 函数进入主程序。

C 语言运行时环境和 Rust 运行时环境都需要标准库支持,我们的程序无法访问。如果覆盖了 start 语义项,仍然需要 crt0,并不能解决问题。所以需要重写覆盖整个 crt0 入口点:

os/src/main.rs

//! # 全局属性
//! - `#![no_std]`  
//!   禁用标准库
#![no_std]
//!
//! - `#![no_main]`  
//!   不使用 `main` 函数等全部 Rust-level 入口点来作为程序入口
#![no_main]

use core::panic::PanicInfo;

/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

/// 覆盖 crt0 中的 _start 函数
/// 我们暂时将它的实现为一个死循环
#[no_mangle]
pub extern "C" fn _start() -> ! {
    loop {}
}

我们加上 #![no_main] 告诉编译器我们不用常规的入口点。

同时我们实现一个 _start 函数来代替 crt0,并加上 #[no_mangle] 告诉编译器对于此函数禁用编译期间的名称重整(Name Mangling),即确保编译器生成一个名为 _start 的函数,而非为了实现函数重载等而生成的形如 _ZN3blog_os4_start7hb173fedf945531caE 散列化后的函数名。由于 _start 是大多数系统的默认入口点名字,所以我们要确保它不会发生变化。

接着,我们使用 extern "C" 描述 _start 函数,这是 Rust 中的 FFI (Foreign Function Interface, 语言交互接口)语法,表示此函数是一个 C 函数而非 Rust 函数。由于 _start 是作为 C 语言运行时的入口点,看起来合情合理。

由于程序会一直停在 crt0 的入口点,我们可以移除没用的 main 函数。

链接错误

再次 cargo build ,我们会看到一大段链接错误。

链接器(Linker)是一个程序,它将生成的目标文件组合为一个可执行文件。不同的操作系统如 Windows、macOS 或 Linux,规定了不同的可执行文件格式,因此也各有自己的链接器,抛出不同的错误;但这些错误的根本原因还是相同的:链接器的默认配置假定程序依赖于 C 语言的运行时环境,但我们的程序并不依赖于它。

为了解决这个错误,我们需要告诉链接器,它不应该包含 C 语言运行时环境。我们可以选择提供特定的链接器参数(Linker Argument),也可以选择编译为裸机目标(Bare Metal Target),我们将沿着后者的思路在后面解决这个问题,即直接编译为裸机目标不链接任何运行时环境。