mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-20 07:57:49 +00:00
[Chinese] Translate posts 5-8 and improve translation of posts 1-4 (#1131)
This commit is contained in:
@@ -1,23 +1,25 @@
|
||||
+++
|
||||
title = "最小化内核"
|
||||
title = "最小内核"
|
||||
weight = 2
|
||||
path = "zh-CN/minimal-rust-kernel"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
# Please update this when updating the translation
|
||||
translation_based_on_commit = "bd6fbcb1c36705b2c474d7fcee387bfea1210851"
|
||||
translation_based_on_commit = "096c044b4f3697e91d8e30a2e817e567d0ef21a2"
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["luojia65", "Rustin-Liu"]
|
||||
translators = ["luojia65", "Rustin-Liu", "liuyuran"]
|
||||
# GitHub usernames of the people that contributed to this translation
|
||||
translation_contributors = ["JiangengDong"]
|
||||
+++
|
||||
|
||||
在这篇文章中,我们将基于 **x86架构**(the x86 architecture),使用 Rust 语言,编写一个最小化的 64 位内核。我们将从上一章中构建的独立式可执行程序开始,构建自己的内核;它将向显示器打印字符串,并能被打包为一个能够引导启动的**磁盘映像**(disk image)。
|
||||
在这篇文章中,我们将基于 **x86架构**(the x86 architecture),使用 Rust 语言,编写一个最小化的 64 位内核。我们将从上一章中构建的[独立式可执行程序][freestanding-rust-binary]开始,构建自己的内核;它将向显示器打印字符串,并能被打包为一个能够引导启动的**磁盘映像**(disk image)。
|
||||
|
||||
[freestanding Rust binary]: @/edition-2/posts/01-freestanding-rust-binary/index.md
|
||||
|
||||
<!-- more -->
|
||||
|
||||
This blog is openly developed on [GitHub]. If you have any problems or questions, please open an issue there. You can also leave comments [at the bottom]. The complete source code for this post can be found in the [`post-02`][post branch] branch.
|
||||
此博客在 [GitHub] 上公开开发. 如果您有任何问题或疑问,请在此处打开一个 issue。 您也可以在[底部][at the bottom]发表评论. 这篇文章的完整源代码可以在 [`post-02`] [post branch] 分支中找到。
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[at the bottom]: #comments
|
||||
@@ -32,7 +34,7 @@ This blog is openly developed on [GitHub]. If you have any problems or questions
|
||||
|
||||
x86 架构支持两种固件标准: **BIOS**([Basic Input/Output System](https://en.wikipedia.org/wiki/BIOS))和 **UEFI**([Unified Extensible Firmware Interface](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface))。其中,BIOS 标准显得陈旧而过时,但实现简单,并为 1980 年代后的所有 x86 设备所支持;相反地,UEFI 更现代化,功能也更全面,但开发和构建更复杂(至少从我的角度看是如此)。
|
||||
|
||||
在这篇文章中,我们暂时只提供 BIOS 固件的引导启动方式。
|
||||
在这篇文章中,我们暂时只提供 BIOS 固件的引导启动方式,但是UEFI支持也已经在计划中了。如果你希望帮助我们推进它,请查阅这份 [Github issue](https://github.com/phil-opp/blog_os/issues/349)。
|
||||
|
||||
### BIOS 启动
|
||||
|
||||
@@ -57,11 +59,17 @@ x86 架构支持两种固件标准: **BIOS**([Basic Input/Output System](htt
|
||||
3. GRUB 和 Multiboot 标准并没有被详细地解释,阅读相关文档需要一定经验;
|
||||
4. 为了创建一个能够被引导的磁盘映像,我们在开发时必须安装 GRUB:这加大了基于 Windows 或 macOS 开发内核的难度。
|
||||
|
||||
出于这些考虑,我们决定不使用 GRUB 或者 Multiboot 标准。然而,Multiboot 支持功能也在 bootimage 工具的开发计划之中,所以从原理上讲,如果选用 bootimage 工具,在未来使用 GRUB 引导你的系统内核是可能的。
|
||||
出于这些考虑,我们决定不使用 GRUB 或者 Multiboot 标准。然而,Multiboot 支持功能也在 bootimage 工具的开发计划之中,所以从原理上讲,如果选用 bootimage 工具,在未来使用 GRUB 引导你的系统内核是可能的。 如果你对编写一个支持 Mutiboot 标准的内核有兴趣,可以查阅 [初版文档][first edition]。
|
||||
|
||||
## 最小化内核
|
||||
[first edition]: @/edition-1/_index.md
|
||||
|
||||
现在我们已经明白电脑是如何启动的,那也是时候编写我们自己的内核了。我们的小目标是,创建一个内核的磁盘映像,它能够在启动时,向屏幕输出一行“Hello World!”;我们的工作将基于上一章构建的独立式可执行程序。
|
||||
### UEFI
|
||||
|
||||
(截至此时,我们并未提供UEFI相关教程,但我们确实有此意向。如果你愿意提供一些帮助,请在 [Github issue](https://github.com/phil-opp/blog_os/issues/349) 告知我们,不胜感谢。)
|
||||
|
||||
## 最小内核
|
||||
|
||||
现在我们已经明白电脑是如何启动的,那也是时候编写我们自己的内核了。我们的小目标是,创建一个内核的磁盘映像,它能够在启动时,向屏幕输出一行“Hello World!”;我们的工作将基于上一章构建的[独立式可执行程序][freestanding Rust binary]。
|
||||
|
||||
如果读者还有印象的话,在上一章,我们使用 `cargo` 构建了一个独立的二进制程序;但这个程序依然基于特定的操作系统平台:因平台而异,我们需要定义不同名称的函数,且使用不同的编译指令。这是因为在默认情况下,`cargo` 会为特定的**宿主系统**(host system)构建源码,比如为你正在运行的系统构建源码。这并不是我们想要的,因为我们的内核不应该基于另一个操作系统——我们想要编写的,就是这个操作系统。确切地说,我们想要的是,编译为一个特定的**目标系统**(target system)。
|
||||
|
||||
@@ -135,7 +143,9 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
|
||||
"disable-redzone": true,
|
||||
```
|
||||
|
||||
我们正在编写一个内核,所以我们应该同时处理中断。要安全地实现这一点,我们必须禁用一个与**红区**(redzone)有关的栈指针优化:因为此时,这个优化可能会导致栈被破坏。我们撰写了一篇专门的短文,来更详细地解释红区及与其相关的优化。
|
||||
我们正在编写一个内核,所以我们迟早要处理中断。要安全地实现这一点,我们必须禁用一个与**红区**(redzone)有关的栈指针优化:因为此时,这个优化可能会导致栈被破坏。如果需要更详细的资料,请查阅我们的一篇关于 [禁用红区][disabling the red zone] 的短文。
|
||||
|
||||
[disabling the red zone]: @/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.zh-CN.md
|
||||
|
||||
```json
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
@@ -147,7 +157,7 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
|
||||
|
||||
禁用 SIMD 产生的一个问题是,`x86_64` 架构的浮点数指针运算默认依赖于 SIMD 寄存器。我们的解决方法是,启用 `soft-float` 特征,它将使用基于整数的软件功能,模拟浮点数指针运算。
|
||||
|
||||
为了让读者的印象更清晰,我们撰写了一篇关于禁用 SIMD 的短文。
|
||||
为了让读者的印象更清晰,我们撰写了一篇关于 [禁用 SIMD][disabling SIMD](@/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.zh-CN.md) 的短文。
|
||||
|
||||
现在,我们将各个配置项整合在一起。我们的目标配置清单应该长这样:
|
||||
|
||||
@@ -171,7 +181,9 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
|
||||
|
||||
### 编译内核
|
||||
|
||||
要编译我们的内核,我们将使用 Linux 系统的编写风格(这可能是 LLVM 的默认风格)。这意味着,我们需要把前一篇文章中编写的入口点重命名为 `_start`:
|
||||
要编译我们的内核,我们将使用 Linux 系统的编写风格(这可能是 LLVM 的默认风格)。这意味着,我们需要把[前一篇文章][previous post]中编写的入口点重命名为 `_start`:
|
||||
|
||||
[previous post]: @/edition-2/posts/01-freestanding-rust-binary/index.md
|
||||
|
||||
```rust
|
||||
// src/main.rs
|
||||
@@ -203,61 +215,99 @@ pub extern "C" fn _start() -> ! {
|
||||
> cargo build --target x86_64-blog_os.json
|
||||
|
||||
error[E0463]: can't find crate for `core`
|
||||
(或者是下面的错误)
|
||||
error[E0463]: can't find crate for `compiler_builtins`
|
||||
```
|
||||
|
||||
哇哦,编译失败了!输出的错误告诉我们,Rust 编译器找不到 `core` 或者 `compiler_builtins` 包;而所有 `no_std` 上下文都隐式地链接到这两个包。[`core` 包](https://doc.rust-lang.org/nightly/core/index.html)包含基础的 Rust 类型,如` Result`、`Option` 和迭代器等;[`compiler_builtins` 包](https://github.com/rust-lang-nursery/compiler-builtins)提供 LLVM 需要的许多底层操作,比如 `memcpy`。
|
||||
毫不意外的编译失败了,错误信息告诉我们编译器没有找到 [`core`][`core` library] 这个crate,它包含了Rust语言中的部分基础类型,如 `Result`、`Option`、迭代器等等,并且它还会隐式链接到 `no_std` 特性里面。
|
||||
|
||||
通常状况下,`core` 库以**预编译库**(precompiled library)的形式与 Rust 编译器一同发布——这时,`core` 库只对支持的宿主系统有效,而我们自定义的目标系统无效。如果我们想为其它系统编译代码,我们需要为这些系统重新编译整个 `core` 库。
|
||||
[`core` library]: https://doc.rust-lang.org/nightly/core/index.html
|
||||
|
||||
### Cargo xbuild
|
||||
通常状况下,`core` crate以**预编译库**(precompiled library)的形式与 Rust 编译器一同发布——这时,`core` crate只对支持的宿主系统有效,而对我们自定义的目标系统无效。如果我们想为其它系统编译代码,我们需要为这些系统重新编译整个 `core` crate。
|
||||
|
||||
这就是为什么我们需要 [cargo xbuild 工具](https://github.com/rust-osdev/cargo-xbuild)。这个工具封装了 `cargo build`;但不同的是,它将自动交叉编译 `core` 库和一些**编译器内建库**(compiler built-in libraries)。我们可以用下面的命令安装它:
|
||||
#### `build-std` 选项
|
||||
|
||||
```bash
|
||||
cargo install cargo-xbuild
|
||||
此时就到了cargo中 [`build-std` 特性][`build-std` feature] 登场的时刻,该特性允许你按照自己的需要重编译 `core` 等标准crate,而不需要使用Rust安装程序内置的预编译版本。 但是该特性是全新的功能,到目前为止尚未完全完成,所以它被标记为 "unstable" 且仅被允许在 [nightly Rust 编译器][nightly Rust compilers] 环境下调用。
|
||||
|
||||
[`build-std` feature]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
|
||||
[nightly Rust compilers]: #安装 Nightly Rust
|
||||
|
||||
要启用该特性,你需要创建一个 [cargo 配置][cargo configuration] 文件,即 `.cargo/config.toml`,并写入以下语句:
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
|
||||
[unstable]
|
||||
build-std = ["core", "compiler_builtins"]
|
||||
```
|
||||
|
||||
这个工具依赖于Rust的源代码;我们可以使用 `rustup component add rust-src` 来安装源代码。
|
||||
该配置会告知cargo需要重新编译 `core` 和 `compiler_builtins` 这两个crate,其中 `compiler_builtins` 是 `core` 的必要依赖。 另外重编译需要提供源码,我们可以使用 `rustup component add rust-src` 命令来下载它们。
|
||||
|
||||
现在我们可以使用 `xbuild` 代替 `build` 重新编译:
|
||||
<div class="note">
|
||||
|
||||
```bash
|
||||
> cargo xbuild --target x86_64-blog_os.json
|
||||
**Note:** 仅 `2020-07-15` 之后的Rust nightly版本支持 `unstable.build-std` 配置项。
|
||||
|
||||
</div>
|
||||
|
||||
在设定 `unstable.build-std` 配置项并安装 `rust-src` 组件之后,我们就可以开始编译了:
|
||||
|
||||
```
|
||||
> cargo build --target x86_64-blog_os.json
|
||||
Compiling core v0.0.0 (/…/rust/src/libcore)
|
||||
Compiling compiler_builtins v0.1.5
|
||||
Compiling rustc-std-workspace-core v1.0.0 (/…/rust/src/tools/rustc-std-workspace-core)
|
||||
Compiling alloc v0.0.0 (/tmp/xargo.PB7fj9KZJhAI)
|
||||
Finished release [optimized + debuginfo] target(s) in 45.18s
|
||||
Compiling blog_os v0.1.0 (file:///…/blog_os)
|
||||
Compiling rustc-std-workspace-core v1.99.0 (/…/rust/src/tools/rustc-std-workspace-core)
|
||||
Compiling compiler_builtins v0.1.32
|
||||
Compiling blog_os v0.1.0 (/…/blog_os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
|
||||
```
|
||||
|
||||
我们能看到,`cargo xbuild` 为我们自定义的目标交叉编译了 `core`、`compiler_builtin` 和 `alloc` 三个部件。这些部件使用了大量的**不稳定特性**(unstable features),所以只能在[nightly 版本的 Rust 编译器][installing rust nightly]中工作。这之后,`cargo xbuild` 成功地编译了我们的 `blog_os` 包。
|
||||
如你所见,在执行 `cargo build` 之后, `core`、`rustc-std-workspace-core` (`compiler_builtins` 的依赖)和 `compiler_builtins` crate被重新编译了。
|
||||
|
||||
[installing rust nightly]: #an-zhuang-nightly-rust
|
||||
#### 内存相关函数
|
||||
|
||||
现在我们可以为裸机编译内核了;但是,我们提供给引导程序的入口点 `_start` 函数还是空的。我们可以添加一些东西进去,不过我们可以先做一些优化工作。
|
||||
目前来说,Rust编译器假定所有内置函数(`built-in functions`)在所有系统内都是存在且可用的。事实上这个前提只对了一半,
|
||||
绝大多数内置函数都可以被 `compiler_builtins` 提供,而这个crate刚刚已经被我们重编译过了,然而部分内存相关函数是需要操作系统相关的标准C库提供的。
|
||||
比如,`memset`(该函数可以为一个内存块内的所有比特进行赋值)、`memcpy`(将一个内存块里的数据拷贝到另一个内存块)以及`memcmp`(比较两个内存块的数据)。
|
||||
好在我们的内核暂时还不需要用到这些函数,但是不要高兴的太早,当我们编写更丰富的功能(比如拷贝数据结构)时就会用到了。
|
||||
|
||||
### 设置默认目标
|
||||
现在我们当然无法提供操作系统相关的标准C库,所以我们需要使用其他办法提供这些东西。一个显而易见的途径就是自己实现 `memset` 这些函数,但不要忘记加入 `#[no_mangle]` 语句,以避免编译时被自动重命名。 当然,这样做很危险,底层函数中最细微的错误也会将程序导向不可预知的未来。比如,你可能在实现 `memcpy` 时使用了一个 `for` 循环,然而 `for` 循环本身又会调用 [`IntoIterator::into_iter`] 这个trait方法,这个方法又会再次调用 `memcpy`,此时一个无限递归就产生了,所以还是使用经过良好测试的既存实现更加可靠。
|
||||
|
||||
为了避免每次使用`cargo xbuild`时传递`--target`参数,我们可以覆写默认的编译目标。我们创建一个名为`.cargo/config`的[cargo配置文件](https://doc.rust-lang.org/cargo/reference/config.html),添加下面的内容:
|
||||
[`IntoIterator::into_iter`]: https://doc.rust-lang.org/stable/core/iter/trait.IntoIterator.html#tymethod.into_iter
|
||||
|
||||
幸运的是,`compiler_builtins` 事实上自带了所有相关函数的实现,只是在默认情况下,出于避免和标准C库发生冲突的考量被禁用掉了,此时我们需要将 [`build-std-features`] 配置项设置为 `["compiler-builtins-mem"]` 来启用这个特性。如同 `build-std` 配置项一样,该特性可以使用 `-Z` 参数启用,也可以在 `.cargo/config.toml` 中使用 `unstable` 配置集启用。现在我们的配置文件中的相关部分是这样子的:
|
||||
|
||||
[`build-std-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std-features
|
||||
|
||||
```toml
|
||||
# in .cargo/config
|
||||
# in .cargo/config.toml
|
||||
|
||||
[unstable]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
build-std = ["core", "compiler_builtins"]
|
||||
```
|
||||
|
||||
(`compiler-builtins-mem` 特性是在 [这个PR](https://github.com/rust-lang/rust/pull/77284) 中被引入的,所以你的Rust nightly更新时间必须晚于 `2020-09-30`。)
|
||||
|
||||
该参数为 `compiler_builtins` 启用了 [`mem` 特性][`mem` feature],至于具体效果,就是已经在内部通过 `#[no_mangle]` 向链接器提供了 [`memcpy` 等函数的实现][`memcpy` etc. implementations]。
|
||||
|
||||
[`mem` feature]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/Cargo.toml#L54-L55
|
||||
[`memcpy` etc. implementations]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/src/mem.rs#L12-L69
|
||||
|
||||
经过这些修改,我们的内核已经完成了所有编译所必需的函数,那么让我们继续对代码进行完善。
|
||||
|
||||
#### 设置默认编译目标
|
||||
|
||||
每次调用 `cargo build` 命令都需要传入 `--target` 参数很麻烦吧?其实我们可以复写掉默认值,从而省略这个参数,只需要在 `.cargo/config.toml` 中加入以下 [cargo 配置][cargo configuration]:
|
||||
|
||||
[cargo configuration]: https://doc.rust-lang.org/cargo/reference/config.html
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
|
||||
[build]
|
||||
target = "x86_64-blog_os.json"
|
||||
```
|
||||
|
||||
这里的配置告诉 `cargo` 在没有显式声明目标的情况下,使用我们提供的 `x86_64-blog_os.json` 作为目标配置。这意味着保存后,我们可以直接使用:
|
||||
这个配置会告知 `cargo` 使用 `x86_64-blog_os.json` 这个文件作为默认的 `--target` 参数,此时只输入短短的一句 `cargo build` 就可以编译到指定平台了。如果你对其他配置项感兴趣,亦可以查阅 [官方文档][cargo configuration]。
|
||||
|
||||
```
|
||||
cargo xbuild
|
||||
```
|
||||
|
||||
来编译我们的内核。[官方提供的一份文档](https://doc.rust-lang.org/cargo/reference/config.html)中有对 cargo 配置文件更详细的说明。
|
||||
那么现在我们已经可以用 `cargo build` 完成程序编译了,然而被成功调用的 `_start` 函数的函数体依然是一个空空如也的循环,是时候往屏幕上输出一点什么了。
|
||||
|
||||
### 向屏幕打印字符
|
||||
|
||||
@@ -332,7 +382,7 @@ cargo install bootimage
|
||||
> cargo bootimage
|
||||
```
|
||||
|
||||
可以看到的是,`bootimage` 工具开始使用 `cargo xbuild` 编译你的内核,所以它将增量编译我们修改后的源码。在这之后,它会编译内核的引导程序,这可能将花费一定的时间;但和所有其它依赖包相似的是,在首次编译后,产生的二进制文件将被缓存下来——这将显著地加速后续的编译过程。最终,`bootimage` 将把内核和引导程序组合为一个可引导的磁盘映像。
|
||||
可以看到的是,`bootimage` 工具开始使用 `cargo build` 编译你的内核,所以它将增量编译我们修改后的源码。在这之后,它会编译内核的引导程序,这可能将花费一定的时间;但和所有其它依赖包相似的是,在首次编译后,产生的二进制文件将被缓存下来——这将显著地加速后续的编译过程。最终,`bootimage` 将把内核和引导程序组合为一个可引导的磁盘映像。
|
||||
|
||||
运行这行命令之后,我们应该能在 `target/x86_64-blog_os/debug` 目录内找到我们的映像文件 `bootimage-blog_os.bin`。我们可以在虚拟机内启动它,也可以刻录到 U 盘上以便在真机上启动。(需要注意的是,因为文件格式不同,这里的 bin 文件并不是一个光驱映像,所以将它刻录到光盘不会起作用。)
|
||||
|
||||
@@ -349,10 +399,13 @@ cargo install bootimage
|
||||
现在我们可以在虚拟机中启动内核了。为了在[ QEMU](https://www.qemu.org/) 中启动内核,我们使用下面的命令:
|
||||
|
||||
```bash
|
||||
> qemu-system-x86_64 -drive format=raw,file=bootimage-blog_os.bin
|
||||
> qemu-system-x86_64 -drive format=raw,file=target/x86_64-blog_os/debug/bootimage-blog_os.bin
|
||||
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
|
||||
```
|
||||
|
||||

|
||||
然后就会弹出一个独立窗口:
|
||||
|
||||

|
||||
|
||||
我们可以看到,屏幕窗口已经显示出 “Hello World!” 字符串。祝贺你!
|
||||
|
||||
@@ -383,7 +436,7 @@ runner = "bootimage runner"
|
||||
|
||||
命令 `bootimage runner` 由 `bootimage` 包提供,参数格式经过特殊设计,可以用于 `runner` 命令。它将给定的可执行文件与项目的引导程序依赖项链接,然后在 QEMU 中启动它。`bootimage` 包的 [README文档](https://github.com/rust-osdev/bootimage) 提供了更多细节和可以传入的配置参数。
|
||||
|
||||
现在我们可以使用 `cargo xrun` 来编译内核并在 QEMU 中启动了。和 `xbuild` 类似,`xrun` 子命令将在调用 cargo 命令前编译内核所需的包。这个子命令也由 `cargo-xbuild` 工具提供,所以你不需要安装额外的工具。
|
||||
现在我们可以使用 `cargo run` 来编译内核并在 QEMU 中启动了。
|
||||
|
||||
## 下篇预告
|
||||
|
||||
|
||||
Reference in New Issue
Block a user