[rCore学习笔记 010]基于 SBI 服务完成输出和关机
RustSBI的两个职责
- 它会在计算机启动时进行它所负责的环境初始化工作,并将计算机控制权移交给内核
- 在内核运行时响应内核的请求为内核提供服务
这里用不太确切的话表述一下,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. crate
和super
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
实现关机
用rust
在sbi.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
这里讲两个我个人比较不熟悉的地方:
use
使用这个关键字可以引入代码块中的内容,那么use sbi_rt::{system_reset,NoReason,Shutdown,SystemFailure};
实际上就是引入了sbi_rt
里边的system_reset
这个函数和NoReason,Shutdown,SystemFailure
,这几个宏.->!
并不是返回值为空,rust
里返回为空一般是不写return
或者不写一个无;
的表达式就可以完成了,这时候返回的是()
即一个空的元组.!
意思是函数不收敛,也就是永远都不用返回了,比如单片机编程里的主循环函数还有这个shutdown
,都关机了自然不需要返回值了.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
这段代码我的评价是:
包含宏编程和方法两部分,这两部分是Rust
和C/C++
非常不同的地方.
但是我们还是能够慢慢地解析这段代码.
crate
包(Crate):一个由多个模块组成的树形结构,可以作为三方库进行分发,也可以生成可执行文件进行运行.
当你在代码中使用 crate::
开头的路径时,你是在从当前crate的根级别开始导入一个项。这与直接从外部crate或者库中导入(比如使用 extern crate
或直接指定外部crate的名字)是不同的。
这里use crate::sbi::console_putchar;
其实是在当前编译的源码里选择了sbi
这个crate
进行导入.
Rust的Method
首先是struct
和impl
,也就是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
为例:
macro_rules! print { ... }
: 定义了一个名为print
的宏。macro_rules!
是Rust中定义宏的关键字。($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
模式匹配为止。
$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!
宏:
- 我们一定需要这个宏吗?为什么需要?
- 这个宏怎么实现,需要添加新的函数还是添加逻辑和匹配用
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
的不够理解:
- 使用
#[macro_use]
和mod console;
得到报错:unresolved module, can't find module file: lang_items/console.rs, or lang_items/console/mod.rs
. - 发现原本文档里给出的代码中
info.message()
报错cannot find macro
printlnin 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