[rCore学习笔记 010]基于 SBI 服务完成输出和关机

RustSBI的两个职责

  1. 它会在计算机启动时进行它所负责的环境初始化工作,并将计算机控制权移交给内核
  2. 在内核运行时响应内核的请求为内核提供服务

这里用不太确切的话表述一下,RustSBI作为介于内核和硬件之间的软件,要完成输出和关机,思路是内核需要调用RustSBI进行对硬件的控制的.
对于怎么对硬件进行抽象使之可以完成RustSBI规定的服务,则有专门的文档规定,详可看官方文档.

内核调用RustSBI提供的服务

可以从之前的操作中得知我们是使用rustsbi-qemu.bin这个二进制固件烧入系统中使得RustSBI服务正常工作的,可以看到RustSBI没有直接和编译出os.bin文件链接在一起,因此是不能使用函数调用的方式来调用RustSBI的服务的.
RustSBI 开源社区的 sbi_rt封装了调用 SBI 服务的接口,我们直接使用即可.

为项目添加sbi_rt的服务

os/Cargo.toml中添加依赖就行, 即在[dependencies]下一行添加上sbi_rt的信息 .

// os/Cargo.toml
[dependencies]
sbi-rt = { version = "0.0.2", features = ["legacy"] }

在官方文档里这样描述为什么要加上这个选项:
这里需要带上 legacy 的 feature,因为我们需要用到的串口读写接口都属于 SBI 的遗留接口。

detail

这里的features看起来似乎比较难以理解,它实际上是条件编译的一个选项,是项目中自定义的,详细可以看这里条件编译 Features - Rust语言圣经(Rust Course)..

编写内核和RustSBI通信的接口

os/src下创建文件sbi.rs:

// os/src/sbi.rs
pub fn console_putchar(c: usize) {
    #[allow(deprecated)]
    sbi_rt::legacy::console_putchar(c);
}

detail

  • #[allow(deprecated)]: 这是一个属性(attribute)用来告诉Rust编译器允许使用已经被标注为“废弃”(deprecated)的代码。这意味着sbirt::legacy::console_putchar函数可能已经被标记为不建议使用或计划在未来移除,但由于某些原因(比如暂时没有更好的替代方案或出于兼容性考虑),代码中仍然需要使用它。
  • sbi_rt::legacy::console_putchar(c): 这行代码是实际执行系统调用的地方,用来向控制台输出一个字符。sbi_rt是依赖库,提供了与RISC-V SBI相关的功能实现。“legacy”部分再次强调了这是使用了某种旧有的或非标准的接口来完成任务。参数c是一个无符号整数(usize),代表了要输出的字符的编码。
  • pub关键字:
    • pub是public的缩写,是一个访问修饰符。当一个函数、结构体、枚举、模块、trait或者结构体、枚举中的字段被标记为pub时,表明它是公开的(public),即可以在定义它的模块之外的地方被访问到。如果不使用pub,则默认是私有的(private),只能在定义它的模块内部访问。在你的代码示例中,pub fn console_putchar(c: usize)声明了一个公共函数,意味着其他模块可以调用这个函数来向控制台输出字符。
  • usize类型:
    • usize是一种整数类型,它的大小依赖于目标平台的指针宽度。在32位系统上,usize通常是32位(4字节);在64位系统上,则是64位(8字节)。usize主要用于与内存地址相关的操作,比如数组索引、分配的内存大小等。它保证能够存储平台上任何可能的内存地址,因此非常适合用来表示大小、计数或任何需要与内存大小直接关联的数值。在你的代码中,console_putchar函数接受一个usize类型的参数c,这意味着它可以处理任何有效的字符编码,因为usize足以覆盖任何可能的字符编码值范围。

main函数调用sbi模块

Rust引入模块

这里使用mod引入模块,在main.rs的第一行加入mod sbi;

dtail

