Skip to content

Commit

Permalink
精修了前 7 个部分 chyyuu#13
Browse files Browse the repository at this point in the history
  • Loading branch information
LyricZhao committed Apr 13, 2020
1 parent 56e83a0 commit 941ad86
Show file tree
Hide file tree
Showing 14 changed files with 104 additions and 83 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
# rCore 教学实验(开发中)

*(介绍)*
本教学仓库是继 [rCore_tutorial](https://rcore-os.github.io/rCore_tutorial_doc/) 后重构的版本。

## 仓库目录

- `docs/`教学实验指导分实验内容
- `notes/`开发日志
- `docs/`教学实验指导分实验内容和开发规范
- `notes/`开题报告和若干讨论
- `os/`:操作系统代码
- `SUMMARY.md`:GitBook 目录页
- `book.json`:GitBook 配置文件
- `rust-toolchain`:限定 Rust 工具链版本
*note 目前使用比较旧的,整体完成之后在考虑更新*

- `rust-toolchain`:限定 Rust 工具链版本
<!-- Rust 工具链版本需要根据时间更新 -->

## 实验指导

基于 GitBook,目前尚未部署到服务器

*部署好像需要一些特殊的配置,需要 Tsinghua Git 提供域名,以及添加服务器(在 gitlab.io 上的话似乎可以直接搞)*
基于 GitBook,目前已经部署到了 [GitHub Pages](https://os20-rcore-tutorial.github.io/rCore-Tutorial-deploy)
上面。

### 本地使用方法

```shell
```bash
npm install -g gitbook-cli
gitbook install
gitbook serve
```

## 代码

### 操作系统代码
基于 cargo 项目,进入 `os` 目录通过相关命令可以运行:
```bash
cd os
# 编译并运行
make run
# 根据代码注释生成文档
cargo doc
```
7 changes: 3 additions & 4 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* [环境部署](docs/pre-lab/env.md)

## 实验指导
* 零:一个运行在 OpenSBI 之上的程序
* 实验指导零
* [摘要](docs/lab-0/guide/intro.md)
* [创建项目](docs/lab-0/guide/part-1.md)
* [移除标准库依赖](docs/lab-0/guide/part-2.md)
Expand All @@ -27,7 +27,7 @@
* [使用 QEMU 运行](docs/lab-0/guide/part-8.md)
* [接口封装和代码整理](docs/lab-0/guide/part-9.md)
* [小结](docs/lab-0/guide/summary.md)
* 一:中断处理
* 实验指导一
* [摘要](docs/lab-1/guide/intro.md)
* [背景知识](docs/lab-1/guide/part-1.md)
* [RISC-V 中的中断](docs/lab-1/guide/part-2.md)
Expand All @@ -38,5 +38,4 @@
* [小结](docs/lab-1/guide/summary.md)

## 实验题目
* [零:TODO](docs/lab-0/exercise/intro.md)
* [一:异常处理](docs/lab-1/exercise/intro.md)
* [实验题目一](docs/lab-1/exercise/intro.md)
9 changes: 8 additions & 1 deletion book.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@
"search-pro",
"click-reveal",
"expandable-chapters-interactive"
]
],
"pluginsConfig": {
"fontsettings": {
"theme": "white",
"family": "sans",
"size": 1
}
}
}
4 changes: 4 additions & 0 deletions docs/format/code.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## 代码规范

### 代码风格
- 以 cargo 输出没有 Warning 为准
- 可以通过 `cargo fmt` 来自动规范代码

### 注释规范
-`//!` 注释外层内容,例如在文件开始注释整个模块
-`///` 为函数添加 doc 注释,其内部使用 markdown 语法
Expand Down
2 changes: 1 addition & 1 deletion docs/format/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
- 其他一些名词
- ABI
- GitHub
- Bootloader
- Rust 相关
- rustup
- cargo
Expand Down Expand Up @@ -81,6 +80,7 @@
}
```
- 在使用伪代码时,不使用 `$` `%` 等符号描述寄存器,使用 `:=` 表示赋值,例如 `pc := sepc`
- 代码过长或会让文档显得很长时,需要进行折叠

### 小节格式

Expand Down
3 changes: 3 additions & 0 deletions docs/lab-0/exercise/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 练习题

<!-- TODO 讨论需不需要放入练习题 -->
3 changes: 2 additions & 1 deletion docs/lab-0/guide/part-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### 创建项目
我们首先创建一个整个项目的目录,并在工作目录中首先创建一个名为 `rust-toolchain` 的文件,并在其中写入所需要的工具链版本:
{% label %}rust-toolchain{% endlabel %}
```
nightly-2020-03-06
```
Expand Down Expand Up @@ -37,4 +38,4 @@ $ cargo run
Hello, world!
```

打开 `os/src/main.rs` 发现里面确实只是输出了一行 `Hello, world!`。这个应用可以正常运行,但是即使只是这么一个简单的功能,也离不开所在操作系统的帮助。我们既然要写一个新的操作系统,就不能依赖于任何已有操作系统,接下来我们尝试移除该应用对于操作系统的依赖
打开 `os/src/main.rs` 发现里面确实只是输出了一行 `Hello, world!`。这个应用可以正常运行,但是即使只是这么一个简单的功能,也离不开所在操作系统的帮助。我们既然要写一个新的操作系统,就不能依赖于任何已有操作系统,接下来我们尝试移除该项目对于操作系统的依赖
44 changes: 20 additions & 24 deletions docs/lab-0/guide/part-2.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
## 移除标准库依赖

### 禁用标准库
项目默认是链接 Rust 标准库 std 的,它依赖于操作系统,因此我们需要显式将其禁用
项目默认是链接 Rust 标准库 std 的,它依赖于操作系统,因此我们需要显式通过 `#![no_std]` 将其禁用

{% label %}os/src/main.rs{% endlabel %}
```rust
// os/src/main.rs

//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
Expand All @@ -18,6 +17,7 @@ fn main() {

我们使用 `cargo build` 构建项目,会出现下面的错误:

{% label %}运行输出{% endlabel %}
```rust
error: cannot find macro `println` in this scope
--> src/main.rs:3:5
Expand All @@ -32,19 +32,14 @@ error: language item required, but not found: `eh_personality`

### 宏 println!

第一个错误是说 `println!` 宏未找到,实际上这个宏属于 Rust 标准库 std,由于它被我们禁用了当然就找不到了。我们暂时将该输出语句删除,之后给出不依赖操作系统的实现。

> **[info] `println!` 哪里依赖了操作系统**
>
> 这个宏所调用的函数会输出到标准输出,而这需要操作系统的支持。
第一个错误是说 `println!` 宏未找到,实际上这个宏属于 Rust 标准库 std,它会依赖操作系统标准输出等一系列功能。由于它被我们禁用了当然就找不到了。我们暂时将该输出语句删除,之后给出不依赖操作系统的实现。

### panic 处理函数

第二个错误是说需要一个函数作为 `panic_handler`这个函数负责在程序 `panic` 时调用。它默认使用标准库 std 中实现的函数并依赖于操作系统特殊的文件描述符,由于我们禁用了标准库,因此只能自己实现它:
第二个错误是说需要一个函数作为 `panic_handler`这个函数负责在程序发生 panic 时调用。它默认使用标准库 std 中实现的函数并依赖于操作系统特殊的文件描述符,由于我们禁用了标准库,因此只能自己实现它:

{% label %}os/src/main.rs{% endlabel %}
```rust
// os/src/main.rs

use core::panic::PanicInfo;

/// 当 panic 发生时会调用该函数
Expand All @@ -55,36 +50,36 @@ fn panic(_info: &PanicInfo) -> ! {
}
```

> **[info] Rust panic**
> **[info] Rust Panic**
>
> panic 在 Rust 中表明程序遇到了不可恢复的错误,只能被迫停止运行
> Panic 在 Rust 中表明程序遇到了错误,需要被迫停止运行或者通过捕获的机制来处理
类型为 `PanicInfo` 的参数包含了 panic 发生的文件名、代码行数和可选的错误信息。这个函数从不返回,所以他被标记为发散函数(diverging function)。发散函数的返回类型称作 Never 类型("never" type),记为 `!`。对这个函数,我们目前能做的很少,所以我们只需编写一个死循环 `loop {}`
类型为 `PanicInfo` 的参数包含了 panic 发生的文件名、代码行数和可选的错误信息。这个函数从不返回,所以他被标记为发散函数(Diverging Function)。发散函数的返回类型称作 Never 类型("never" type),记为 `!`。对这个函数,我们目前能做的很少,所以我们只需编写一个死循环 `loop {}`

这里我们用到了核心库 core,与标准库 std 不同,这个库不需要操作系统的支持,下面我们还会与它打交道。

### eh_personality 语义项

第三个错误提到了语义项(language item) ,它是编译器内部所需的特殊函数或类型。刚才的 `panic_handler` 也是一个语义项,我们要用它告诉编译器当程序 panic 之后如何处理。
第三个错误提到了语义项(Language Item) ,它是编译器内部所需的特殊函数或类型。刚才的 `panic_handler` 也是一个语义项,我们要用它告诉编译器当程序发生 panic 之后如何处理。

而这个错误相关语义项 `eh_personality` ,其中 eh 是 exception handling 的缩写,它是一个标记某函数用来实现**堆栈展开**处理功能的语义项。这个语义项也与 panic 有关。
而这个错误相关语义项 `eh_personality` ,其中 eh 是 Exception Handling 的缩写,它是一个标记某函数用来实现**堆栈展开**处理功能的语义项。这个语义项也与 panic 有关。

> **[info] 堆栈展开 (stack unwinding) **
> **[info] 堆栈展开 (Stack Unwinding) **
>
> 通常,当程序出现了异常 (这里指类似 Java 中层层抛出的异常),从异常点开始会沿着 caller 调用栈一层一层回溯,直到找到某个函数能够捕获这个异常。这个过程称为堆栈展开。
> 通常当程序出现了异常时,从异常点开始会沿着 caller 调用栈一层一层回溯,直到找到某个函数能够捕获这个异常或终止程序。这个过程称为堆栈展开。
>
> 当程序出现不可恢复错误时,我们需要沿着调用栈一层层回溯上去回收每个 caller 中定义的局部变量避免造成内存溢出。这里的回收包括 C++ 的 RAII 的析构以及 Rust 的 drop。
> 当程序出现异常时,我们需要沿着调用栈一层层回溯上去回收每个 caller 中定义的局部变量(这里的回收包括 C++ 的 RAII 的析构以及 Rust 的 drop 等)避免造成捕获异常并恢复后的内存溢出
>
> 而在 Rust 中,panic 证明程序出现了不可恢复错误,我们则会对于每个 caller 函数调用依次这个被标记为堆栈展开处理函数的函数
> 而在 Rust 中,panic 证明程序出现了错误,我们则会对于每个 caller 函数调用依次这个被标记为堆栈展开处理函数的函数进行清理
>
> 这个处理函数是一个依赖于操作系统的复杂过程,在标准库中实现,我们禁用了标准库使得编译器找不到该过程的实现函数了
> 这个处理函数是一个依赖于操作系统的复杂过程,在标准库中实现。但是我们禁用了标准库使得编译器找不到该过程的实现函数了
简单起见,我们暂时不考虑内存溢出,设置当程序 panic 时不做任何清理工作,直接退出程序即可。这样堆栈展开处理函数不会被调用,编译器也就不会去寻找它的实现了。
简单起见,我们这里不会进一步捕获异常也不需要清理现场,我们设置为直接退出程序即可。这样堆栈展开处理函数不会被调用,编译器也就不会去寻找它的实现了。

因此,我们在项目配置文件中直接将 dev 配置 (对应构建命令为 `cargo build`) 和 release 配置(对应构建命令为 `cargo build --release`)的 panic 的处理策略设为直接终止(abort)而不进行堆栈展开(当然我们写的 panic 处理函数还是会调用的)
因此,我们在项目配置文件中直接将 dev 配置和 release 配置的 panic 的处理策略设为直接终止,也就是直接调用我们的 `panic_handler` 而不是先进行堆栈展开等处理再调用

{% label %}os/Cargo.toml{% endlabel %}
```toml
// Cargo.toml
...

# panic 时直接终止,因为我们没有实现堆栈展开的功能
Expand All @@ -97,6 +92,7 @@ panic = "abort"

此时,我们 `cargo build` ,但是又出现了新的错误,我们将在后面的部分解决:

{% label %}运行输出{% endlabel %}
```bash
error: requires `start` lang_item
```
19 changes: 8 additions & 11 deletions docs/lab-0/guide/part-3.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
## 移除运行时环境依赖

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

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

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

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

{% label %}os/src/main.rs{% endlabel %}
```rust
// src/main.rs

//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
Expand Down Expand Up @@ -40,18 +39,16 @@ pub extern "C" fn _start() -> ! {

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

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

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

返回值类型为 `!` 表明这个函数不会返回。由于这个函数被操作系统或 Bootloader 直接调用,这样做是必须的。

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

### 链接错误

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

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

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

0 comments on commit 941ad86

Please sign in to comment.