实现格式化输出
只能使用 console_putchar 这种苍白无力的输出手段让人头皮发麻。如果我们能使用 print! 宏的话该有多好啊!于是我们就来实现自己的 print!宏!
我们将这一部分放在 src/io.rs 中,先用 console_putchar 实现两个基础函数:
1. // src/lib.rs
3. // 由于使用到了宏,需要进行设置
4. // 同时,这个 module 还必须放在其他 module 前
5. #[macro_use]
6. mod io;
8. // src/io.rs
10. use crate::sbi;
12. // 输出一个字符
13. pub fn putchar(ch: char) {
14. sbi::console_putchar(ch as u8 as usize);
15. }
17. // 输出一个字符串
18. pub fn puts(s: &str) {
19. for ch in s.chars() {
20. putchar(ch);
21. }
22. }
而关于格式化输出, 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! 宏的实现思路便为:
- 解析传入参数,转化为
format_args!可接受的输入(事实上原封不动就行了),并通过format_args!宏得到Arguments类; - 调用
write_fmt函数输出这个类;
而为了调用 write_fmt 函数,我们必须实现 write_str 函数,而它可用 puts 函数来实现。支持print!宏的代码片段如下:
1. // src/io.rs
3. use core::fmt::{ self, Write };
5. struct Stdout;
7. impl fmt::Write for Stdout {
8. fn write_str(&mut self, s: &str) -> fmt::Result {
9. puts(s);
10. Ok(())
11. }
12. }
14. pub fn _print(args: fmt::Arguments) {
15. Stdout.write_fmt(args).unwrap();
16. }
18. #[macro_export]
19. macro_rules! print {
20. ($($arg:tt)*) => ({
21. $crate::io::_print(format_args!($($arg)*));
22. });
23. }
25. #[macro_export]
26. macro_rules! println {
27. () => ($crate::print!("\n"));
28. ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
29. }
由于并不是重点就不在这里赘述宏的语法细节了(实际上我也没弄懂),总之我们实现了 print!, println! 两个宏,现在是时候看看效果了! 首先,我们在 panic 时也可以看看到底发生了什么事情了!
1. // src/lang_items.rs
3. #[panic_handler]
4. fn panic(info: &PanicInfo) -> ! {
5. println!("{}", info);
6. loop {}
7. }
其次,我们可以验证一下我们之前为内核分配的内存布局是否正确:
1. // src/init.rs
3. use crate::io;
4. use crate::sbi;
6. #[no_mangle]
7. pub extern "C" fn rust_main() -> ! {
8. extern "C" {
9. fn _start();
10. fn bootstacktop();
11. }
12. println!("_start vaddr = 0x{:x}", _start as usize);
13. println!("bootstacktop vaddr = 0x{:x}", bootstacktop as usize);
14. println!("hello world!");
15. panic!("you want to do nothing!");
16. loop {}
17. }
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 的位置了!这将大大有利于调试。
目前所有的代码可以在这里找到。