下边是 同义千问 给出的教程,另外,很多detail模块也都是生成式AI对代码或者指令的解读,这很有助于减少我们去搜索的时间,但是同时生成的回答也是缺省的,因此也不能选择全信.

在Rust中,引入模块是组织和重用代码的关键机制。Rust的模块系统允许你将代码分割成逻辑上独立的部分,有助于提高代码的可读性、可维护性和重用性。下面是几种引入模块的基本方式:

1. 使用mod关键字定义模块

在Rust中,你可以使用mod关键字来定义一个新的模块。模块可以是文件也可以是代码块。

  • 文件作为模块:在项目目录中创建一个新的.rs文件,例如创建一个名为utils.rs的文件,该文件自动成为一个名为utils的模块。在主文件(如main.rs)或其他模块中,可以使用mod utils;来引入它。

  • 代码块作为模块:在同一个文件内部,可以直接定义一个模块代码块。

    mod my_module {
        // 这里是模块的代码
    }
    

2. 使用use关键字引入路径

一旦定义了模块,就可以使用use关键字来引入模块中的项(如函数、结构体、枚举等),使其在当前作用域可用,无需全路径引用。

  • 引入模块中的项:

    use my_module::my_function;
    

    这样一来,就可以直接调用my_function()而不需要指定模块名。

  • 引入整个模块(作为命名空间):

    use my_module::*; // 注意这会引入模块中所有的公开项,谨慎使用以避免名称冲突
    

    这样做后,访问模块内的项时需要使用模块名作为前缀,如my_module::my_function(),除非项已经被直接引入。

  • 重命名引入的项:

    use my_module::my_function as my_alias;
    

    这样,my_function就被重命名为my_alias,可以使用my_alias()来调用。

3. 层次化的模块结构

Rust支持多层次的模块嵌套。你可以在一个模块内部定义另一个模块,通过.来构建路径,如use crate::outer_mod::inner_mod::function_name;

4. cratesuper

  • crate: 引用当前crate的根路径。
  • super: 引用当前模块的父模块。

示例

假设项目结构如下:

project/
│   main.rs
│
└───utils/
    │   helpers.rs

main.rs:

mod utils;

fn main() {
    utils::helpers::say_hello(); // 直接使用全路径
    use utils::helpers::say_hello; // 使用use引入后
    say_hello(); // 简化调用
}

utils/helpers.rs:

pub fn say_hello() {
    println!("Hello, Rust!");
}

在这个例子中,main.rs通过mod utils;引入了utils模块,而utils模块内部的helpers模块通过文件方式定义,并暴露了say_hello函数。通过use关键字,我们可以在main函数中直接调用helpers模块中的函数。

调用模块中函数

main.rs进行编程

// os/src/main.rs
#![no_std]
#![no_main]

mod sbi;
mod lang_items;
  
use core::arch::global_asm;
global_asm!(include_str!("entry.asm"));

  
#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();
    sbi::console_putchar('O' as usize);
    sbi::console_putchar('K' as usize);
    loop {}
}

fn clear_bss() {
    extern "C" {
        fn sbss();
        fn ebss();
    }
    (sbss as usize..ebss as usize).for_each(|a| {
        unsafe { (a as *mut u8).write_volatile(0) }
    });
}

编译

cargo build --release

清除元数据

rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/os -O binary target/riscv64gc-unknown-none-elf/release/os.bin

启动QEMU

^fb8fca

qemu-system-riscv64 \
    -machine virt \
    -nographic \
    -bios ../bootloader/rustsbi-qemu.bin \
    -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000 \
    -s -S

打开另一个终端GDB监听

riscv64-unknown-elf-gdb \
    -ex 'file target/riscv64gc-unknown-none-elf/release/os' \
    -ex 'set arch riscv:rv64' \
    -ex 'target remote localhost:1234'

GDB调试

由于启用了调试模式,因此需要一个指令让它运行下去,没有断点,那么c运行到断点即为我们需要的一直运行下去的指令.

