第二节 SBI 功能调用

到目前为止,我们还看不到内核的输出信息,只能通过查看 qemu 跟踪日志,确认工作成果。现在是实现打印输出的时候了!

有两个办法可以让内核支持 console,一是通过管理 Uart 串口设备进行输出,二是直接调用 OpenSBI 提供的功能。前一个方式需要自己实现驱动,但目前我们连最基础的内存管理都未能完成,缺乏实现驱动的条件;所以决定采用第二个办法。

前面一章提到,OpenSBI 提供了一系列功能调用,可以通过调用号去请求 SBI 为我们完成部分工作。查阅 OpenSBI 文档,发现功能调用 console_putchar 具有打印输出一个字符的能力,正可以作为输出功能的基础。然后从 crate.io 中,我们找到了 sbi-rt 这个现成的库,它封装了对 sbi 功能调用的各种方法。现在就使用它来实现 console 模块。

// axorigin/src/console.rs
pub fn putchar(c: u8) {
    #[allow(deprecated)]
    sbi_rt::legacy::console_putchar(c as usize);
}

pub fn write_bytes(bytes: &[u8]) {
    for c in bytes {
        putchar(*c);
    }
}

在 Cargo.toml 中,引入对 sbi-rt 的依赖。

// axorigin/Cargo.toml
[dependencies]
sbi-rt = { version = "0.0.2", features = ["legacy"] }

mod console 引入 main.rs,把 wfi 指令替换为调用字符串输出。

mod console;

unsafe extern "C" fn rust_entry(_hartid: usize, _dtb: usize) {
   console::write_bytes(b"\nHello, ArceOS!\n");
}

通过 make run 运行,终于在屏幕上看到了输出 “Hello, ArceOS!”。

仅仅支持输出字符串不方便,下面来进一步支持类似 print 的变参输出方式。

首先定义一个代表 Console 的全局变量,实现 Write 这个 Trait:

// axorigin/src/console.rs
use core::fmt::{Write, Error};

struct Console;

impl Write for Console {
    fn write_str(&mut self, s: &str) -> Result<(), Error> {
        write_bytes(s.as_bytes());
        Ok(())
    }
}

pub fn __print_impl(args: core::fmt::Arguments) {
    Console.write_fmt(args).unwrap();
}

当我们对 Console 调用 write_fmt(...) 时,Rust 库会帮助处理变参,我们只需要实现 write_str(...) 这个 Trait 的方法就可以了。

然后,提供两个宏定义 ax_print! 和 ax_println! 封装输出功能:

// axorigin/src/console.rs
#[macro_export]
macro_rules! ax_print {
    ($($arg:tt)*) => {
        $crate::console::__print_impl(format_args!($($arg)*));
    }
}

#[macro_export]
macro_rules! ax_println {
    () => { $crate::print!("\n") };
    ($($arg:tt)*) => {
        $crate::console::__print_impl(format_args!("{}\n", format_args!($($arg)*)));
    }
}

最后,在 axorigin 中调用 ax_println! 宏测试一下,删除 console::write_bytes(...) 那一行,替换为 ax_println! 输出:

// axorigin/src/main.rs
unsafe extern "C" fn rust_entry(_hartid: usize, _dtb: usize) {
    let version = 1;
    ax_println!("\nHello, ArceOS!");
    ax_println!("version: [{}]", version);
}

通过 make run 编译运行,看到作为变参的 version 信息输出。显示如下:

Hello, ArceOS! version: [1]

目前只能支持 Rust 基本类型,等下一章支持动态内存分配之后,我们再进一步测试对复杂集合类型的输出功能。