# 1. 独立式可执行程序

> 本章代码对应 commit ：2ee1e8427f857388d9d93301d17d4464ffb2747a

## 概要

由于我们的目标是编写一个操作系统，所以首先我们需要创建一个独立于操作系统的可执行程序，又称 **独立式可执行程序（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](https://doc.rust-lang.org/1.13.0/book/nightly-rust.html) 。

安装成功后使用 `rustc --version` 或 `rustup show` 查看当前 rust 的版本。

```
$ rustc --version
rustc 1.38.0-nightly (8a58268b5 2019-07-31)
```

> 如果未能成功切换 rust 版本，请查看 [how to switch rust toolchain](https://github.com/LearningOS/rcore_step_by_step/wiki/how-to-switch-rust-toolchain)

## 创建并执行rust binary 项目

使用 `cargo new` 创建一个新的 rust binary 项目，如下：

```
$ cargo new os --bin --edition 2018
$ cd os
$ cargo run
Compiling os v0.1.0 (/media/chyyuu/chydata/thecode/rust-related/new_rcore_step_by_step/os)
    Finished dev [unoptimized + debuginfo] target(s) in 0.97s
     Running `target/debug/os`
Hello, world!
```

| `cargo new` 的参数  | 含义                              |
| ---------------- | ------------------------------- |
| `os`             | 项目的名称                           |
| `--bin`          | 可执行项目，和其相对的是库项目 `--lib`         |
| `--edition 2018` | 使用新版 Rust 2018 而不是老旧的 Rust 2015 |

第一个应用已经能运行了！但这不是我们需要的OS，而仅仅是一个离不了OS的应用程序而已。我们需要建立直接运行在硬件上的OS。下面将进行进一步探索。

## 添加 no\_std 属性

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

```rust
#![no_std]
fn main() {
    println!("Hello, world!");
}
```

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

```rust
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
```

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

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

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

```rust
#![no_std]
fn main() {}
```

## FIX error: '#\[panic\_handler]' function required, but not found

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

```rust
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`
```

## FIX error: language item required, but not found: 'eh\_personality'

**eh\_personality** 语义项（language item）是 exception handling personality 的意思。 我们不使用该项，故直接将 dev (use for `cargo build`) 和 release (use for `cargo build --release`) 的 panic 的处理策略设为 abort。

```rust
# Cargo.toml

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
```

> 通常, 当程序出现了异常 (这里指类似 Java 中层层抛出的异常), 从异常点开始会沿着 caller 调用栈一层一层回溯, 直到找到某个函数能够捕获 (catch) 这个异常. 这个过程称为 **堆栈展开 (stack unwinding)**. 回溯的时候, caller 中也许会有局部变量需要清理, 比如 C++ 的 RAII 的析构或者 Rust 的 drop. exception handling personality 是一个函数, 它对于每个 caller 函数执行这个过程. 这是一个复杂的过程, 并且还依赖其他各种库文件.
>
> 上面设置为 abort, 是指 OS panic 了那么我们不做任何清理的意思. 这样相对简单.

再次运行 `cargo build` ，不出所料，又出现了新的 error：

```
error: requires `start` lang_item
```

## FIX 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 入口点：

```rust
#![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 ，在其他系统请 [点击这里](https://os.phil-opp.com/freestanding-rust-binary/#summary) 。暂时无法编译也没关系，因为下一章会重写 `_start` 函数

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

再次执行 `cargo build` ，很不幸，又出现了 error：

```rust
linking with `cc` failed: exit code: 1
```

但幸运的是，这是我们本章所需要处理的最后一个 error!

## FIX error: linking with 'cc' failed: exit code: 1

在链接 C runtime 时，会需要一些 C 标准库(libc)的内容。由于 **#!\[no\_std]** 禁用了标准库，所以我们需要禁用常规的 C 启动例程。 于是将之前的 `cargo build` 换成如下的命令就好啦：

```
> cargo rustc -- -C link-arg=-nostartfiles
   Compiling os v0.1.0 (/mnt/os)
    Finished dev [unoptimized + debuginfo] target(s) in 0.21s
```

生成的可执行程序在 `target/debug/` 中，在 `Cargo.toml` 同级目录下执行如下命令可以看到

```
$ ls target/debug/os -l
-rwxr-xr-x 2 dzy dzy 10680 Aug  1 16:55 target/debug/os
```

> 适用于 Linux ，在其他系统请 [点击这里](https://os.phil-opp.com/freestanding-rust-binary/#summary)

可以把上述命令写到一个脚本"build.sh"中，并直接执行这个build.sh即可：

```
$ cat > build.sh
cargo rustc -- -C link-arg=-nostartfiles
$ chmod 755 build.sh
$ ./build.sh
```

历经千辛万苦，我们终于成功构建了一个 **Freestanding Rust Binary** ！！！

> 此处应有掌声

## 预告

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