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

[rCore学习笔记 010]基于 SBI 服务完成输出和关机的更多相关文章

  1. 多线程编程学习笔记——异步调用WCF服务

    接上文 多线程编程学习笔记——使用异步IO 接上文 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端 接上文 多线程编程学习笔记——异步操作数据库 本示例描述了如何创建一个WCF服务,并宿主 ...

  2. Binder学习笔记(九)—— 服务端如何响应Test()请求 ?

    从服务端代码出发,TestServer.cpp int main() { sp < ProcessState > proc(ProcessState::self()); sp < I ...

  3. Python学习笔记010——匿名函数lambda

    1 语法 my_lambda = lambda arg1, arg2 : arg1 + arg2 + 1 arg1.arg2:参数 arg1 + arg2 + 1 :表达式 2 描述 匿名函数不需要r ...

  4. 10月9日Android学习笔记:活动与服务之间的通信

    最近在照着<第一行代码>这本书来学安卓,顺便记下笔记.主要的内容是Android中服务的第二种启动方式,通过活动绑定服务来启动服务,实现活动与服务之间的通信. 一. 首先创建一个服务类 p ...

  5. Nagios学习笔记四:基于NRPE监控远程Linux主机

    1.NRPE简介 Nagios监控远程主机的方法有多种,其方式包括SNMP.NRPE.SSH和NCSA等.这里介绍其通过NRPE监控远程Linux主机的方式. NRPE(Nagios Remote P ...

  6. 【ALB学习笔记】基于事件触发方式的串行通信接口数据接收案例

    基于事件触发方式的串行通信接口数据接收案例 广东职业技术学院  欧浩源 一.案例背景 之前写过一篇<基于多线程方式的串行通信接口数据接收案例>的博文,讨论了采用轮询方式接收串口数据的情况. ...

  7. Netty4 学习笔记之一:客户端与服务端通信 demo

    前言 因为以前在项目中使用过Mina框架,感受到了该框架的强大之处.于是在业余时间也学习了一下Netty.因为Netty的主要版本是Netty3和Netty4(Netty5已经被取消了),所以我就直接 ...

  8. Netty4 学习笔记之四: Netty HTTP服务的实现

    前言 目前主流的JAVA web 的HTTP服务主要是 springMVC和Struts2,更早的有JSP/servlet. 在学习Netty的时候,发现Netty 也可以作HTTP服务,于是便将此整 ...

  9. Docker学习笔记 - Docker客户端和服务端

    学习内容: Docker客户端和服务端的通讯方式:client和自定义程序 Docker客户端和服务端的连接方式:socket 演示Docker客户端和服务端之间用remote-api通讯:nc   ...

  10. Dubbo学习笔记2:Dubbo服务提供端与消费端应用的搭建

    Demo结构介绍 Demo使用Maven聚合功能,里面有三个模块,目录如下: 其中Consumer模块为服务消费者,里面TestConsumer和consumer.xml组成了基于Spring配置方式 ...

随机推荐

  1. pod(三):pod的管理

    目录 一.系统环境 二.前言 三.pod的管理 3.1 环境介绍 3.2 管理pod 一.系统环境 服务器版本 docker软件版本 CPU架构 CentOS Linux release 7.4.17 ...

  2. Vue cli之项目打包

    在项目根目录中执行如下命令: npm run build 注:Vue脚手架打包的项目必须在服务器上运行,不能直接双击运行: 在打包之后项目中出现 dist 目录,dist 目录就是 Vue脚手架项目的 ...

  3. C# WinForm控件及其子控件转成图片(支持带滚动条的长截图)

    概述(Overview) 参考了网上的分析,感觉都不太理想:1.一个控件内如果包含多个子控件时没有考虑顺序问题:2.超出控件可显示区域时不能长截图,有滚动条会多余截取了滚动条.这个随笔旨在解决这个问题 ...

  4. K-D Tree 总结

    Luogu题单 前置芝士 \(K-D\;Tree\) 例题略解 P2479 [SDOI2010]捉迷藏 大概就是 K-D Tree 的板子题了吧,网上的打法都不太友好,参考了 fengwu 的打法. ...

  5. tab切换之循环遍历

     <style>         *{             margin: 0;             padding:0;         }         ul,ol,li{ ...

  6. 夜莺项目发布 v6.1.0 版本,增强可观测性数据串联

    大家好,夜莺项目发布 v6.1.0 版本,这是一个中版本迭代,不止是 bugfix 了,而是引入了既有功能的增强.具体增强了什么功能,下面一一介绍. 1. 增强可观测性数据串联 从 v6.1.0 开始 ...

  7. 苹果手机 ios 系统如何升级为鸿蒙HarmonyOS

    用苹果手机的朋友们注意了 根据最新的可靠消息,苹果手机升级为HarmonyOS,教程如下: 第一步 手机电量充足的情况下,将苹果手机连接至WIFI无线网络. 第二步 ...... [下一页]

  8. HTML5画布-小球碰撞

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` HTML5画布-小球碰撞 日期:2017-7-18 阿珏 ...

  9. ssh_exchange_identification: Connection closed by remote host 错误解决方案

    问题 今天登陆服务器时候,ssh 后返回 ssh_exchange_identification: Connection closed by remote host 错误,重试了几次,会有一定概率失败 ...

  10. centos8使用nmcli实现bond

    #添加bonding接口 nmcli con add type bond con-name bond0 ifname bond0 mode active-backup ipv4.method manu ...