Rust 写操作系统之 Hello world(二) 已经完成了 Hello World 打印的任务了。这个章节属于进阶篇,主要利用 Rust 宏和包特性的使用,使得我们的输出字符串的能力更强,同时引入第三库让 OpenSBI 相关函数调用更加简洁。学过 C 语言的同学再学习 Rust 之后基本回不去了, Rust 的宏和包管理仅这两特性就可以碾压 C 语言。
sbi-rt 包引入
sbi-rt 包是为特权软件提供 RISC-V 特权二进制接口(Supervisor Binary Interface)的运行时库。
我们将在 myos 中引用这个库,方便后续开发更多的 sbi 功能使用(包括 timer、中断等)。
Rust 使用 cargo 进行包管理,引用第三库会自动从 http://crate.io 仓库下载(也可以引用本地的包)。
引用 sbi-rt 库(指定 0.0.3 版本)只要在 Cargo.toml 添加如下信息即可:
[dependencies]
sbi-rt = { version = "0.0.3", features = ["legacy"] }
在我们工程源码目录下新建一个 sbi.rs 源码文件,我们使用 sbi-rt 库封装一个串口输出和关机的函数。
/***************************************************************
*
* OpenSBI 已经提供了打印串口输出的函数调用,不需要自己写串口驱动了
* 通过 ecall 指令陷入到 OpenSBI,通过汇编把打印的字符串传入即可
*
***************************************************************/
pub fn console_putchar(c: usize) {
#[allow(deprecated)]
sbi_rt::legacy::console_putchar(c);
}
/***************************************************************
* OpenSBI 关机命令
***************************************************************/
pub fn shutdown(failure: bool) -> ! {
use sbi_rt::{system_reset, NoReason, Shutdown, SystemFailure};
if !failure {
system_reset(Shutdown, NoReason);
} else {
system_reset(Shutdown, SystemFailure);
}
unreachable!()
}
我们已经生成了 sbi.rs 并定义了两个函数,Cargo 默认是不会自动编译我们这个文件的。Rust 引用一个文件的代码,需要手动使用 mod 关键字来告诉编译器需要编译这个文件。使用方法是在 src/main.rs 中添加如下代码:
#![no_std]
#![no_main]
#![feature(asm_const)]
mod sbi;
我们来测试一下 OpenSBI 提供的 shutdown 关机函数,这样我们就不会每次运行完 Qemu 之后卡在那里,还要手动按组合件 Ctrl+a x 退出了。修改 src/main.rs 中的 rust_entry 函数,在最后一行添加
/***************************************************************
*
* rust_entry 初始化 bss 段内存并打印 Hello myos
*
***************************************************************/
#[no_mangle]
unsafe extern "C" fn rust_entry(_cpu_id: usize, _dtb: usize) {
// 导入 linker.ld 中定义的符号,最终输出的值可以在 target/output.map 文件中查看
extern "C" { fn sbss(); fn ebss(); }
/* 初始化 bss 段内存 */
core::slice::from_raw_parts_mut(sbss as *mut u8, ebss as usize - sbss as usize).fill(0);
/* 打印字符串 */
for ch in "Hello myos!\n".chars() {
console_putchar(ch as usize);
}
sbi::shutdown(false);
}
?
至此我们已经完成了第三方包的引用,并封装了 sbi 模块并完成了关机功能的使用测试。测试验证命令如下:
cargo build
?
rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/debug/myos -O binary target/riscv64gc-unknown-none-elf/debug/myos.bin
?
qemu-system-riscv64 -machine virt -bios default -nographic -kernel target/riscv64gc-unknown-none-elf/debug/myos.bin
Rust 宏的使用
我们已经支持了串口的单字符串打印信息,这个是最底层的基础函数,使用起来并不是很方便。
下面我们使用 Rust 的 macro_rules 来定义跟标准库一样的 print! 和 println! 两个宏。
首先我们新建一个 console.rs 文件,在里面我们实现上述两个宏。
/* 这里不使用 std,只使用 core 的定义 */
use core::fmt::{self, Write};
/* 定义一个空对象,主要是为了继承 Write 这个 trait 并继承其默认代码 */
struct Stdout;
/* 继承 Write trait */
impl Write for Stdout {
/* 我们只需要使用这个函数即可,其他默认实现会把 format 转成 str */
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
crate::sbi::console_putchar(c as usize);
}
Ok(())
}
}
/* 定义打印函数,参数: format_args 宏的返回值 */
pub fn __print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
// 定义 print 宏
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {
$crate::console::__print(format_args!($($arg)*));
}
}
// 定义 println 宏
#[macro_export]
macro_rules! println {
() => { $crate::print!("\n") };
($($arg:tt)*) => {
$crate::console::__print(format_args!("{}\n", format_args!($($arg)*)));
}
}
Rust 的宏有两大类, macro_rules 宏属于声明式宏,跟C一样有点类似,遇到了进行匹配替换。声明式宏相比于另外一种过程宏相对简单的一些(表达式匹配替换,也不会很简单)。我们这里就直接上代码,大家有兴趣了解 macro_rules 宏的使用可参考官网:https://doc.rust-lang.org/rust-by-example/macros.html,后续有需要再新写一篇文章专门介绍这个宏。
跟上面 sbi.rs 一样,我们需要在 src/main.rs 中引用 console.rs 使得编译起能够进行编译。但是这里还有一点区别的是,console.rs 中使用 macro_export 导出了宏,我们也需要在 src/main.rs 声明要使用这个模块的宏,代码如下:
#![no_std]
#![no_main]
#![feature(asm_const)]
?
#[macro_use]
mod console;
mod sbi;
?
println! 宏的代码都写完了,我们在 rust_entry 中使用看一下效果吧。
#[no_mangle]
unsafe extern "C" fn rust_entry(_cpu_id: usize, _dtb: usize) {
// 导入 linker.ld 中定义的符号,最终输出的值可以在 target/output.map 文件中查看
extern "C" { fn sbss(); fn ebss(); }
/* 初始化 bss 段内存 */
core::slice::from_raw_parts_mut(sbss as *mut u8, ebss as usize - sbss as usize).fill(0);
println!("+++++++++++++++++++++++++++++++++++++++++++++++++\n\n");
println!(" Hello myos !!!\n\n");
println!("+++++++++++++++++++++++++++++++++++++++++++++++++\n\n");
sbi::shutdown(false);
}
为了方便调试,我们把编译运行的命令写到脚本 run.sh 中,只需执行这个命令即可看到我们成功输出打印了。
本项目源码路径:https://gitee.com/rust4fun/myos/tree/master/ch1-hello
本文暂时没有评论,来添加一个吧(●'◡'●)