实验要求
-
实现分支:ch1
-
完成实验指导书中的内容并在裸机上实现 hello world 输出。
-
实现彩色输出宏 (只要求可以彩色输出,不要求 log 等级控制,不要求多种颜色)
-
隐形要求:可以关闭内核所有输出。从 lab2 开始要求关闭内核所有输出(如果实现了 log 等级控制,那么这一点自然就实现了)。
-
利用彩色输出宏输出 os 内存空间布局
-
输出 .text、.data、.rodata、.bss 各段位置,输出等级为 INFO。
challenge: 支持多核,实现多个核的 boot。
开始
基于 rCore-Tutorial-v3 的 ch1
进行开发彩色打印 “hello world” 很简单,只需要参考 println!
宏进行实现即可,难点在于裸机打印。
先晒结果:
打印彩色字体
彩色打印是基于 ANSI 转义序列。在 shell 中执行如下命令就可以打印彩色字体。
echo -e "\x1b[31mhello world\x1b[0m"
在前端开发中,浏览器 devTools 的输出也支持彩色打印字体、背景等,跟上述 shell 打印是一样的。
console.log('\x1b[31mhello world\x1b[0m')
在裸机上实现 hello world 输出
首先要了解在操作系统支持下打印 “hello world” 和在裸机上打印的区别。知道在操作系统支持下打印 “hello world” 究竟发生了什么,这是如何在裸机上打印的关键点。
首先我们假设使用 Rust 向标准输出流打印 “hello world”,会编写如下代码:
fn main() {
println!("hello world");
}
然后执行 cargo run
或者直接通过 rustc
进行编译执行,这样标准输出设备上就会显示 “hello world” 了。println!
是一个 rust 标准库支持的宏,这个宏的实现实际上又调用了操作系统的系统调用,系统调用再调用硬件接口来实现打印功能。
然而我们要在裸机上打印 “hello world”,假设我们还需要使用上面的程序代码实现,这就有问题了,裸机并没有操作系统,所以没有系统调用供 rust 标准库来实现打印宏。
还有一个问题,就算是裸机也有不同的硬件支持,我们编译的程序也需要知道编译结果需要在什么平台上运行,即目标平台,一般用(CPU 架构,CPU 厂商,操作系统,运行时)来区分,比如我们实验的平台就是 riscv64gc-unknown-none-elf
。rust 标准库在裸机平台上没有实现,但在一些平台,比如 riscv64gc-unknown-linux-gnu
这种有操作系统和运行时的平台 rust 标准库是有实现的。
综上,我们需要做的就是直接调用操作系统之下的运行环境接口(SBI,Supervisor Binary Interface)来打印而不使用标准库,并将 rust 程序编译为 riscv64gc-unknown-none-elf
平台上的程序,然后在对应裸机上执行。
在这里,使用 qemu-system-riscv64
来模拟 riscv 裸机环境,其中有一些限制,你需要知道这个裸机对应的 bootloader 应该放在内存哪里,bootloader 执行完成后跳转到的我们的程序起始位置在哪里。
log 控制等级、展示不同颜色
因为 rust 有现成的 crate 可用,使用了 log 这个 crate,只需要实现 Log
接口即可通过等级来打印不同颜色的日志,也可以设置最强日志等级进行拦截。
利用彩色输出宏输出 os 内存空间布局
获取内存空间布局,即各个段的位置,即获取一些全局符号,.stext
、.etext
等的位置,在这里我们使用如下代码:
extern "C" {
fn stext();
fn etext();
fn srodata();
fn erodata();
fn sdata();
fn edata();
fn sbss();
fn ebss();
fn boot_stack();
fn boot_stack_top();
}
这是根据 “C” 这种约定的 ABI 来进行获取全局符号,这样我们就获得了各个段的开始和结尾。