[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
[rCore学习笔记 010]基于 SBI 服务完成输出和关机的更多相关文章
- 多线程编程学习笔记——异步调用WCF服务
接上文 多线程编程学习笔记——使用异步IO 接上文 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端 接上文 多线程编程学习笔记——异步操作数据库 本示例描述了如何创建一个WCF服务,并宿主 ...
- Binder学习笔记(九)—— 服务端如何响应Test()请求 ?
从服务端代码出发,TestServer.cpp int main() { sp < ProcessState > proc(ProcessState::self()); sp < I ...
- Python学习笔记010——匿名函数lambda
1 语法 my_lambda = lambda arg1, arg2 : arg1 + arg2 + 1 arg1.arg2:参数 arg1 + arg2 + 1 :表达式 2 描述 匿名函数不需要r ...
- 10月9日Android学习笔记:活动与服务之间的通信
最近在照着<第一行代码>这本书来学安卓,顺便记下笔记.主要的内容是Android中服务的第二种启动方式,通过活动绑定服务来启动服务,实现活动与服务之间的通信. 一. 首先创建一个服务类 p ...
- Nagios学习笔记四:基于NRPE监控远程Linux主机
1.NRPE简介 Nagios监控远程主机的方法有多种,其方式包括SNMP.NRPE.SSH和NCSA等.这里介绍其通过NRPE监控远程Linux主机的方式. NRPE(Nagios Remote P ...
- 【ALB学习笔记】基于事件触发方式的串行通信接口数据接收案例
基于事件触发方式的串行通信接口数据接收案例 广东职业技术学院 欧浩源 一.案例背景 之前写过一篇<基于多线程方式的串行通信接口数据接收案例>的博文,讨论了采用轮询方式接收串口数据的情况. ...
- Netty4 学习笔记之一:客户端与服务端通信 demo
前言 因为以前在项目中使用过Mina框架,感受到了该框架的强大之处.于是在业余时间也学习了一下Netty.因为Netty的主要版本是Netty3和Netty4(Netty5已经被取消了),所以我就直接 ...
- Netty4 学习笔记之四: Netty HTTP服务的实现
前言 目前主流的JAVA web 的HTTP服务主要是 springMVC和Struts2,更早的有JSP/servlet. 在学习Netty的时候,发现Netty 也可以作HTTP服务,于是便将此整 ...
- Docker学习笔记 - Docker客户端和服务端
学习内容: Docker客户端和服务端的通讯方式:client和自定义程序 Docker客户端和服务端的连接方式:socket 演示Docker客户端和服务端之间用remote-api通讯:nc ...
- Dubbo学习笔记2:Dubbo服务提供端与消费端应用的搭建
Demo结构介绍 Demo使用Maven聚合功能,里面有三个模块,目录如下: 其中Consumer模块为服务消费者,里面TestConsumer和consumer.xml组成了基于Spring配置方式 ...
随机推荐
- pod(三):pod的管理
目录 一.系统环境 二.前言 三.pod的管理 3.1 环境介绍 3.2 管理pod 一.系统环境 服务器版本 docker软件版本 CPU架构 CentOS Linux release 7.4.17 ...
- Vue cli之项目打包
在项目根目录中执行如下命令: npm run build 注:Vue脚手架打包的项目必须在服务器上运行,不能直接双击运行: 在打包之后项目中出现 dist 目录,dist 目录就是 Vue脚手架项目的 ...
- C# WinForm控件及其子控件转成图片(支持带滚动条的长截图)
概述(Overview) 参考了网上的分析,感觉都不太理想:1.一个控件内如果包含多个子控件时没有考虑顺序问题:2.超出控件可显示区域时不能长截图,有滚动条会多余截取了滚动条.这个随笔旨在解决这个问题 ...
- K-D Tree 总结
Luogu题单 前置芝士 \(K-D\;Tree\) 例题略解 P2479 [SDOI2010]捉迷藏 大概就是 K-D Tree 的板子题了吧,网上的打法都不太友好,参考了 fengwu 的打法. ...
- tab切换之循环遍历
<style> *{ margin: 0; padding:0; } ul,ol,li{ ...
- 夜莺项目发布 v6.1.0 版本,增强可观测性数据串联
大家好,夜莺项目发布 v6.1.0 版本,这是一个中版本迭代,不止是 bugfix 了,而是引入了既有功能的增强.具体增强了什么功能,下面一一介绍. 1. 增强可观测性数据串联 从 v6.1.0 开始 ...
- 苹果手机 ios 系统如何升级为鸿蒙HarmonyOS
用苹果手机的朋友们注意了 根据最新的可靠消息,苹果手机升级为HarmonyOS,教程如下: 第一步 手机电量充足的情况下,将苹果手机连接至WIFI无线网络. 第二步 ...... [下一页]
- HTML5画布-小球碰撞
Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` HTML5画布-小球碰撞 日期:2017-7-18 阿珏 ...
- ssh_exchange_identification: Connection closed by remote host 错误解决方案
问题 今天登陆服务器时候,ssh 后返回 ssh_exchange_identification: Connection closed by remote host 错误,重试了几次,会有一定概率失败 ...
- centos8使用nmcli实现bond
#添加bonding接口 nmcli con add type bond con-name bond0 ifname bond0 mode active-backup ipv4.method manu ...