第二节 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 基本类型,等下一章支持动态内存分配之后,我们再进一步测试对复杂集合类型的输出功能。