本章代码对应 commit :329c65f867880986732987614d1135a6982cff84
概要
通过上一章,我们已经可以在屏幕上打印简单的字符串了。但是这并不足够,本章我们将实现 rust 中最经典的宏: println! ,以便于后续的调试输出。这需要我们对 rust 的一些特性有一定的了解:
简单打印字符和字符串
在一个文件内实现过多的功能会使得文件过于冗长,不易阅读与维护,所以我们(在 main.rs 的同级目录下)创建一个新的文件用于管理 io 。现在我们来为 io 实现两个最简单的函数:
Copy // in io.rs
use bbl :: sbi;
pub fn putchar (ch : char ) {
sbi :: console_putchar (ch as u8 as usize );
}
pub fn puts (s : & str ) {
for ch in s . chars () {
putchar (ch);
}
}
从函数名可以看出,这两个函数的功能分别是 打印一个字符 和 打印 str 。
在 main.rs 中引入 io 库:
修改 rust_main 为:
Copy #[no_mangle]
pub extern "C" fn rust_main () -> ! {
io :: puts ( "666666" );
loop {}
}
编译运行,屏幕成功输出了 666666 !
支持println!宏执行
很显然,要完成 println! , print! 是必不可少的。那我们就先来实现 print! :
Copy // in io.rs
#[macro_export]
macro_rules! print {
( $ ( $ arg : tt) * ) => ({
$crate:: io :: _print ( format_args! ( $ ( $ arg) * ));
});
}
#[macro_export]
宏使得外部的库也可以使用这个宏。 format_args! 宏可以将 print(...) 内的部分转换为 fmt::Arguments 类型,用以后续打印。这里我们用到了一个还未实现的函数: _print 。他的实现方法十分神奇,现在让我们先来做一些准备工作:
Copy // in io.rs
use core :: fmt :: {self, Write };
struct StdOut ;
impl fmt :: Write for StdOut {
fn write_str ( &mut self, s : & str ) -> fmt :: Result {
puts (s);
Ok (())
}
}
我们引入了 fmt::Write 特征(trait) ,创建了一个新的类: StdOut 。这里我们为 StdOut 实现了他的 trait 。接下来,就让我们来实现 _print 吧:
Copy // in io.rs
pub fn _print (args : fmt :: Arguments ) {
StdOut . write_fmt (args) . unwrap ();
}
细心的你可能已经发现, write_fmt 和我们上一步实现的函数并不一样。这不是笔误,反而是前面所提到的 神奇之处 。由于我们实现了 write_str ,核心库会帮我们自动实现 write_fmt 。如果你想进一步了解这部分内容,可以阅读 rust 官方文档中 core::fmt::Write 部分 和 rust 官方教程中 Traits 部分 。
完成了上述所有步骤后,我们的 io.rs 应该是这个样子的:
Copy use bbl :: sbi;
use core :: fmt :: {self, Write };
pub fn putchar (ch : char ) {
sbi :: console_putchar (ch as u8 as usize );
}
pub fn puts (s : & str ) {
for ch in s . chars () {
putchar (ch);
}
}
#[macro_export]
macro_rules! print {
( $ ( $ arg : tt) * ) => ({
$crate:: io :: _print ( format_args! ( $ ( $ arg) * ));
});
}
pub fn _print (args : fmt :: Arguments ) {
StdOut . write_fmt (args) . unwrap ();
}
struct StdOut ;
impl fmt :: Write for StdOut {
fn write_str ( &mut self, s : & str ) -> fmt :: Result {
puts (s);
Ok (())
}
}
为了在 main.rs 中使用 io.rs 中的宏,我们需要在 pub mod io
的上方添加属性:
Copy #[macro_use]
pub mod io;
然后修改 rust_main :
Copy #[no_mangle]
pub extern "C" fn rust_main () -> ! {
let a = "Hello" ;
let b = "World" ;
print! ( "{}, {}!" , a, b);
loop {}
}
编译运行!可以看到,我们的 os 如预期一样,输出了 Hello World! 。在高兴之前,先让我们完成最后一步,编写 println! :
Copy #[macro_export]
macro_rules! println {
() => ( $crate:: print! ( "\n" ));
( $ ( $ arg : tt) * ) => ( $crate:: print! ( "{}\n" , format_args! ( $ ( $ arg) * )));
}
现在我们可以让 println! 进行一些更高难度的工作,打印 panic 信息。首先,修改 panic 函数为:
Copy #[panic_handler]
fn panic (info : & PanicInfo ) -> ! {
println! ( "{}" , info);
loop {}
}
然后将 rust_main 中的无限循环替换为:
Copy panic! ( "End of rust_main" );
完成这些后,我们的 main.rs 应该长这样:
Copy #![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points
#![feature(global_asm)]
#[macro_use]
pub mod io;
use core :: panic :: PanicInfo ;
global_asm! ( include_str! ( "boot/entry.asm" ));
#[panic_handler]
fn panic (_info : & PanicInfo ) -> ! {
println! ( "{}" , _info);
loop {}
}
#[no_mangle]
pub extern "C" fn rust_main () -> ! {
let a = "Hello" ;
let b = "World" ;
println! ( "{}, {}!" , a, b);
panic! ( "End of rust_main" );
}
#[no_mangle]
pub extern fn abort () {
panic! ( "abort!" );
}
再次编译运行,程序输出:
Copy Hello, World!
panicked at 'End of rust_main', src/main.rs:25:5
预告
当 CPU 访问无效的寄存器地址,或进行除零操作,或者进行 系统调用 时,会产生中断。下一章,我们将实现一个简单的中断机制对这些情况进行处理。