使用Rust从零写操作系统 (1) —— 独立式可执行程序

释放双眼,带上耳机,听听看~!

本系列博客系转载,出处: 知乎专栏:从零开始写 OS

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


1
2
3
1本小节代码对应 commit :bc429d04d479da60fc4bd05824a124d18c5e33ac
2
3

概要

由于我们的目标是编写一个操作系统,所以首先我们需要创建一个独立于操作系统的可执行程序,又称 独立式可执行程序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 属性

因为我们的目标是编写一个操作系统,所以我们不能使用任何依赖于操作系统的库。项目默认是链接标准库的,我们需要显示的将其禁用:


1
2
3
4
5
6
7
8
9
1// main.rs
2
3#![no_std]
4
5fn main() {
6    println!("Hello, world!");
7}
8
9

如果此时执行 cargo build 构建项目,会产生以下两个错误:


1
2
3
4
5
6
7
8
9
1error: cannot find macro `println!` in this scope
2 --> src/main.rs:6:5
3  |
46 |     println!("Hello, world!");
5  |     ^^^^^^^
6
7error: `#[panic_handler]` function required, but not found
8
9

现在我们来依次解决这两个问题。

error: cannot find macro 'println!' in this scope

println 宏属于标准库,所以禁用标准库后自然不能再使用 println函数 。由于我们当前目标只是写一个可执行的文件,所以将其删除即可:


1
2
3
4
5
6
7
1// in main.rs
2
3#![no_std]
4
5fn main() {}
6
7

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

在程序发生 panic 时需要调用相应函数。标准库有对应函数,但是由于我们使用了no_std 属性,所以接下来我们需要自己实现一个函数:


1
2
3
4
5
6
7
8
9
10
11
1// in main.rs
2
3use core::panic::PanicInfo;
4
5// This function is called on panic.
6#[panic_handler]
7fn panic(_info: &PanicInfo) -> ! {
8    loop {}
9}
10
11

由于程序 panic 后就应该结束,所以用-> ! 表示该函数不会返回。由于目前的 OS 功能还很弱小,所以只能无限循环。

解决了上述两个 error 后,再次执行 cargo build ,结果出现了新的 error:


1
2
3
1error: language item required, but not found: `eh_personality`
2
3

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

eh_personality语义项(language item)用于标记函数:该函数在 堆栈展开(stack unwinding) 时被调用。当程序发生 panic 时,rust 会调用 堆栈展开 析构堆栈中的所有生存变量,达到释放内存的目的。但是这是一个复杂的过程,而且依赖于一些其他的库文件。所以我们只是简单的将其禁用:


1
2
3
4
5
6
7
8
9
1# Cargo.toml
2
3[profile.dev]
4panic = "abort"
5
6[profile.release]
7panic = "abort"
8
9

将 dev (use for cargo build) 和 release (use for cargo build –release) 的 panic 的处理策略设为abort ,这样我们便不再依赖 eh_personality 语义项。

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


1
2
3
1error: requires `start` lang_item
2
3

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 入口点:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2#![no_std] // don't link the Rust standard library
3#![no_main] // disable all Rust-level entry points
4
5use core::panic::PanicInfo;
6
7// This function is called on panic.
8#[panic_handler]
9fn panic(_info: &PanicInfo) -> ! {
10    loop {}
11}
12
13#[no_mangle] // don't mangle the name of this function
14pub extern "C" fn _start() -> ! {
15    // this function is the entry point, since the linker looks for a function
16    // named `_start` by default
17    loop {}
18}
19
20

适用于 Linux ,在其他系统请 点击这里 。暂时无法编译也没关系,因为下一小节会重写 _start 函数。

这里 pub extern "C" fn main 就是我们需要的start 。 #[no_mangle]属性用于防止改名称被混淆。由于 start 只能由操作系统或引导加载程序直接调用,不会被其他函数调用,所以不能够返回。如果需要离开该函数,应该使用 exit 系统调用。但是由于我们的操作系统还没有实现 exit 系统调用,所以暂时使用无限循环防止函数返回。由于 start 函数无法返回或退出,自然也就不会调用 main 。所以将 main 函数删除,并且增加属性标签 #![no_main] 。

再次执行 cargo build ,很不幸,又出现了 error:


1
2
3
1linking with `cc` failed: exit code: 1
2
3

但幸运的是,这是我们本章所需要处理的最后一个 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 !!!


1
2
3
1此处应有掌声
2
3

预告

下一章,我们将在 Freestanding Rust Binary 的基础上,创建 最小内核 ,将其和 bootloader 链接成为可以被 qemu 加载的 bootimage 。并且将能够在屏幕上打印Hello World!

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++ lambda表达式

2022-1-11 12:36:11

安全运维

react开发 vscode 插件配置

2021-12-19 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索