实现格式化输出

只能使用 console_putchar 这种苍白无力的输出手段让人头皮发麻。如果我们能使用 print! 宏的话该有多好啊!于是我们就来实现自己的 print!宏!

我们将这一部分放在 src/io.rs 中,先用 console_putchar 实现两个基础函数:

  1. // src/lib.rs
  2. // 由于使用到了宏,需要进行设置
  3. // 同时,这个 module 还必须放在其他 module 前
  4. #[macro_use]
  5. mod io;
  6. // src/io.rs
  7. use crate::sbi;
  8. // 输出一个字符
  9. pub fn putchar(ch: char) {
  10. sbi::console_putchar(ch as u8 as usize);
  11. }
  12. // 输出一个字符串
  13. pub fn puts(s: &str) {
  14. for ch in s.chars() {
  15. putchar(ch);
  16. }
  17. }

而关于格式化输出, rust 中提供了一个接口 core::fmt::Write ,你需要实现函数

  1. // required
  2. fn write_str(&mut self, s: &str) -> Result

随后你就可以调用如下函数(会进一步调用write_str 实现函数)来进行显示。

  1. // provided
  2. fn write_fmt(mut self: &mut Self, args: Arguments<'_>) -> Result

write_fmt 函数需要处理 Arguments 类封装的输出字符串。而我们已经有现成的 format_args! 宏,它可以将模式字符串+参数列表的输入转化为 Arguments 类!比如 format_args!("{} {}", 1, 2)

因此,我们的 print! 宏的实现思路便为:

  1. 解析传入参数,转化为 format_args! 可接受的输入(事实上原封不动就行了),并通过 format_args! 宏得到 Arguments 类;
  2. 调用 write_fmt 函数输出这个类;

而为了调用 write_fmt 函数,我们必须实现 write_str 函数,而它可用 puts 函数来实现。支持print!宏的代码片段如下:

  1. // src/io.rs
  2. use core::fmt::{ self, Write };
  3. struct Stdout;
  4. impl fmt::Write for Stdout {
  5. fn write_str(&mut self, s: &str) -> fmt::Result {
  6. puts(s);
  7. Ok(())
  8. }
  9. }
  10. pub fn _print(args: fmt::Arguments) {
  11. Stdout.write_fmt(args).unwrap();
  12. }
  13. #[macro_export]
  14. macro_rules! print {
  15. ($($arg:tt)*) => ({
  16. $crate::io::_print(format_args!($($arg)*));
  17. });
  18. }
  19. #[macro_export]
  20. macro_rules! println {
  21. () => ($crate::print!("\n"));
  22. ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
  23. }

由于并不是重点就不在这里赘述宏的语法细节了(实际上我也没弄懂),总之我们实现了 print!, println! 两个宏,现在是时候看看效果了! 首先,我们在 panic 时也可以看看到底发生了什么事情了!

  1. // src/lang_items.rs
  2. #[panic_handler]
  3. fn panic(info: &PanicInfo) -> ! {
  4. println!("{}", info);
  5. loop {}
  6. }

其次,我们可以验证一下我们之前为内核分配的内存布局是否正确:

  1. // src/init.rs
  2. use crate::io;
  3. use crate::sbi;
  4. #[no_mangle]
  5. pub extern "C" fn rust_main() -> ! {
  6. extern "C" {
  7. fn _start();
  8. fn bootstacktop();
  9. }
  10. println!("_start vaddr = 0x{:x}", _start as usize);
  11. println!("bootstacktop vaddr = 0x{:x}", bootstacktop as usize);
  12. println!("hello world!");
  13. panic!("you want to do nothing!");
  14. loop {}
  15. }

make run 一下,我们可以看到输出为:

格式化输出通过

  1. _start vaddr = 0x80200000
  2. bootstacktop vaddr = 0x80208000
  3. hello world!
  4. panicked at 'you want to do nothing!', src/init.rs:15:5

我们看到入口点的地址确实为我们安排的 0x80200000 ,同时栈的地址也与我们在内存布局中看到的一样。更重要的是,我们现在能看到内核 panic 的位置了!这将大大有利于调试。

目前所有的代码可以在这里找到。