移除标准库依赖
禁用标准库
项目默认是链接 Rust 标准库 std 的,它依赖于操作系统,因此我们需要显式通过 #![no_std]
将其禁用:
os/src/main.rs
//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
#![no_std]
fn main() {
println!("Hello, rCore-Tutorial!");
}
我们使用 cargo build
构建项目,会出现下面的错误:
运行输出
error: cannot find macro `println` in this scope
--> src/main.rs:3:5
|
7 | println!("Hello, rCore-Tutorial!");
| ^^^^^^^
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
接下来,我们依次解决这些问题。
宏 println!
第一个错误是说 println!
宏未找到,实际上这个宏属于 Rust 标准库 std,它会依赖操作系统标准输出等一系列功能。由于它被我们禁用了当然就找不到了。我们暂时将该输出语句删除,之后给出不依赖操作系统的实现。
panic 处理函数
第二个错误是说需要一个函数作为 panic_handler
,这个函数负责在程序发生 panic 时调用。它默认使用标准库 std 中实现的函数并依赖于操作系统特殊的文件描述符,由于我们禁用了标准库,因此只能自己实现它:
os/src/main.rs
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
[info] Rust Panic
Panic 在 Rust 中表明程序遇到了错误,需要被迫停止运行或者通过捕获的机制来处理。
类型为 PanicInfo
的参数包含了 panic 发生的文件名、代码行数和可选的错误信息。这个函数从不返回,所以他被标记为发散函数(Diverging Function)。发散函数的返回类型称作 Never 类型("never" type),记为 !
。对这个函数,我们目前能做的很少,所以我们只需编写一个死循环 loop {}
。
这里我们用到了核心库 core,与标准库 std 不同,这个库不需要操作系统的支持,下面我们还会与它打交道。
eh_personality 语义项
第三个错误提到了语义项(Language Item) ,它是编译器内部所需的特殊函数或类型。刚才的 panic_handler
也是一个语义项,我们要用它告诉编译器当程序发生 panic 之后如何处理。
而这个错误相关语义项 eh_personality
,其中 eh 是 Exception Handling 的缩写,它是一个标记某函数用来实现堆栈展开处理功能的语义项。这个语义项也与 panic 有关。
[info] 堆栈展开 (Stack Unwinding)
通常当程序出现了异常时,从异常点开始会沿着 caller 调用栈一层一层回溯,直到找到某个函数能够捕获这个异常或终止程序。这个过程称为堆栈展开。
当程序出现异常时,我们需要沿着调用栈一层层回溯上去回收每个 caller 中定义的局部变量(这里的回收包括 C++ 的 RAII 的析构以及 Rust 的 drop 等)避免造成捕获异常并恢复后的内存溢出。
而在 Rust 中,panic 证明程序出现了错误,我们则会对于每个 caller 函数调用依次这个被标记为堆栈展开处理函数的函数进行清理。
这个处理函数是一个依赖于操作系统的复杂过程,在标准库中实现。但是我们禁用了标准库使得编译器找不到该过程的实现函数了。
简单起见,我们这里不会进一步捕获异常也不需要清理现场,我们设置为直接退出程序即可。这样堆栈展开处理函数不会被调用,编译器也就不会去寻找它的实现了。
因此,我们在项目配置文件中直接将 dev 配置和 release 配置的 panic 的处理策略设为直接终止,也就是直接调用我们的 panic_handler
而不是先进行堆栈展开等处理再调用。
os/Cargo.toml
...
# panic 时直接终止,因为我们没有实现堆栈展开的功能
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
此时,我们 cargo build
,但是又出现了新的错误,我们将在后面的部分解决:
运行输出
error: requires `start` lang_item