使用Rust从零写操作系统 (3) —— 格式化输出

释放双眼,带上耳机,听听看~!

本系列博客系转载,出处: 知乎专栏:从零开始写 OS

所有代码都在:https://github.com/LearningOS/rcore_step_by_step


1
2
3
1本小节代码对应 commit :1b493d3bcaca2d41123adcaaa7174daaa26852a6
2
3

概要

通过上一章,我们已经可以在屏幕上打印简单的字符串了。但是这并不足够,本章我们将实现 rust 中最经典的宏: println! ,以便于后续的调试输出。这需要我们对 rust 的一些特性有一定的了解:

  1. 宏的使用。
  2. trait 的特性。

打印字符和字符串

在一个文件内实现过多的功能会使得文件过于冗长,不易阅读与维护,所以我们(在 main.rs 的同级目录下)创建一个新的文件用于管理 io 。现在我们来为 io 实现两个最简单的函数:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1// in io.rs
2
3use bbl::sbi;
4
5pub fn putchar(ch: char) {
6    sbi::console_putchar(ch as u8 as usize);
7}
8
9pub fn puts(s: &str) {
10    for ch in s.chars() {
11        putchar(ch);
12    }
13}
14
15

从函数名可以看出,这两个函数的功能分别是打印一个字符和 打印 str 。

在 main.rs 中引入 io 库:


1
2
3
1pub mod io;
2
3

修改 rust_main 为:


1
2
3
4
5
6
7
1#[no_mangle]
2pub extern "C" fn rust_main() -> ! {
3    io::puts("666666");
4    loop {}
5}
6
7

编译运行,屏幕成功输出了 666666 !

实现 println!

很显然,要完成 println! , print! 是必不可少的。那我们就先来实现print! :


1
2
3
4
5
6
7
8
9
10
1// in io.rs
2
3#[macro_export]
4macro_rules! print {
5    ($($arg:tt)*) => ({
6        $crate::io::_print(format_args!($($arg)*));
7    });
8}
9
10

#[macro_export] 宏使得外部的库也可以使用这个宏。 format_args! 宏可以将 print(…) 内的部分转换为 fmt::Arguments 类型,用以后续打印。这里我们用到了一个还未实现的函数: _print 。他的实现方法十分神奇,现在让我们先来做一些准备工作:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
1// in io.rs
2
3use core::fmt::{self, Write};
4
5struct StdOut;
6
7impl fmt::Write for StdOut {
8    fn write_str(&mut self, s: &str) -> fmt::Result {
9        puts(s);
10        Ok(())
11    }
12}
13
14

我们引入了 fmt::Write 特征(trait) ,创建了一个新的类: StdOut 。这里我们为 StdOut 实现了他的 trait 。接下来,就让我们来实现 _print吧:


1
2
3
4
5
6
7
1// in io.rs
2
3pub fn _print(args: fmt::Arguments) {
4    StdOut.write_fmt(args).unwrap();
5}
6
7

细心的你可能已经发现, write_fmt 和我们上一步实现的函数并不一样。这不是笔误,反而是前面所提到的 神奇之处 。由于我们实现了 write_str ,核心库会帮我们自动实现 write_fmt。如果你想进一步了解这部分内容,可以阅读 rust 官方文档中 core::fmt::Write 部分 和 rust 官方教程中 Traits 部分 。

完成了上述所有步骤后,我们的 io.rs 应该是这个样子的:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1use bbl::sbi;
2use core::fmt::{self, Write};
3
4pub fn putchar(ch: char) {
5    sbi::console_putchar(ch as u8 as usize);
6}
7
8pub fn puts(s: &str) {
9    for ch in s.chars() {
10        putchar(ch);
11    }
12}
13
14#[macro_export]
15macro_rules! print {
16    ($($arg:tt)*) => ({
17        $crate::io::_print(format_args!($($arg)*));
18    });
19}
20
21pub fn _print(args: fmt::Arguments) {
22    StdOut.write_fmt(args).unwrap();
23}
24
25struct StdOut;
26
27impl fmt::Write for StdOut {
28    fn write_str(&mut self, s: &str) -> fmt::Result {
29        puts(s);
30        Ok(())
31    }
32}
33
34

为了在 main.rs中使用 io.rs 中的宏,我们需要在 pub mod io 的上方添加属性:


1
2
3
4
1#[macro_use]
2pub mod io;
3
4

然后修改 rust_main:


1
2
3
4
5
6
7
8
9
1#[no_mangle]
2pub extern "C" fn rust_main() -> ! {
3    let a = "Hello";
4    let b = "World";
5    print!("{}, {}!", a, b);
6    loop {}
7}
8
9

编译运行!可以看到,我们的 os如预期一样,输出了 Hello World! 。在高兴之前,先让我们完成最后一步,编写 println!:


1
2
3
4
5
6
7
1#[macro_export]
2macro_rules! println {
3    () => ($crate::print!("\n"));
4    ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
5}
6
7

现在我们可以让 println! 进行一些更高难度的工作,打印 panic 信息。首先,修改 panic 函数为:


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

然后将 rust_main中的无限循环替换为:


1
2
3
1panic!("End of rust_main");
2
3

完成这些后,我们的 main.rs 应该长这样:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1#![no_std] // don't link the Rust standard library
2#![no_main] // disable all Rust-level entry points
3#![feature(global_asm)]
4
5#[macro_use]
6pub mod io;
7
8use core::panic::PanicInfo;
9
10global_asm!(include_str!("boot/entry.asm"));
11
12#[panic_handler]
13fn panic(_info: &PanicInfo) -> ! {
14    println!("{}", _info);
15    loop {}
16}
17
18#[no_mangle]
19pub extern "C" fn rust_main() -> ! {
20    let a = "Hello";
21    let b = "World";
22    println!("{}, {}!", a, b);
23    panic!("End of rust_main");
24}
25
26#[no_mangle]
27pub extern fn abort() {
28    panic!("abort!");
29}
30
31

再次编译运行,程序输出:


1
2
3
4
1Hello, World!
2panicked at 'End of rust_main', src/main.rs:25:5
3
4

预告

当 CPU 访问无效的寄存器地址,或进行除零操作,或者进行 系统调用 时,会产生中断。下一章,我们将实现一个简单的中断机制对这些情况进行处理。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全技术

使用Rust开发编译系统(C以及Rust编译的过程)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索