计算机系统应用教程网站

网站首页 > 技术文章 正文

Rust 写操作系统之Hello world (三)

btikc 2024-09-03 11:22:59 技术文章 11 ℃ 0 评论

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

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表