mirror of
https://github.com/LearningOS/rust-based-os-comp2022.git
synced 2026-05-08 23:01:31 +08:00
add os[1-8]-ref for os refereces, add guide, add README
This commit is contained in:
96
guide/source/chapter1/0intro.rst
Normal file
96
guide/source/chapter1/0intro.rst
Normal file
@@ -0,0 +1,96 @@
|
||||
引言
|
||||
=====================
|
||||
|
||||
本章导读
|
||||
--------------------------
|
||||
|
||||
大多数程序员的职业生涯都从 ``Hello, world!`` 开始。
|
||||
|
||||
.. code-block::
|
||||
|
||||
printf("Hello world!\n");
|
||||
cout << "Hello world!\n";
|
||||
print("Hello world!")
|
||||
System.out.println("Hello world!");
|
||||
echo "Hello world!"
|
||||
println!("Hello world!");
|
||||
|
||||
然而,要用几行代码向世界问好,并不像表面上那么简单。
|
||||
``Hello, world!`` 程序能够编译运行,靠的是以 **编译器** 为主的开发环境和以 **操作系统** 为主的执行环境。
|
||||
|
||||
在本章中,我们将抽丝剥茧,一步步让 ``Hello, world!`` 程序脱离其依赖的执行环境,
|
||||
编写一个能打印 ``Hello, world!`` 的 OS。这趟旅途将让我们对应用程序及其执行环境有更深入的理解。
|
||||
|
||||
.. attention::
|
||||
实验指导书存在的目的是帮助读者理解框架代码。
|
||||
|
||||
为便于测试,完成编程实验时,请以框架代码为基础,不必跟着文档从零开始编写内核。
|
||||
|
||||
为了做到这一步,首先需要让程序不依赖于标准库,
|
||||
并通过编译。
|
||||
|
||||
接下来要让脱离了标准库的程序能输出(即支持 ``println!``),这对程序的开发和调试至关重要。
|
||||
我们先在用户态下实现该功能,在 `此处 <https://github.com/LearningOS/rCore-Tutorial-Book-2021Autumn/tree/ch2-U-nostd>`_ 获取相关代码。
|
||||
|
||||
最后把程序移植到内核态,构建在裸机上支持输出的最小运行时环境。
|
||||
|
||||
实践体验
|
||||
---------------------------
|
||||
|
||||
本章一步步实现了支持打印字符串的简单操作系统。
|
||||
|
||||
获取本章代码:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ git clone https://github.com/LearningOS/rCore-Tutorial-Code-2022S
|
||||
$ cd rCore-Tutorial-Code-2022S
|
||||
$ git checkout ch1
|
||||
|
||||
运行本章代码,并设置日志级别为 ``TRACE``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cd os
|
||||
$ make run LOG=TRACE
|
||||
|
||||
|
||||
预期输出:
|
||||
|
||||
.. figure:: color-demo.png
|
||||
:align: center
|
||||
|
||||
除了 ``Hello, world!`` 之外还有一些额外的信息,最后关机。
|
||||
|
||||
本章代码树
|
||||
------------------------------------------------
|
||||
|
||||
|
||||
.. code-block::
|
||||
|
||||
├── bootloader (内核依赖的运行在 M 特权级的 SBI 实现,本项目中我们使用 RustSBI)
|
||||
│ └── rustsbi-qemu.bin
|
||||
├── os
|
||||
│ ├── Cargo.toml (cargo 项目配置文件)
|
||||
│ ├── Makefile
|
||||
│ └── src
|
||||
│ ├── console.rs (将打印字符的 SBI 接口进一步封装实现更加强大的格式化输出)
|
||||
│ ├── entry.asm (设置内核执行环境的的一段汇编代码)
|
||||
│ ├── lang_items.rs (需要我们提供给 Rust 编译器的一些语义项,目前包含内核 panic 时的处理逻辑)
|
||||
│ ├── linker.ld (控制内核内存布局的链接脚本以使内核运行在 qemu 虚拟机上)
|
||||
│ ├── logging.rs (为本项目实现了日志功能)
|
||||
│ ├── main.rs (内核主函数)
|
||||
│ └── sbi.rs (封装底层 SBI 实现提供的 SBI 接口)
|
||||
└── rust-toolchain (整个项目的工具链版本)
|
||||
|
||||
cloc os
|
||||
-------------------------------------------------------------------------------
|
||||
Language files blank comment code
|
||||
-------------------------------------------------------------------------------
|
||||
Rust 5 25 6 155
|
||||
make 1 11 4 34
|
||||
Assembly 1 1 0 11
|
||||
TOML 1 2 1 7
|
||||
-------------------------------------------------------------------------------
|
||||
SUM: 8 39 11 207
|
||||
-------------------------------------------------------------------------------
|
||||
120
guide/source/chapter1/1app-ee-platform.rst
Normal file
120
guide/source/chapter1/1app-ee-platform.rst
Normal file
@@ -0,0 +1,120 @@
|
||||
应用程序执行环境与平台支持
|
||||
================================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 5
|
||||
|
||||
|
||||
执行应用程序
|
||||
-------------------------------
|
||||
|
||||
我们先从最简单的 Rust ``Hello, world`` 程序开始,用 Cargo 工具创建 Rust 项目。
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo new os
|
||||
|
||||
此时,项目的文件结构如下:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ tree os
|
||||
os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
|
||||
1 directory, 2 files
|
||||
|
||||
其中 ``Cargo.toml`` 中保存了项目的库依赖、作者信息等。
|
||||
|
||||
cargo 为我们准备好了 ``Hello world!`` 源代码:
|
||||
|
||||
.. code-block:: rust
|
||||
:linenos:
|
||||
:caption: 最简单的 Rust 应用
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
输入 ``cargo run`` 构建并运行项目:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.15s
|
||||
Running `target/debug/os`
|
||||
Hello, world!
|
||||
|
||||
我们在屏幕上看到了一行 ``Hello, world!`` ,但为了打印出 ``Hello, world!``,我们需要的不止几行源代码。
|
||||
|
||||
理解应用程序执行环境
|
||||
-------------------------------
|
||||
|
||||
在现代通用操作系统(如 Linux)上运行应用程序,需要多层次的执行环境栈支持:
|
||||
|
||||
|
||||
.. figure:: app-software-stack.png
|
||||
:align: center
|
||||
|
||||
应用程序执行环境栈:图中的白色块自上而下表示各级执行环境,黑色块则表示相邻两层执行环境之间的接口。
|
||||
下层作为上层的执行环境,支持上层代码运行。
|
||||
|
||||
我们的应用程序通过调用标准库或第三方库提供的接口,仅需少量源代码就能完成复杂的功能;
|
||||
``Hello, world!`` 程序调用的 ``println!`` 宏就是由 Rust 标准库 std 和 GNU Libc 等提供的。
|
||||
这些库属于应用程序的 **执行环境** (Execution Environment),而它们的实现又依赖于操作系统提供的系统调用。
|
||||
|
||||
平台与目标三元组
|
||||
---------------------------------------
|
||||
|
||||
编译器在编译、链接得到可执行文件时需要知道,程序要在哪个 **平台** (Platform) 上运行,
|
||||
**目标三元组** (Target Triplet) 描述了目标平台的 CPU 指令集、操作系统类型和标准运行时库。
|
||||
|
||||
我们研究一下现在 ``Hello, world!`` 程序的目标三元组是什么:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ rustc --version --verbose
|
||||
rustc 1.61.0-nightly (68369a041 2022-02-22)
|
||||
binary: rustc
|
||||
commit-hash: 68369a041cea809a87e5bd80701da90e0e0a4799
|
||||
commit-date: 2022-02-22
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.61.0-nightly
|
||||
LLVM version: 14.0.0
|
||||
|
||||
其中 host 一项表明默认目标平台是 ``x86_64-unknown-linux-gnu``,
|
||||
CPU 架构是 x86_64,CPU 厂商是 unknown,操作系统是 linux,运行时库是 gnu libc。
|
||||
|
||||
接下来,我们希望把 ``Hello, world!`` 移植到 RICV 目标平台 ``riscv64gc-unknown-none-elf`` 上运行。
|
||||
|
||||
.. note::
|
||||
|
||||
``riscv64gc-unknown-none-elf`` 的 CPU 架构是 riscv64gc,厂商是 unknown,操作系统是 none,
|
||||
elf 表示没有标准的运行时库。没有任何系统调用的封装支持,但可以生成 ELF 格式的执行程序。
|
||||
我们不选择有 linux-gnu 支持的 ``riscv64gc-unknown-linux-gnu``,是因为我们的目标是开发操作系统内核,而非在 linux 系统上运行的应用程序。
|
||||
|
||||
修改目标平台
|
||||
----------------------------------
|
||||
|
||||
将程序的目标平台换成 ``riscv64gc-unknown-none-elf``,试试看会发生什么:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo run --target riscv64gc-unknown-none-elf
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
error[E0463]: can't find crate for `std`
|
||||
|
|
||||
= note: the `riscv64gc-unknown-none-elf` target may not be installed
|
||||
|
||||
|
||||
报错的原因是目标平台上确实没有 Rust 标准库 std,也不存在任何受 OS 支持的系统调用。
|
||||
这样的平台被我们称为 **裸机平台** (bare-metal)。
|
||||
|
||||
幸运的是,除了 std 之外,Rust 还有一个不需要任何操作系统支持的核心库 core,
|
||||
它包含了 Rust 语言相当一部分核心机制,可以满足本门课程的需求。
|
||||
有很多第三方库也不依赖标准库 std,而仅仅依赖核心库 core。
|
||||
|
||||
为了以裸机平台为目标编译程序,我们要将对标准库 std 的引用换成核心库 core。
|
||||
158
guide/source/chapter1/2remove-std.rst
Normal file
158
guide/source/chapter1/2remove-std.rst
Normal file
@@ -0,0 +1,158 @@
|
||||
.. _term-remove-std:
|
||||
|
||||
移除标准库依赖
|
||||
==========================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 5
|
||||
|
||||
|
||||
首先在 ``os`` 目录下新建 ``.cargo`` 目录,并在这个目录下创建 ``config`` 文件,输入如下内容:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
# os/.cargo/config
|
||||
[build]
|
||||
target = "riscv64gc-unknown-none-elf"
|
||||
|
||||
|
||||
这将使 cargo 工具在 os 目录下默认会使用 riscv64gc-unknown-none-elf 作为目标平台。
|
||||
这种编译器运行的平台(x86_64)与可执行文件运行的目标平台不同的情况,称为 **交叉编译** (Cross Compile)。
|
||||
|
||||
移除 println! 宏
|
||||
----------------------------------
|
||||
|
||||
|
||||
我们在 ``main.rs`` 的开头加上一行 ``#![no_std]``,
|
||||
告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core。重新编译,报错如下:
|
||||
|
||||
.. error::
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo build
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
error: cannot find macro `println` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
|
||||
println! 宏是由标准库 std 提供的,且会使用到一个名为 write 的系统调用。
|
||||
无论如何,我们先将这行代码注释掉。
|
||||
|
||||
|
||||
提供语义项 panic_handler
|
||||
----------------------------------------------------
|
||||
|
||||
.. error::
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo build
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
|
||||
标准库 std 提供了 Rust 错误处理函数 ``#[panic_handler]``,其大致功能是打印出错位置和原因并杀死当前应用。
|
||||
但核心库 core 并没有提供这项功能,得靠我们自己实现。
|
||||
|
||||
新建一个子模块 ``lang_items.rs``,在里面编写 panic 处理函数,通过标记 ``#[panic_handler]`` 告知编译器采用我们的实现:
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
// os/src/lang_items.rs
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
目前我们遇到错误什么都不做,只在原地 ``loop`` 。
|
||||
|
||||
移除 main 函数
|
||||
-----------------------------
|
||||
|
||||
重新编译,又有了新错误:
|
||||
|
||||
.. error::
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ cargo build
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
error: requires `start` lang_item
|
||||
|
||||
编译器提醒我们缺少一个名为 ``start`` 的语义项。
|
||||
``start`` 语义项代表了标准库 std 在执行应用程序之前需要进行的一些初始化工作。由于我们禁用了标准库,编译器也就找不到这项功能的实现了。
|
||||
|
||||
在 ``main.rs`` 的开头加入设置 ``#![no_main]`` 告诉编译器我们没有一般意义上的 ``main`` 函数,
|
||||
并将原来的 ``main`` 函数删除。这样编译器也就不需要考虑初始化工作了。
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo build
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
|
||||
|
||||
至此,我们终于移除了所有标准库依赖,目前的代码如下:
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
// os/src/main.rs
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
mod lang_items;
|
||||
|
||||
// os/src/lang_items.rs
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
|
||||
分析被移除标准库的程序
|
||||
-----------------------------
|
||||
|
||||
我们可以通过一些工具来分析目前的程序:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
[文件格式]
|
||||
$ file target/riscv64gc-unknown-none-elf/debug/os
|
||||
target/riscv64gc-unknown-none-elf/debug/os: ELF 64-bit LSB executable, UCB RISC-V, ......
|
||||
|
||||
[文件头信息]
|
||||
$ rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os
|
||||
File: target/riscv64gc-unknown-none-elf/debug/os
|
||||
Format: elf64-littleriscv
|
||||
Arch: riscv64
|
||||
AddressSize: 64bit
|
||||
......
|
||||
Type: Executable (0x2)
|
||||
Machine: EM_RISCV (0xF3)
|
||||
Version: 1
|
||||
Entry: 0x0
|
||||
......
|
||||
}
|
||||
|
||||
[反汇编导出汇编程序]
|
||||
$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os
|
||||
target/riscv64gc-unknown-none-elf/debug/os: file format elf64-littleriscv
|
||||
|
||||
|
||||
通过 ``file`` 工具对二进制程序 ``os`` 的分析可以看到,它好像是一个合法的 RV64 执行程序,
|
||||
但 ``rust-readobj`` 工具告诉我们它的入口地址 Entry 是 ``0``。
|
||||
再通过 ``rust-objdump`` 工具把它反汇编,没有生成任何汇编代码。
|
||||
可见,这个二进制程序虽然合法,但它是一个空程序,原因是缺少了编译器规定的入口函数 ``_start`` 。
|
||||
|
||||
从下一节开始,我们将着手实现本节移除的、由用户态执行环境提供的功能。
|
||||
|
||||
.. note::
|
||||
|
||||
本节内容部分参考自 `BlogOS 的相关章节 <https://os.phil-opp.com/freestanding-rust-binary/>`_ 。
|
||||
|
||||
282
guide/source/chapter1/3mini-rt-usrland.rst
Normal file
282
guide/source/chapter1/3mini-rt-usrland.rst
Normal file
@@ -0,0 +1,282 @@
|
||||
.. _term-print-userminienv:
|
||||
|
||||
构建用户态执行环境
|
||||
=================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 5
|
||||
|
||||
.. note::
|
||||
|
||||
前三小节的用户态程序案例代码在 `此处 <https://github.com/LearningOS/rCore-Tutorial-Book-2021Autumn/tree/ch2-U-nostd>`_ 获取。
|
||||
|
||||
|
||||
用户态最小化执行环境
|
||||
----------------------------
|
||||
|
||||
执行环境初始化
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
首先我们要给 Rust 编译器编译器提供入口函数 ``_start()`` ,
|
||||
在 ``main.rs`` 中添加如下内容:
|
||||
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
// os/src/main.rs
|
||||
#[no_mangle]
|
||||
extern "C" fn _start() {
|
||||
loop{};
|
||||
}
|
||||
|
||||
|
||||
对上述代码重新编译,再用分析工具分析:
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo build
|
||||
Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
|
||||
|
||||
[反汇编导出汇编程序]
|
||||
$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os
|
||||
target/riscv64gc-unknown-none-elf/debug/os: file format elf64-littleriscv
|
||||
|
||||
Disassembly of section .text:
|
||||
|
||||
0000000000011120 <_start>:
|
||||
; loop {}
|
||||
11120: 09 a0 j 2 <_start+0x2>
|
||||
11122: 01 a0 j 0 <_start+0x2>
|
||||
|
||||
|
||||
反汇编出的两条指令就是一个死循环,
|
||||
这说明编译器生成的已经是一个合理的程序了。
|
||||
用 ``qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os`` 命令可以执行这个程序。
|
||||
|
||||
|
||||
程序正常退出
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
我们把 ``_start()`` 函数中的循环语句注释掉,重新编译并分析,看到其汇编代码是:
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os
|
||||
|
||||
target/riscv64gc-unknown-none-elf/debug/os: file format elf64-littleriscv
|
||||
|
||||
|
||||
Disassembly of section .text:
|
||||
|
||||
0000000000011120 <_start>:
|
||||
; }
|
||||
11120: 82 80 ret
|
||||
|
||||
看起来是合法的执行程序。但如果我们执行它,会引发问题:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os
|
||||
段错误 (核心已转储)
|
||||
|
||||
这个简单的程序导致 ``qemu-riscv64`` 崩溃了!为什么会这样?
|
||||
|
||||
.. note::
|
||||
|
||||
QEMU有两种运行模式:
|
||||
|
||||
``User mode`` 模式,即用户态模拟,如 ``qemu-riscv64`` 程序,
|
||||
能够模拟不同处理器的用户态指令的执行,并可以直接解析ELF可执行文件,
|
||||
加载运行那些为不同处理器编译的用户级Linux应用程序。
|
||||
|
||||
``System mode`` 模式,即系统态模式,如 ``qemu-system-riscv64`` 程序,
|
||||
能够模拟一个完整的基于不同CPU的硬件系统,包括处理器、内存及其他外部设备,支持运行完整的操作系统。
|
||||
|
||||
|
||||
目前的执行环境还缺了一个退出机制,我们需要操作系统提供的 ``exit`` 系统调用来退出程序。这里先给出代码:
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
// os/src/main.rs
|
||||
|
||||
const SYSCALL_EXIT: usize = 93;
|
||||
|
||||
fn syscall(id: usize, args: [usize; 3]) -> isize {
|
||||
let mut ret;
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
inlateout("x10") args[0] => ret,
|
||||
in("x11") args[1],
|
||||
in("x12") args[2],
|
||||
in("x17") id,
|
||||
);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn sys_exit(xstate: i32) -> isize {
|
||||
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn _start() {
|
||||
sys_exit(9);
|
||||
}
|
||||
|
||||
``main.rs`` 增加的内容不多,但还是有点与一般的应用程序有所不同,因为它引入了汇编和系统调用。
|
||||
第二章的第二节 :doc:`/chapter2/2application` 会详细介绍上述代码的含义。
|
||||
这里读者只需要知道 ``_start`` 函数调用了一个 ``sys_exit`` 函数,
|
||||
向操作系统发出了退出的系统调用请求,退出码为 ``9`` 。
|
||||
|
||||
我们编译执行以下修改后的程序:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo build --target riscv64gc-unknown-none-elf
|
||||
Compiling os v0.1.0 (/media/chyyuu/ca8c7ba6-51b7-41fc-8430-e29e31e5328f/thecode/rust/os_kernel_lab/os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.26s
|
||||
|
||||
[打印程序的返回值]
|
||||
$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os; echo $?
|
||||
9
|
||||
|
||||
可以看到,返回的结果确实是 ``9`` 。这样,我们勉强完成了一个简陋的用户态最小化执行环境。
|
||||
|
||||
|
||||
有显示支持的用户态执行环境
|
||||
----------------------------
|
||||
|
||||
没有 ``println`` 输出信息,终究觉得缺了点啥。
|
||||
|
||||
Rust 的 core 库内建了以一系列帮助实现显示字符的基本 Trait 和数据结构,函数等,我们可以对其中的关键部分进行扩展,就可以实现定制的 ``println!`` 功能。
|
||||
|
||||
|
||||
实现输出字符串的相关函数
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
.. attention::
|
||||
|
||||
如果你觉得理解 Rust 宏有困难,把它当成黑盒就好!
|
||||
|
||||
|
||||
首先封装一下对 ``SYSCALL_WRITE`` 系统调用。
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
const SYSCALL_WRITE: usize = 64;
|
||||
|
||||
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
|
||||
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
|
||||
}
|
||||
|
||||
然后实现基于 ``Write`` Trait 的数据结构,并完成 ``Write`` Trait 所需要的 ``write_str`` 函数,并用 ``print`` 函数进行包装。
|
||||
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
struct Stdout;
|
||||
|
||||
impl Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
sys_write(1, s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(args: fmt::Arguments) {
|
||||
Stdout.write_fmt(args).unwrap();
|
||||
}
|
||||
|
||||
最后,实现基于 ``print`` 函数,实现Rust语言 **格式化宏** ( `formatting macros <https://doc.rust-lang.org/std/fmt/#related-macros>`_ )。
|
||||
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($fmt: literal $(, $($arg: tt)+)?) => {
|
||||
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
($fmt: literal $(, $($arg: tt)+)?) => {
|
||||
print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
|
||||
}
|
||||
}
|
||||
|
||||
接下来,我们调整一下应用程序,让它发出显示字符串和退出的请求:
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn _start() {
|
||||
println!("Hello, world!");
|
||||
sys_exit(9);
|
||||
}
|
||||
|
||||
|
||||
现在,我们编译并执行一下,可以看到正确的字符串输出,且程序也能正确退出!
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cargo build --target riscv64gc-unknown-none-elf
|
||||
Compiling os v0.1.0 (/media/chyyuu/ca8c7ba6-51b7-41fc-8430-e29e31e5328f/thecode/rust/os_kernel_lab/os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
|
||||
|
||||
$ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os; echo $?
|
||||
Hello, world!
|
||||
9
|
||||
|
||||
|
||||
.. 下面出错的情况是会在采用 linker.ld,加入了 .cargo/config
|
||||
.. 的内容后会出错:
|
||||
.. .. [build]
|
||||
.. .. target = "riscv64gc-unknown-none-elf"
|
||||
.. .. [target.riscv64gc-unknown-none-elf]
|
||||
.. .. rustflags = [
|
||||
.. .. "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
|
||||
.. .. ]
|
||||
|
||||
.. 重新定义了栈和地址空间布局后才会出错
|
||||
|
||||
.. 段错误 (核心已转储)
|
||||
|
||||
.. 系统崩溃了!借助以往的操作系统内核编程经验和与下一节调试kernel的成果经验,我们直接定位为是 **栈** (Stack) 没有设置的问题。我们需要添加建立栈的代码逻辑。
|
||||
|
||||
.. .. code-block:: asm
|
||||
|
||||
.. # entry.asm
|
||||
|
||||
.. .section .text.entry
|
||||
.. .globl _start
|
||||
.. _start:
|
||||
.. la sp, boot_stack_top
|
||||
.. call rust_main
|
||||
|
||||
.. .section .bss.stack
|
||||
.. .globl boot_stack
|
||||
.. boot_stack:
|
||||
.. .space 4096 * 16
|
||||
.. .globl boot_stack_top
|
||||
.. boot_stack_top:
|
||||
|
||||
.. 然后把汇编代码嵌入到 ``main.rs`` 中,并进行微调。
|
||||
|
||||
.. .. code-block:: rust
|
||||
|
||||
.. #![feature(global_asm)]
|
||||
|
||||
.. global_asm!(include_str!("entry.asm"));
|
||||
|
||||
.. #[no_mangle]
|
||||
.. #[link_section=".text.entry"]
|
||||
.. extern "C" fn rust_main() {
|
||||
|
||||
.. 再次编译执行,可以看到正确的字符串输出,且程序也能正确结束!
|
||||
328
guide/source/chapter1/4mini-rt-baremetal.rst
Normal file
328
guide/source/chapter1/4mini-rt-baremetal.rst
Normal file
@@ -0,0 +1,328 @@
|
||||
.. _term-print-kernelminienv:
|
||||
|
||||
构建裸机执行环境
|
||||
=================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 5
|
||||
|
||||
有了上一节实现的用户态的最小执行环境,稍加改造,就可以完成裸机上的最小执行环境了。
|
||||
本节中,我们将把 ``Hello world!`` 应用程序从用户态搬到内核态。
|
||||
|
||||
|
||||
裸机启动过程
|
||||
----------------------------
|
||||
|
||||
用 QEMU 软件 ``qemu-system-riscv64`` 来模拟 RISC-V 64 计算机。加载内核程序的命令如下:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
qemu-system-riscv64 \
|
||||
-machine virt \
|
||||
-nographic \
|
||||
-bios $(BOOTLOADER) \
|
||||
-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)
|
||||
|
||||
|
||||
- ``-bios $(BOOTLOADER)`` 意味着硬件加载了一个 BootLoader 程序,即 RustSBI
|
||||
- ``-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)`` 表示硬件内存中的特定位置 ``$(KERNEL_ENTRY_PA)`` 放置了操作系统的二进制代码 ``$(KERNEL_BIN)`` 。 ``$(KERNEL_ENTRY_PA)`` 的值是 ``0x80200000`` 。
|
||||
|
||||
当我们执行包含上述启动参数的 qemu-system-riscv64 软件,就意味给这台虚拟的 RISC-V64 计算机加电了。
|
||||
此时,CPU 的其它通用寄存器清零,而 PC 会指向 ``0x1000`` 的位置,这里有固化在硬件中的一小段引导代码,
|
||||
它会很快跳转到 ``0x80000000`` 的 RustSBI 处。
|
||||
RustSBI完成硬件初始化后,会跳转到 ``$(KERNEL_BIN)`` 所在内存位置 ``0x80200000`` 处,
|
||||
执行操作系统的第一条指令。
|
||||
|
||||
.. figure:: chap1-intro.png
|
||||
:align: center
|
||||
|
||||
.. note::
|
||||
|
||||
**RustSBI 是什么?**
|
||||
|
||||
SBI 是 RISC-V 的一种底层规范,RustSBI 是它的一种实现。
|
||||
操作系统内核与 RustSBI 的关系有点像应用与操作系统内核的关系,后者向前者提供一定的服务。只是SBI提供的服务很少,
|
||||
比如关机,显示字符串等。
|
||||
|
||||
实现关机功能
|
||||
----------------------------
|
||||
|
||||
对上一节实现的代码稍作调整,通过 ``ecall`` 调用 RustSBI 实现关机功能:
|
||||
|
||||
.. _term-llvm-sbicall:
|
||||
|
||||
.. code-block:: rust
|
||||
|
||||
// bootloader/rustsbi-qemu.bin 直接添加的SBI规范实现的二进制代码,给操作系统提供基本支持服务
|
||||
|
||||
// os/src/sbi.rs
|
||||
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
|
||||
let mut ret;
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ecall",
|
||||
...
|
||||
|
||||
const SBI_SHUTDOWN: usize = 8;
|
||||
|
||||
pub fn shutdown() -> ! {
|
||||
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
|
||||
panic!("It should shutdown!");
|
||||
}
|
||||
|
||||
// os/src/main.rs
|
||||
#[no_mangle]
|
||||
extern "C" fn _start() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
|
||||
应用程序访问操作系统提供的系统调用的指令是 ``ecall`` ,操作系统访问
|
||||
RustSBI提供的SBI调用的指令也是 ``ecall`` ,
|
||||
虽然指令一样,但它们所在的特权级是不一样的。
|
||||
简单地说,应用程序位于最弱的用户特权级(User Mode),
|
||||
操作系统位于内核特权级(Supervisor Mode),
|
||||
RustSBI位于机器特权级(Machine Mode)。
|
||||
下一章会进一步阐释具体细节。
|
||||
|
||||
编译执行,结果如下:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# 编译生成ELF格式的执行文件
|
||||
$ cargo build --release
|
||||
Compiling os v0.1.0 (/media/chyyuu/ca8c7ba6-51b7-41fc-8430-e29e31e5328f/thecode/rust/os_kernel_lab/os)
|
||||
Finished release [optimized] target(s) in 0.15s
|
||||
# 把ELF执行文件转成bianary文件
|
||||
$ rust-objcopy --binary-architecture=riscv64 target/riscv64gc-unknown-none-elf/release/os --strip-all -O binary target/riscv64gc-unknown-none-elf/release/os.bin
|
||||
|
||||
# 加载运行
|
||||
$ qemu-system-riscv64 -machine virt -nographic -bios ../bootloader/rustsbi-qemu.bin -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
|
||||
# 无法退出,风扇狂转,感觉碰到死循环
|
||||
|
||||
问题在哪?通过 rust-readobj 分析 ``os`` 可执行程序,发现其入口地址不是
|
||||
RustSBI 约定的 ``0x80200000`` 。我们需要修改程序的内存布局并设置好栈空间。
|
||||
|
||||
|
||||
设置正确的程序内存布局
|
||||
----------------------------
|
||||
|
||||
可以通过 **链接脚本** (Linker Script) 调整链接器的行为,使得最终生成的可执行文件的内存布局符合我们的预期。
|
||||
|
||||
修改 Cargo 的配置文件来使用我们自己的链接脚本 ``os/src/linker.ld``:
|
||||
|
||||
.. code-block::
|
||||
:linenos:
|
||||
:emphasize-lines: 5,6,7,8
|
||||
|
||||
// os/.cargo/config
|
||||
[build]
|
||||
target = "riscv64gc-unknown-none-elf"
|
||||
|
||||
[target.riscv64gc-unknown-none-elf]
|
||||
rustflags = [
|
||||
"-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
|
||||
]
|
||||
|
||||
具体的链接脚本 ``os/src/linker.ld`` 如下:
|
||||
|
||||
.. code-block::
|
||||
:linenos:
|
||||
|
||||
OUTPUT_ARCH(riscv)
|
||||
ENTRY(_start)
|
||||
BASE_ADDRESS = 0x80200000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = BASE_ADDRESS;
|
||||
skernel = .;
|
||||
|
||||
stext = .;
|
||||
.text : {
|
||||
*(.text.entry)
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
etext = .;
|
||||
srodata = .;
|
||||
.rodata : {
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
erodata = .;
|
||||
sdata = .;
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
edata = .;
|
||||
.bss : {
|
||||
*(.bss.stack)
|
||||
sbss = .;
|
||||
*(.bss .bss.*)
|
||||
}
|
||||
|
||||
. = ALIGN(4K);
|
||||
ebss = .;
|
||||
ekernel = .;
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.eh_frame)
|
||||
}
|
||||
}
|
||||
|
||||
第 1 行我们设置了目标平台为 riscv ;第 2 行我们设置了整个程序的入口点为之前定义的全局符号 ``_start``;
|
||||
第 3 行定义了一个常量 ``BASE_ADDRESS`` 为 ``0x80200000`` ,RustSBI 期望的 OS 起始地址;
|
||||
|
||||
.. attention::
|
||||
|
||||
linker 脚本的语法不做要求,感兴趣的同学可以自行查阅相关资料。
|
||||
|
||||
从 ``BASE_ADDRESS`` 开始,代码段 ``.text``, 只读数据段 ``.rodata``,数据段 ``.data``, bss 段 ``.bss`` 由低到高依次放置,
|
||||
且每个段都有两个全局变量给出其起始和结束地址(比如 ``.text`` 段的开始和结束地址分别是 ``stext`` 和 ``etext`` )。
|
||||
|
||||
|
||||
正确配置栈空间布局
|
||||
----------------------------
|
||||
|
||||
用另一段汇编代码初始化栈空间:
|
||||
|
||||
.. code-block:: asm
|
||||
:linenos:
|
||||
|
||||
# os/src/entry.asm
|
||||
.section .text.entry
|
||||
.globl _start
|
||||
_start:
|
||||
la sp, boot_stack_top
|
||||
call rust_main
|
||||
|
||||
.section .bss.stack
|
||||
.globl boot_stack
|
||||
boot_stack:
|
||||
.space 4096 * 16
|
||||
.globl boot_stack_top
|
||||
boot_stack_top:
|
||||
|
||||
在第 8 行,我们预留了一块大小为 4096 * 16 字节,也就是 :math:`64\text{KiB}` 的空间,
|
||||
用作操作系统的栈空间。
|
||||
栈顶地址被全局符号 ``boot_stack_top`` 标识,栈底则被全局符号 ``boot_stack`` 标识。
|
||||
同时,这块栈空间被命名为
|
||||
``.bss.stack`` ,链接脚本里有它的位置。
|
||||
|
||||
``_start`` 作为操作系统的入口地址,将依据链接脚本被放在 ``BASE_ADDRESS`` 处。
|
||||
``la sp, boot_stack_top`` 作为 OS 的第一条指令,
|
||||
将 sp 设置为栈空间的栈顶。
|
||||
简单起见,我们目前不考虑 sp 越过栈底 ``boot_stack`` ,也就是栈溢出的情形。
|
||||
第二条指令则是函数调用 ``rust_main`` ,这里的 ``rust_main`` 是我们稍后自己编写的应用入口。
|
||||
|
||||
接着,我们在 ``main.rs`` 中嵌入这些汇编代码并声明应用入口 ``rust_main`` :
|
||||
|
||||
.. code-block:: rust
|
||||
:linenos:
|
||||
:emphasize-lines: 7,9,10,11,12
|
||||
|
||||
// os/src/main.rs
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
mod lang_items;
|
||||
|
||||
core::arch::global_asm!(include_str!("entry.asm"));
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> ! {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
背景高亮指出了 ``main.rs`` 中新增的代码。
|
||||
|
||||
第 7 行,我们使用 ``global_asm`` 宏,将同目录下的汇编文件 ``entry.asm`` 嵌入到代码中。
|
||||
|
||||
从第 9 行开始,
|
||||
我们声明了应用的入口点 ``rust_main`` ,需要注意的是,这里通过宏将 ``rust_main``
|
||||
标记为 ``#[no_mangle]`` 以避免编译器对它的名字进行混淆,不然在链接时,
|
||||
``entry.asm`` 将找不到 ``main.rs`` 提供的外部符号 ``rust_main``,导致链接失败。
|
||||
|
||||
再次使用上节中的编译,生成和运行操作,我们看到QEMU模拟的RISC-V 64计算机 **优雅** 地退出了!
|
||||
|
||||
.. code-block:: console
|
||||
# 教程使用的 RustSBI 版本比代码框架稍旧,输出有所不同
|
||||
$ qemu-system-riscv64 \
|
||||
> -machine virt \
|
||||
> -nographic \
|
||||
> -bios ../bootloader/rustsbi-qemu.bin \
|
||||
> -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
|
||||
[rustsbi] Version 0.1.0
|
||||
.______ __ __ _______.___________. _______..______ __
|
||||
| _ \ | | | | / | | / || _ \ | |
|
||||
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
|
||||
| / | | | | \ \ | | \ \ | _ < | |
|
||||
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
|
||||
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
|
||||
|
||||
[rustsbi] Platform: QEMU
|
||||
[rustsbi] misa: RV64ACDFIMSU
|
||||
[rustsbi] mideleg: 0x222
|
||||
[rustsbi] medeleg: 0xb1ab
|
||||
[rustsbi] Kernel entry: 0x80200000
|
||||
|
||||
|
||||
清空 .bss 段
|
||||
----------------------------------
|
||||
|
||||
等一等,与内存相关的部分太容易出错了, **清零 .bss 段** 的工作我们还没有完成。
|
||||
|
||||
.. code-block:: rust
|
||||
:linenos:
|
||||
|
||||
// os/src/main.rs
|
||||
fn clear_bss() {
|
||||
extern "C" {
|
||||
fn sbss();
|
||||
fn ebss();
|
||||
}
|
||||
(sbss as usize..ebss as usize).for_each(|a| {
|
||||
unsafe { (a as *mut u8).write_volatile(0) }
|
||||
});
|
||||
}
|
||||
|
||||
pub fn rust_main() -> ! {
|
||||
clear_bss();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
链接脚本 ``linker.ld`` 中给出的全局符号 ``sbss`` 和 ``ebss`` 让我们能轻松确定 ``.bss`` 段的位置。
|
||||
|
||||
|
||||
添加裸机打印相关函数
|
||||
----------------------------------
|
||||
|
||||
在上一节中我们为用户态程序实现的 ``println`` 宏,略作修改即可用于本节的内核态操作系统。
|
||||
详见 ``os/src/console.rs``。
|
||||
|
||||
利用 ``println`` 宏,我们重写异常处理函数 ``panic``,使其在 panic 时能打印错误发生的位置。
|
||||
相关代码位于 ``os/src/lang_items.rs`` 中。
|
||||
|
||||
我们还使用第三方库 ``log`` 为你实现了日志模块,相关代码位于 ``os/src/logging.rs`` 中。
|
||||
|
||||
.. note::
|
||||
|
||||
在 cargo 项目中引入外部库 log,需要修改 ``Cargo.toml`` 加入相应的依赖信息。
|
||||
|
||||
现在,让我们重复一遍本章开头的试验,``make run LOG=TRACE``!
|
||||
|
||||
.. figure:: color-demo.png
|
||||
:align: center
|
||||
|
||||
产生 panic 的地点与源码中的实际位置一致!至此,我们完成了第一章的实验内容,
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
背景知识:`理解应用程序和执行环境 <https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter1/4understand-prog.html>`_
|
||||
143
guide/source/chapter1/5exercise.rst
Normal file
143
guide/source/chapter1/5exercise.rst
Normal file
@@ -0,0 +1,143 @@
|
||||
chapter1练习(已经废弃,没删是怕以后有用)
|
||||
=====================================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 4
|
||||
|
||||
- 本节难度: **低**
|
||||
|
||||
编程作业
|
||||
-------------------------------
|
||||
|
||||
彩色化 LOG
|
||||
+++++++++++++++++++++++++++++++
|
||||
|
||||
.. lab1 的工作使得我们从硬件世界跳入了软件世界,当看到自己的小 os 可以在裸机硬件上输出 ``hello world`` 是不是很高兴呢?但是为了后续的一步开发,更好的调试环境也是必不可少的,第一章的练习要求大家实现更加炫酷的彩色log。
|
||||
|
||||
.. 详细的原理不多说,感兴趣的同学可以参考 `ANSI转义序列 <https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97>`_ ,现在执行如下这条命令试试
|
||||
|
||||
.. .. code-block:: console
|
||||
|
||||
.. $ echo -e "\x1b[31mhello world\x1b[0m"
|
||||
|
||||
.. 如果你明白了我们是如何利用串口实现输出,那么要实现彩色输出就十分容易了,只需要用需要输出的字符串替换上一条命令中的 ``hello world``,用期望颜色替换 ``31(代表红色)`` 即可。
|
||||
|
||||
.. .. warning::
|
||||
|
||||
.. 以下内容仅为推荐实现,不是练习要求,有时间和兴趣的同学可以尝试。
|
||||
|
||||
.. 我们推荐实现如下几个等级的输出,输出优先级依次降低:
|
||||
|
||||
.. .. list-table:: log 等级推荐
|
||||
.. :header-rows: 1
|
||||
.. :align: center
|
||||
|
||||
.. * - 名称
|
||||
.. - 颜色
|
||||
.. - 用途
|
||||
.. * - ERROR
|
||||
.. - 红色(31)
|
||||
.. - 表示发生严重错误,很可能或者已经导致程序崩溃
|
||||
.. * - WARN
|
||||
.. - 黄色(93)
|
||||
.. - 表示发生不常见情况,但是并不一定导致系统错误
|
||||
.. * - INFO
|
||||
.. - 蓝色(34)
|
||||
.. - 比较中庸的选项,输出比较重要的信息,比较常用
|
||||
.. * - DEBUG
|
||||
.. - 绿色(32)
|
||||
.. - 输出信息较多,在 debug 时使用
|
||||
.. * - TRACE
|
||||
.. - 灰色(90)
|
||||
.. - 最详细的输出,跟踪了每一步关键路径的执行
|
||||
|
||||
.. 我们可以输出比设定输出等级以及更高输出等级的信息,如设置 ``LOG = INFO``,则输出 ``ERROR``、``WARN``、``INFO`` 等级的信息。简单 demo 如下,输出等级为 INFO:
|
||||
|
||||
.. .. image:: color-demo.png
|
||||
|
||||
.. 为了方便使用彩色输出,我们要求同学们实现彩色输出的宏或者函数,用以代替 print 完成输出内核信息的功能,它们有着和 prinf 十分相似的使用格式,要求支持可变参数解析,形如:
|
||||
|
||||
.. .. code-block:: rust
|
||||
|
||||
.. // 这段代码输出了 os 内存空间布局,这到这些信息对于编写 os 十分重要
|
||||
|
||||
.. info!(".text [{:#x}, {:#x})", s_text as usize, e_text as usize);
|
||||
.. debug!(".rodata [{:#x}, {:#x})", s_rodata as usize, e_rodata as usize);
|
||||
.. error!(".data [{:#x}, {:#x})", s_data as usize, e_data as usize);
|
||||
|
||||
.. .. code-block:: c
|
||||
|
||||
.. info("load range : [%d, %d] start = %d\n", s, e, start);
|
||||
|
||||
.. 在以后,我们还可以在 log 信息中增加线程、CPU等信息(只是一个推荐,不做要求),这些信息将极大的方便你的代码调试。
|
||||
|
||||
|
||||
实验要求
|
||||
+++++++++++++++++++++++++++++++
|
||||
|
||||
.. - 实现分支:ch1。
|
||||
.. - 完成实验指导书中的内容,在裸机上实现 ``hello world`` 输出。
|
||||
.. - 实现彩色输出宏(只要求可以彩色输出,不要求 log 等级控制,不要求多种颜色)。
|
||||
.. - 隐形要求:可以关闭内核所有输出。从 lab2 开始要求关闭内核所有输出(如果实现了 log 等级控制,那么这一点自然就实现了)。
|
||||
.. - 利用彩色输出宏输出 os 内存空间布局,即:输出 ``.text``、``.data``、``.rodata``、``.bss`` 各段位置,输出等级为 ``INFO``。
|
||||
|
||||
实验检查
|
||||
+++++++++++++++++++++++++++++++
|
||||
|
||||
.. - 实验目录要求(Rust)
|
||||
|
||||
.. .. code-block::
|
||||
|
||||
.. ├── os(内核实现)
|
||||
.. │ ├── Cargo.toml(配置文件)
|
||||
.. │ ├── Makefile (要求 make run LOG=xxx 可以正确执行,可以不实现对 LOG 这一属性的支持,设置默认输出等级为 INFO)
|
||||
.. │ └── src(所有内核的源代码放在 os/src 目录下)
|
||||
.. │ ├── main.rs(内核主函数)
|
||||
.. │ └── ...
|
||||
.. ├── reports
|
||||
.. │ ├── lab1.md/pdf
|
||||
.. │ └── ...
|
||||
.. ├── README.md(其他必要的说明)
|
||||
.. ├── ...
|
||||
|
||||
.. 报告命名 labx.md/pdf,统一放在 reports 目录下。每个实验新增一个报告,为了方便修改,检查报告是以最新分支的所有报告为准。
|
||||
|
||||
.. - 检查
|
||||
|
||||
.. .. code-block:: console
|
||||
|
||||
.. $ cd os
|
||||
.. $ git checkout ch1
|
||||
.. $ make run LOG=INFO
|
||||
|
||||
.. 可以正确执行(可以不支持LOG参数,只有要彩色输出就好),可以看到正确的内存布局输出,根据实现不同数值可能有差异,但应该位于 ``linker.ld`` 中指示 ``BASE_ADDRESS`` 后一段内存,输出之后关机。
|
||||
|
||||
问答作业
|
||||
-------------------------------
|
||||
|
||||
.. 1. 为了方便 os 处理,M态软件会将 S 态异常/中断委托给 S 态软件,请指出有哪些寄存器记录了委托信息,rustsbi 委托了哪些异常/中断?(也可以直接给出寄存器的值)
|
||||
|
||||
.. 2. 请学习 gdb 调试工具的使用(这对后续调试很重要),并通过 gdb 简单跟踪从机器加电到跳转到 0x80200000 的简单过程。只需要描述重要的跳转即可,只需要描述在 qemu 上的情况。
|
||||
|
||||
.. 3. tips:
|
||||
|
||||
.. - 事实上进入 rustsbi 之后就不需要使用 gdb 调试了。可以直接阅读代码。`rustsbi起始代码 <https://github.com/rustsbi/rustsbi-qemu/blob/main/rustsbi-qemu/src/main.rs#L146>`_ 。
|
||||
.. - 可以使用示例代码 Makefile 中的 ``make debug`` 指令。
|
||||
.. - 一些可能用到的 gdb 指令:
|
||||
.. - ``x/10i 0x80000000`` : 显示 0x80000000 处的10条汇编指令。
|
||||
.. - ``x/10i $pc`` : 显示即将执行的10条汇编指令。
|
||||
.. - ``x/10xw 0x80000000`` : 显示 0x80000000 处的10条数据,格式为16进制32bit。
|
||||
.. - ``info register``: 显示当前所有寄存器信息。
|
||||
.. - ``info r t0``: 显示 t0 寄存器的值。
|
||||
.. - ``break funcname``: 在目标函数第一条指令处设置断点。
|
||||
.. - ``break *0x80200000``: 在 0x80200000 出设置断点。
|
||||
.. - ``continue``: 执行直到碰到断点。
|
||||
.. - ``si``: 单步执行一条汇编指令。
|
||||
|
||||
报告要求
|
||||
-------------------------------
|
||||
|
||||
- 简单总结你实现的功能(200字以内,不要贴代码)。
|
||||
- 完成问答题。
|
||||
- (optional) 你对本次实验设计及难度/工作量的看法,以及有哪些需要改进的地方,欢迎畅所欲言。
|
||||
BIN
guide/source/chapter1/app-software-stack.png
Normal file
BIN
guide/source/chapter1/app-software-stack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
guide/source/chapter1/chap1-intro.png
Normal file
BIN
guide/source/chapter1/chap1-intro.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
BIN
guide/source/chapter1/color-demo.png
Normal file
BIN
guide/source/chapter1/color-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
13
guide/source/chapter1/index.rst
Normal file
13
guide/source/chapter1/index.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
.. _link-chapter1:
|
||||
|
||||
第一章:应用程序与基本执行环境
|
||||
==============================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
0intro
|
||||
1app-ee-platform
|
||||
2remove-std
|
||||
3mini-rt-usrland
|
||||
4mini-rt-baremetal
|
||||
Reference in New Issue
Block a user