c

log

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
OK

不使用GDB直接运行

这里初学的时候因为太形而上了,使用了刻板的指令,我们可以直接去掉-s -S这一行,直接运行

qemu-system-riscv64 \
    -machine virt \
    -nographic \
    -bios ../bootloader/rustsbi-qemu.bin \
    -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000 

实现关机

rustsbi.rs末尾 增加这个函数:

// os/src/sbi.rs
pub fn shutdown(failure: bool)->!
{

    use sbi_rt::{system_reset,NoReason,Shutdown,SystemFailure};
    if !failure
    {
        system_reset(Shutdown, NoReason);
    }
    else
    {
        system_reset(Shutdown, SystemFailure);    
    }
    unreachable!()
}

detail

这里讲两个我个人比较不熟悉的地方:

  1. use使用这个关键字可以引入代码块中的内容,那么use sbi_rt::{system_reset,NoReason,Shutdown,SystemFailure};实际上就是引入了sbi_rt里边的system_reset这个函数和NoReason,Shutdown,SystemFailure,这几个宏.
  2. ->!并不是返回值为空,rust里返回为空一般是不写return或者不写一个无; 的表达式就可以完成了,这时候返回的是()即一个空的元组.!意思是函数不收敛,也就是永远都不用返回了,比如单片机编程里的主循环函数还有这个shutdown,都关机了自然不需要返回值了.
  3. unreachable!()是一个表述接下来的代码是不可达的,也就是说一般而言是没有办法到达这个地方的, 代码执行到这个地方说明发生了没有预期到的情况,如果执行流到达这里,Rust编译器会认为这是一个未定义行为(UB),在开发阶段这可以帮助捕获逻辑错误.

调用关机

这里只贴出我修改了的rust_main入口函数:

pub fn rust_main() -> ! {
    clear_bss();
    sbi::console_putchar('O' as usize);
    sbi::console_putchar('K' as usize);
    sbi::shutdown(false);
    //loop {}
}

编译

cargo build --release

清除元数据

rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/os -O binary target/riscv64gc-unknown-none-elf/release/os.bin

QEMU运行

qemu-system-riscv64 \
    -machine virt \
    -nographic \
    -bios ../bootloader/rustsbi-qemu.bin \
    -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000 

log

这个log和之前的是一样的,但是可以看到qemu退出了,因为关机了.

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
OKwinddevil@winddevil-virtual-machine:~/workspace/os$ 

实现格式化输出

说实话有点类似于STM32在有了使用串口写的putchar之后实现printf的场景.这就是抽象的魅力,其实有点类似于自己实现了一个HAL层.

创建console子模块

os/src目录下创建console.rs.

// os/src/main.rs

#[macro_use]
mod console;

// os/src/console.rs
use crate::sbi::console_putchar;
use core::fmt::{self, Write};

struct Stdout;

impl Write for Stdout {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            console_putchar(c as usize);
        }
        Ok(())
    }
}

pub fn print(args: fmt::Arguments) {
    Stdout.write_fmt(args).unwrap();
}

#[macro_export]
macro_rules! print {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print(format_args!($fmt $(, $($arg)+)?));
    }
}
  
#[macro_export]
macro_rules! println {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
    }
}

detail

这段代码我的评价是:

包含宏编程和方法两部分,这两部分是RustC/C++非常不同的地方.

但是我们还是能够慢慢地解析这段代码.

crate

包(Crate):一个由多个模块组成的树形结构,可以作为三方库进行分发,也可以生成可执行文件进行运行.

当你在代码中使用 crate:: 开头的路径时,你是在从当前crate的根级别开始导入一个项。这与直接从外部crate或者库中导入(比如使用 extern crate 或直接指定外部crate的名字)是不同的。

这里use crate::sbi::console_putchar;其实是在当前编译的源码里选择了sbi这个crate进行导入.

Rust的Method

