我们先将 console_putchar 函数删掉,并将 rust_main 中调用 console_putchar 的部分也删除。

    随后将 rust_main 抽取到init.rs中:

    将语义项们抽取到lang_items.rs中:

    1. // src/lang_items.rs
    2. use core::panic::PanicInfo;
    3. #[panic_handler]
    4. fn panic(_: &PanicInfo) -> ! {
    5. loop {}
    6. }
    7. #[no_mangle]
    8. extern "C" fn abort() -> ! {
    9. panic!("abort!");
    10. }

    并在代表os cratelib.rs 中引用这两个子模块:

    1. // src/lib.rs
    2. #![no_std]
    3. #![feature(asm)]
    4. mod init;

    以及只需使用 os crate 的孤零零的 main.rs

    使用 OpenSBI 提供的服务

    OpenSBI 实际上不仅起到了 bootloader 的作用,还为我们提供了一些服务供我们在编写内核时使用。这层接口称为 SBI (Supervisor Binary Interface),是 S-Mode 的 kernel 和 M-Mode 执行环境之间的标准接口。

    我们查看 ,里面包含了一些以 C 函数格式给出的我们可以调用的接口。

    上一节中我们的 console_putchar 函数类似于调用下面的接口来实现的:

    1. void sbi_console_putchar(int ch)

    实际的过程是这样的:我们通过 ecall 发起系统调用。OpenSBI 会检察发起的系统调用的编号,如果编号在 0-8 之间,则进行处理,否则交由我们自己的中断处理程序处理(暂未实现)。

    执行 ecall 前需要指定系统调用的编号,传递参数。一般而言,

    封装 SBI 接口 - 图2

    为参数:

    1. // src/lib.rs
    2. mod sbi;

    对于参数比较少且是基本数据类型的时候,我们从左到右使用寄存器

    就可以完成参数的传递。(可参考 riscv calling convention

    然而,如这种情况一样,设置寄存器并执行汇编指令,这超出了 Rust 语言的描述能力。然而又与之前 global_asm! 大段插入汇编代码不同,我们要把 u8 类型的单个字符传给

    封装 SBI 接口 - 图4

    作为输入参数,这种情况较为强调 Rust 与汇编代码的交互。此时我们通常使用 内联汇编(inline assembly)

    输出部分,我们将结果保存到变量 ret 中,限制条件 {x10} 告诉编译器使用寄存器

    ,前面的 = 表明汇编代码会修改该寄存器并作为最后的返回值。一般情况下 的 constraint 部分前面都要加上 =

    输入部分,我们分别通过寄存器

    封装 SBI 接口 - 图6

    传入参数 arg0,arg1,arg2,which ,它们分别代表接口可能所需的三个输入参数(arg0,arg1,arg2),以及用来区分我们调用的是哪个接口的 SBI Extension ID(*which*) 。这里之所以提供三个输入参数是为了将所有接口囊括进去,对于某些接口有的输入参数是冗余的,比如sbi_console_putchar 由于只需一个输入参数,它就只关心寄存器

    的值。

    在 clobbered registers list 中,出现了一个 "memory" ,这用来告诉编译器汇编代码隐式的修改了在汇编代码中未曾出现的某些寄存器。所以,它也不能认为汇编代码中未出现的寄存器就会在内联汇编前后保持不变了。

    在 option 部分出现了 "volatile" ,我们可能在很多地方看到过这个单词。不过在内联汇编中,主要意思是告诉编译器,不要将内联汇编代码移动到别的地方去。我们知道,编译器通常会对翻译完的汇编代码进行优化,其中就包括对指令的位置进行调换。像这种情况,调换可能就会产生我们预期之外的结果。谨慎起见,我们针对内联汇编禁用这一优化。

    1. // src/sbi.rs
    2. pub fn console_putchar(ch: usize) {
    3. sbi_call(SBI_CONSOLE_PUTCHAR, ch, 0, 0);
    4. }
    5. pub fn console_getchar() -> usize {
    6. sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0)
    7. }
    8. ...
    9. const SBI_SET_TIMER: usize = 0;
    10. const SBI_CONSOLE_PUTCHAR: usize = 1;
    11. const SBI_CONSOLE_GETCHAR: usize = 2;
    12. const SBI_CLEAR_IPI: usize = 3;
    13. const SBI_SEND_IPI: usize = 4;
    14. const SBI_REMOTE_FENCE_I: usize = 5;
    15. const SBI_REMOTE_SFENCE_VMA: usize = 6;
    16. const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7;

    现在我们比较深入的理解了 console_putchar 到底是怎么一回事。下一节我们将使用 console_putchar 实现格式化输出,为后面的调试提供方便。