首先是structimpl,也就是rust的数据结构和其附属的方法的定义方式,由于rust让令人烦的object滚蛋了,因此它的数据结构和对应方法的实现方法是很奇怪的.

参看下图的两边,比如C++JAVA,其实现数据结构和其对应处理方法的方式是定义Class,而Rust的实现方式是分开,数据结构就放在struct里边,其对应的方法就放在impl里边.

这两句代码恰恰就体现了这一点.

struct Stdout;
impl Write for Stdout

Trait特质

可以理解为C++里边的virtual关键字和JAVA里边的interface关键字和abstract抽象类抽象函数.每个特质有一些 待实现 的函数,也有一些已经 默认实现好 的函数,这些函数可以调用 待实现 的函数,从而达到复用的效果.

use core::fmt::{self, Write};

这里引入了Write这个特质,而如下代码则是为Stdout实现这个特质,实现方法就是把它的函数给实现了:

impl Write for Stdout {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            console_putchar(c as usize);
        }
        Ok(())
    }
}

实现一个对外的print函数

这里借用了Stdout里刚刚实现的Write特质,虽然我们没有实现write_fmt方法,但是我们实现了write_str方法,给Stdout实现了Write的特质,因此可以调用Write里一些已经实现好的方法.

pub fn print(args: fmt::Arguments) {
    Stdout.write_fmt(args).unwrap();
}

使用宏进行匹配

Rust的宏也是会展开的,但是同时它是进行 匹配 的.
这里先提一嘴#[macro_export]代表这个宏可以被其它模块引用.

macro_rules! print为例:

  1. macro_rules! print { ... }: 定义了一个名为print的宏。macro_rules!是Rust中定义宏的关键字。
  2. ($fmt: literal $(, $($arg: tt)+)?): 定义了宏的输入模式。这里有两个部分:
    • $fmt: literal:表示宏的第一个参数必须是一个字符串字面量(literal),这将作为格式化字符串。
    • $(, $($arg: tt)+)?:这部分是可选的,表示可以有零个或多个额外的参数。$arg: tt表示这些参数可以是任何token tree(tt),即宏可以接受任意类型的参数(如表达式、标识符等),而不仅仅是字面量或标识符。这里的逗号和括号构造了一个递归的模式,允许接收任意数量的参数。
      • $( ... )?:这里的括号加上问号表示该模式是可选的,可以出现零次或一次。这意味着宏调用时,除了第一个必需的格式化字符串外,后面的参数列表是可以省略的。
      • , $($arg: tt)+:这部分定义了当有额外参数时,它们是如何被解析的。
        • ,:首先期待一个逗号,这是因为宏参数之间通常由逗号分隔。
        • $($arg: tt)+:这是个重复模式,匹配一个或多个token tree(tt)。tt是token tree的缩写,是Rust宏系统中的基本单位,可以是任何有效的Rust语法元素,比如标识符、操作符、字面量、括号表达式等。这意味着宏可以接受任意类型的后续参数,不限于特定类型,给予了宏极高的灵活性。
          • $: 表示这是一个宏参数占位符,将会被实际的token序列替换。
          • $arg: 是宏参数的名称,用来引用捕获到的一系列token。在实际应用中,你可以选择任何合法的变量名来代替arg
          • tt: 指的是token tree。在Rust的宏系统中,token是最小的语法单元,包括关键字、标识符、字面量、运算符、括号等。Token trees则是这些token组成的树状结构,可以是单个token,也可以是由花括号 {}、方括号 [] 或圆括号 () 包围的token序列,甚至是嵌套的token树结构。
          • $($arg: tt)+: 这是一个重复匹配器,匹配一个或多个连续的token tree。加号 (+) 表示前面的模式必须至少出现一次,但可以重复多次。这意味着宏可以捕获一个序列的token,直到没有更多的token与tt模式匹配为止。
  3. $crate::console::print(format_args!($fmt $(, $($arg)+)?));: 宏展开的代码模板部分。
    • $crate::console::print(...);:调用console模块(假设是当前crate的一部分或者是已经导入的)中的print函数来输出内容。$crate是一个特殊的变量,代表当前crate的根路径,这样写可以确保即使在重命名crate的情况下代码也能正确工作。
    • format_args!($fmt $(, $($arg)+)?);:调用了Rust标准库中的format_args!宏,它会根据提供的格式字符串$fmt和参数$($arg)+生成一个格式化的字符串。这个结果作为一个参数传递给了console::print函数,实现最终的打印功能。

macro_rules! println也用同样的方法理解即可.

尝试输出Hello World

这时候只需要在main.rs里引入console模块然后调用宏即可,如下代码是修改的部分而不是完整的文件内容:

// os/src/main.rs
#[macro_use]
mod console;
#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();
    println!("Hello World");
    shutdown(false)
    //loop {}
}

这里特别注意#[macro_use]代表指示编译器从指定的模块中导入所有的宏定义,这样就可以导入console中所有的宏了.

编译-去除元数据-运行

这里不多废话了,这里也已经用了很多次了:

cargo build --release
rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/os -O binary target/riscv64gc-unknown-none-elf/release/os.bin
qemu-system-riscv64 \
    -machine virt \
    -nographic \
    -bios ../bootloader/rustsbi-qemu.bin \
    -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000 

log

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
Hello World

处理致命错误

错误处理是编程的重要一环,它能够保证程序的可靠性和可用性,使得程序能够从容应对更多突发状况而不至于过早崩溃。

Rust的错误处理

不同于 C 的返回错误编号 errno 模型和 C++/Java 的 try-catch 异常捕获模型,Rust 将错误分为可恢复和不可恢复错误两大类。这里我们主要关心不可恢复错误。和 C++/Java 中一个异常被抛出后始终得不到处理一样,在 Rust 中遇到不可恢复错误,程序会直接报错退出。例如,使用 panic! 宏便会直接触发一个不可恢复错误并使程序退出。

之前添加的panic模块

lang_items里找到原来实现的panic函数,思考上一小节提到的panic!宏:

  1. 我们一定需要这个宏吗?为什么需要?
  2. 这个宏怎么实现,需要添加新的函数还是添加逻辑和匹配用panic函数来实现

先观察这个函数,这里一定要注意到这里有一个注解#[panic_handler]使得这个函数成为出现错误的时候的入口函数,那么理解了这一点就很好理解了,有点像HAL库里的HardFault_Handler,如果出现了致命错误,那么就会进入这个函数,那么STM32就会卡住,但是我们的STM32有看门狗可以及时恢复STM32的状态,但是对于我们的内核,仅仅实现一个loop{}就结束了吗?:

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

但是好像也没有什么好的办法可以使得系统恢复正常,这里按照官方文档的要求实现打印错误信息和关机:
借助前面实现的 println! 宏和 shutdown 函数,我们可以在 panic 函数中打印错误信息并关机

那么编写lang_item,这里贴出来的是完整的文件内容:

// os/src/lang_items.rs
use crate::{println, sbi::shutdown};
use core::panic::PanicInfo;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    if let Some(location) = info.location() {
        println!(
            "Panicked at {}:{} {}",
            location.file(),
            location.line(),
            info.message().unwrap()
        );
    } else {
        println!("Panicked: {}", info.message().unwrap());
    }
    shutdown(true)
}

这里完成的过程中遇到两个问题,实际上透露了作为一个前C/C++使用者对rust的不够理解:

  1. 使用#[macro_use]mod console;得到报错:unresolved module, can't find module file: lang_items/console.rs, or lang_items/console/mod.rs.
  2. 发现原本文档里给出的代码中info.message()报错cannot find macro println in this scope.

多花点时间总能解决的,

第一个问题

首先观察到第一个问题完整的报错:

unresolved module, can't find module file: lang_items/console.rs, or lang_items/console/mod.rsrust-analyzerE0583 file not found for module `console` to create the module `console`, create file "src/lang_items/console.rs" or "src/lang_items/console/mod.rs" if there is a `mod console` elsewhere in the crate already, import it with `use crate::...` insteadrustcClick for full compiler diagnostic

这里可以看到它似乎试图寻找一个lang_items的文件夹,这里就应该看关于mod 的使用,不知为何要如此,那么我们参考这个链接.
 src/main.rs 和 src/lib.rs 被称为包根(crate root),是由于这两个文件的内容形成了一个模块 crate,该模块位于包的树形结构(由模块组成的树形结构)的根部

mod console;实际上意为在该模块下寻找 /console.rs或者/console/mod.rs的内容填充进去,那么就很容易理解了,在main.rs中是可以被识别为crate的.
那么同样地,因为main.rs中声明了mod console;因此只需要从crate中引用这个宏就行了,使用use crate::println,作为子模块存在的公开宏就可以用了.

第二个问题

我们观察到这个问题的报错:

use of unstable library feature 'panic_info_message'  
see issue #66745 <https://github.com/rust-lang/rust/issues/66745> for more information

我们可以看到是使用了不稳定的库,

可以在main.rs中添加这句,这里注意crate-level attribute should be in the root module,这种注解只能写在main.rs:

#![feature(panic_info_message)]

为什么是message报错但是却要声明panic_info_message的,因为源码里是这么写得:

    #[must_use]
    #[unstable(feature = "panic_info_message", issue = "66745")]
    pub fn message(&self) -> PanicMessage<'_> {
        PanicMessage { message: self.message }
    }

lang_items.rs内容修改为:

//! The panic handler
use crate::sbi::shutdown;
use core::panic::PanicInfo;
use log::*;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    if let Some(location) = info.location() {
        error!(
            "[kernel] Panicked at {}:{} {}",
            location.file(),
            location.line(),
            info.message().as_str().unwrap()
        );
    } else {
        error!("[kernel] Panicked: {}", info.message().as_str().unwrap());
    }
    shutdown(true)
}
问题解决

问题是使用了旧版的工具链,查看当前使用的工具链,

rustup show

log:

Default host: x86_64-unknown-linux-gnu
rustup home:  /home/winddevil/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
nightly-2024-05-01-x86_64-unknown-linux-gnu
nightly-x86_64-unknown-linux-gnu (default)

installed targets for active toolchain
--------------------------------------

riscv64gc-unknown-none-elf
riscv64imac-unknown-none-elf
x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-x86_64-unknown-linux-gnu (default)
rustc 1.81.0-nightly (ba1d7f4a0 2024-06-29)

更改激活的工具链:

rustup default {name}

{name} 改为刚才 rustup show中显示的最新版本的工具链,查看当前使用的工具链,

rustup show

log:

Default host: x86_64-unknown-linux-gnu
rustup home:  /home/winddevil/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
nightly-2024-05-01-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

installed targets for active toolchain
--------------------------------------

riscv64gc-unknown-none-elf
x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-2024-05-01-x86_64-unknown-linux-gnu (default)
rustc 1.80.0-nightly (f705de596 2024-04-30)

修改main.rs

rust_main修改为如下所示:

#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();
    println!("Hello World");
    panic!("Shutdown machine!");
}

编译-去除元数据-运行

这里不多废话了,这里也已经用了很多次了:

cargo build --release
rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/os -O binary target/riscv64gc-unknown-none-elf/release/os.bin
qemu-system-riscv64 \
    -machine virt \
    -nographic \
    -bios ../bootloader/rustsbi-qemu.bin \
    -device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000 

log


[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
Hello World
Panicked at src/main.rs:21 

posted @ 2024-07-09 19:54  winddevil  阅读(138)  评论(0编辑  收藏  举报