- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
本随笔是非常菜的菜鸡写的。如有问题请及时提出.
可以联系:1160712160@qq.com 。
GitHhub:https://github.com/WindDevil (目前啥也没有 。
了解了特权级机制,实际上如果要设计一个应用程序就需要保证它符合U模式的要求,不要去访问S模式下的功能,那么其实现要点是
具体实现的时候也要按照设计方法中提供的要点来进行设计
linker.ld
文件,设置好用户苦在ROM
中的地址ecall
的接口应用程序是单独编译单独生成ELF文件并且裁剪为.bin文件的.那么要对它进行调用只需要先链接进内核再由内核在合适的时机加载到内存.那么在本节介绍中需要实现
hello_world
:在屏幕上打印一行 Hello world from user mode program!
store_fault
:访问一个非法的物理地址,测试批处理系统是否会被该错误影响power
:不断在计算操作和打印字符串操作之间进行特权级切换保证当前位置为workspace.
cd ¬/workspace
用cargo创建工程,在项目目录下创建user文件夹,用来储存 用户态 的接口和代码.
cargo new ./user
工程文件中已经有了保存 用户库 的user/src, 接下来创建保存 应用程序 的user/src/bin
mkdir user/src/bin
在user/src下创建链接文件linker.ld. 。
touch linker.ld
因为应用用户层的入口地址在0x80400000,所以要对第一章给出的linker.ld进行修改,把BASE_ADDRESS设置为0x80400000
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80400000;
SECTIONS
{
. = BASE_ADDRESS;
skernel = .;
stext = .;
.text : {
*(.text.entry)
*(.text .text.*)
}
. = ALIGN(4K);
etext = .;
srodata = .;
.rodata : {
*(.rodata .rodata.*)
*(.srodata .srodata.*)
}
. = ALIGN(4K);
erodata = .;
sdata = .;
.data : {
*(.data .data.*)
*(.sdata .sdata.*)
}
. = ALIGN(4K);
edata = .;
.bss : {
*(.bss.stack)
sbss = .;
*(.bss .bss.*)
*(.sbss .sbss.*)
}
. = ALIGN(4K);
ebss = .;
ekernel = .;
/DISCARD/ : {
*(.eh_frame)
}
}
这个链接文件表达的内存布局如图所示,这里要注意 低地址 和 高地址 在图中的位置
.rodata
和 .data
两部分。前者存放只读的全局数据,通常是一些常数或者是 常量字符串等;而后者存放可修改的全局数据。.bss
保存程序中那些未初始化的全局数据,通常由程序的加载者代为进行零初始化,即将这块区域逐字节清零;回想我们把二进制文件刷入QEMU的指令,实际上就是把编译好的内核放在了0x80200000,也就是.text段
qemu-system-riscv64 \
-machine virt \
-nographic \
-bios ../bootloader/rustsbi-qemu.bin \
-device loader,file=target/riscv64gc-unknown-none-elf/release/os.bin,addr=0x80200000
将 _start 所在的 .text.entry 放在整个程序的开头,也就是说批处理系统只要在加载之后跳转到 0x80400000 就已经进入了 用户库的入口点,并会在初始化之后跳转到应用程序主逻辑. 。
这里注意ENTRY(_start),是设置程序的入口,回顾第一章entry.asm的内容,这里设置了.section .text.entry,而在main.rs中设置了global_asm!(include_str!("entry.asm")),引用了这段代码
# os/src/entry.asm
.section .text.entry
.globl _start
_start:
la sp, boot_stack_top
call rust_main
.section .bss.stack
.globl boot_stack_lower_bound
boot_stack_lower_bound:
.space 4096 * 16
.globl boot_stack_top
boot_stack_top:
提供了最终生成可执行文件的 .bss 段的起始和终止地址,方便 clear_bss 函数使用. 。
观察~/App/rCore-Tutorial-v3/user/src/linker.ld
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80400000;
SECTIONS
{
. = BASE_ADDRESS;
.text : {
*(.text.entry)
*(.text .text.*)
}
.rodata : {
*(.rodata .rodata.*)
*(.srodata .srodata.*)
}
.data : {
*(.data .data.*)
*(.sdata .sdata.*)
}
.bss : {
start_bss = .;
*(.bss .bss.*)
*(.sbss .sbss.*)
end_bss = .;
}
/DISCARD/ : {
*(.eh_frame)
*(.debug*)
}
}
可以看到与我们自己按照文件构建的link.ld文件相比,少了xxx(label) = .;的描述和. = ALIGN(4K);的偏移. 。
xxx(label) = .;用于定义一个符号xxx,并且把当前指针位置设置给他. 。
. = ALIGN(4K)用于检查当前链接位置.是否已经对齐到4K的边界,如果当前链接位置不是4K边界的倍数,链接器会填充足够的字节直到下一个 4K 边界. 。
因此可以知道,在原本的linker.ld中,要设置一些label,这样我们可以通过extern C去得到这些指针从而知道编译之后的各个部分的大小.并且每次设置一个节的时候需要检查对齐4K边界. 。
OUTPUT_ARCH(riscv)
:定义了目标输出架构是RISC-V。ENTRY(_start)
:指定了程序的入口点为_start
函数。这是程序开始执行的地方。BASE_ADDRESS = 0x80200000;
:设置程序的基地址为0x80200000,这是程序加载到内存中的起始地址。SECTIONS
:开始定义内存段的布局。. = BASE_ADDRESS;
:设置当前地址为基地址。skernel = .;
:记录内核段的起始地址。stext = .;
:记录文本段(代码段)的起始地址。.text
:定义文本段,包含可执行代码。*(.text.entry)
和*(.text .text.*)
表示将所有.text
和.text.*
节的内容链接到这里。. = ALIGN(4K);
:对齐当前地址到4K(4096字节)边界。etext = .;
:记录文本段的结束地址。srodata = .;
:记录只读数据段的起始地址。.rodata
:定义只读数据段,包含常量和只读数据。*(.rodata .rodata.*)
和*(.srodata .srodata.*)
表示将所有.rodata
和.srodata
节的内容链接到这里。erodata = .;
:记录只读数据段的结束地址。sdata = .;
:记录初始化数据段的起始地址。.data
:定义初始化数据段,包含已初始化的全局变量。*(.data .data.*)
和*(.sdata .sdata.*)
表示将所有.data
和.sdata
节的内容链接到这里。edata = .;
:记录初始化数据段的结束地址。.bss
:定义未初始化数据段(BSS段),包含未初始化的全局变量。*(.bss.stack)
和*(.bss .bss.*)
以及*(.sbss .sbss.*)
表示将所有.bss
、.sbss
和.bss.stack
节的内容链接到这里。sbss = .;
记录BSS段的起始地址。ebss = .;
:记录未初始化数据段的结束地址。ekernel = .;
:记录整个内核段的结束地址。/DISCARD/
:定义一个丢弃节,用于排除不需要的节,这里是指定不包含.eh_frame
节,通常这个节包含了异常处理帧信息。创建lib.rs模块
touch lib.rs
和os的main.rs一样,创建函数入口的同时,使用#[no_mangle]保证函数名称不被优化,并且使用了一个新的宏#[link_section = ".text.entry"]使得_start 这段代码编译后的汇编代码中放在一个名为 .text.entry 的代码段中,方便我们在后续链接的时候调整它的位置使得它能够作为用户库的入口,这里要注意我们仍然只能使用core库,因此要使用#![no_std]宏
#![no_std]
#[no_mangle]
#[link_section = ".text.entry"]
pub extern "C" fn _start() -> ! {
clear_bss();
exit(main());
panic!("unreachable after sys_exit!");
}
对应第一章也需要清除.bss段,并且使用panic!宏
#![feature(panic_info_message)]
fn clear_bss() {
extern "C" {
fn start_bss();
fn end_bss();
}
(start_bss as usize..end_bss as usize).for_each(|addr| unsafe {
(addr as *mut u8).write_volatile(0);
});
}
并且使用exit接口调用main函数,这里exit接口在后边通过ecall才能实现,而对于main函数,若bin目录下有main符号,则程序可以正常链接,但当我们找不到main的时候也需要有一个保障,这里就涉及了弱链接,如果找不到main则链接这个main
#![feature(linkage)]
#[linkage = "weak"]
#[no_mangle]
fn main() -> i32 {
panic!("Cannot find main!");
}
创建syscall模块,这个文件创建在user/src文件下
touch syscall.rs
我们使用Rust嵌入汇编代码调用ecall以实现 在用户态中发起系统调用 ,ecall的原理如下
当一个进程执行ecall指令时,处理器会触发一个异常,导致控制权转移到预先设定的内核异常处理程序。此时,内核可以检查引发ecall的上下文,并根据传入的参数提供适当的服务,比如打开文件、创建进程、分配内存等.
ecall指令本身并不携带任何参数,但是它可以访问通用寄存器中的值作为参数传递给内核。通常,以下寄存器会被用来传递参数:
x10
(a0):第一个参数x11
(a1):第二个参数x12
(a2):第三个参数x13
(a3):第四个参数x14
(a4):第五个参数x15
(a5):第六个参数x16
(a6):第七个参数x17
(a7):第八个参数,同时也作为系统调用号那么就可以在syscall.rs文件下创建接口
// user/src/syscall.rs
use core::arch::asm;
fn syscall(id: usize, args: [usize; 3]) -> isize {
let mut ret: isize;
unsafe {
asm!(
"ecall",
inlateout("x10") args[0] => ret,
in("x11") args[1],
in("x12") args[2],
in("x17") id
);
}
ret
}
asm! 宏的格式:
ecall
指令,不过它可以支持同时插入多条指令。in("x11") args[1]
则表示将输入参数 args[1]
绑定到 ecall
的输入寄存器 x11
即 a1
中,编译器自动插入相关指令并保证在 ecall
指令被执行之前寄存器 a1
的值与 args[1]
相同。args[2]
和 id
分别绑定到输入寄存器 a2
和 a7
中。a0
寄存器,它同时作为输入和输出,因此我们将 in
改成 inlateout
,并在行末的变量部分使用 {in_var} => {out_var}
的格式,其中 {in_var}
和 {out_var}
分别表示上下文中的输入变量和输出变量。在本章中,应用程序和批处理系统之间按照 API 的结构,约定如下两个系统调用:
/// 功能:将内存中缓冲区中的数据写入文件。
/// 参数:`fd` 表示待写入文件的文件描述符;
/// `buf` 表示内存中缓冲区的起始地址;
/// `len` 表示内存中缓冲区的长度。
/// 返回值:返回成功写入的长度。
/// syscall ID:64
fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize;
/// 功能:退出应用程序并将返回值告知批处理系统。
/// 参数:`exit_code` 表示应用程序的返回值。
/// 返回值:该系统调用不应该返回。
/// syscall ID:93
fn sys_exit(exit_code: usize) -> !;
那么对应的,我们可以使用syscall来实现这两个API
// user/src/syscall.rs
const SYSCALL_WRITE: usize = 64;
const SYSCALL_EXIT: usize = 93;
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
}
pub fn sys_exit(xstate: i32) -> isize {
syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}
注意 sys_write 使用一个 &[u8] 切片类型来描述缓冲区,这是一个 胖指针 (Fat Pointer),里面既包含缓冲区的起始地址,还 包含缓冲区的长度。我们可以分别通过 as_ptr 和 len 方法取出它们并独立地作为实际的系统调用参数.
为了将上述两个系统调用在用户库 user_lib 中进一步封装,从而更加接近在 Linux 等平台的实际系统调用接口,修改lib.rs文件,在其中键入
mod syscall;
use syscall::*;
pub fn write(fd: usize, buf: &[u8]) -> isize {
sys_write(fd, buf)
}
pub fn exit(exit_code: i32) -> isize {
sys_exit(exit_code)
}
console
的实现我们把 console 子模块中 Stdout::write_str 改成基于 write 的实现,且传入的 fd 参数设置为 1,它代表标准输出, 也就是输出到屏幕。目前我们不需要考虑其他的 fd 选取情况。这样,应用程序的 println! 宏借助系统调用变得可用了.
创建console.rs文件,内容和第一章该模块保持一致,只修改Stdout的Write特性的实现
// user/src/console.rs
const STDOUT: usize = 1;
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
write(STDOUT, s.as_bytes());
Ok(())
}
}
最终的文件内容为
use super::write;
use core::fmt::{self, Write};
struct Stdout;
const STDOUT: usize = 1;
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
write(STDOUT, s.as_bytes());
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)+)?));
}
}
可以通过访问~/App/rCore-Tutorial-v3,使用git checkout ch2,切换到第二章的代码查看三个应用的源码.并且可以将他们复制到我们工程的user/src/bin目录下 。
cd ~/App/rCore-Tutorial-v3
git checkout ch2
cd user/src/bin
cp 00hello_world.rs ~/workspace/user/src/bin
cp 01store_fault.rs ~/workspace/user/src/bin
cp 02power.rs ~/workspace/user/src/bin
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
#[no_mangle]
fn main() -> i32 {
println!("Hello, world!");
0
}
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
#[no_mangle]
fn main() -> i32 {
println!("Into Test store_fault, we will insert an invalid store operation...");
println!("Kernel should kill this application!");
unsafe {
core::ptr::null_mut::<u8>().write_volatile(0);
}
0
}
#![no_std]
#![no_main]
#[macro_use]
extern crate user_lib;
const SIZE: usize = 10;
const P: u32 = 3;
const STEP: usize = 100000;
const MOD: u32 = 10007;
#[no_mangle]
fn main() -> i32 {
let mut pow = [0u32; SIZE];
let mut index: usize = 0;
pow[index] = 1;
for i in 1..=STEP {
let last = pow[index];
index = (index + 1) % SIZE;
pow[index] = last * P % MOD;
if i % 10000 == 0 {
println!("{}^{}={}(MOD {})", P, i, pow[index], MOD);
}
}
println!("Test power OK!");
0
}
直接借用~/App/rCore-Tutorial-v3/user里的脚本,在user目录下创建Makefile文件
TARGET := riscv64gc-unknown-none-elf
MODE := release
APP_DIR := src/bin
TARGET_DIR := target/$(TARGET)/$(MODE)
APPS := $(wildcard $(APP_DIR)/*.rs)
ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS))
BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS))
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
elf:
@cargo build --release
binary: elf
@$(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));)
build: binary
解析
TARGET
:设置为目标架构 riscv64gc-unknown-none-elf
,这是 Rust 对 RISC-V 架构的支持。MODE
:设置为 release
,意味着使用 Rust 的发布模式进行构建,这通常会产生优化后的二进制文件。APP_DIR
:源代码目录,这里为 src/bin
,通常存放 Rust 的可执行程序源文件。TARGET_DIR
:构建输出目录,这里根据目标架构和构建模式进行构建。APPS
:使用 wildcard
函数列出 APP_DIR
目录下所有的 .rs
文件,即 Rust 源文件。ELFS
:通过 patsubst
函数转换 APPS
列表,将每个源文件转换为其对应的 ELF 格式的目标文件路径。BINS
:同样使用 patsubst
函数,将 APPS
列表转换为 .bin
格式的二进制文件路径。OBJDUMP
:使用 rust-objdump
工具,指定架构为 riscv64
,用于查看目标文件的内部结构。OBJCOPY
:使用 rust-objcopy
工具,同样指定架构为 riscv64
,用于从 ELF 格式转换为其他格式(如二进制)。elf
:这个目标调用 cargo build --release
命令,使用 Cargo(Rust 的包管理器和构建工具)构建项目,生成 ELF 格式的可执行文件。binary
:这个目标依赖于 elf
目标,然后遍历 ELFS
列表,使用 OBJCOPY
将每个 ELF 文件转换为剥离符号的二进制文件。build
:这个目标依赖于 binary
目标,意味着构建过程的最终目标是生成二进制文件。进入user目录,执行应用程序的自动构建指令make build. 。
报错
error[E0583]: file not found for module `lang_items`
--> src/lib.rs:7:1
|
7 | mod lang_items;
| ^^^^^^^^^^^^^^^
|
= help: to create the module `lang_items`, create file "src/lang_items.rs" or "src/lang_items/mod.rs"
= note: if there is a `mod lang_items` elsewhere in the crate already, import it with `use crate::...` instead
error: invalid register `x10`: unknown register
--> src/syscall.rs:11:13
|
11 | inlateout("x10") args[0] => ret,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: invalid register `x11`: unknown register
--> src/syscall.rs:12:13
|
12 | in("x11") args[1],
| ^^^^^^^^^^^^^^^^^
error: invalid register `x12`: unknown register
--> src/syscall.rs:13:13
|
13 | in("x12") args[2],
| ^^^^^^^^^^^^^^^^^
error: invalid register `x17`: unknown register
--> src/syscall.rs:14:13
|
14 | in("x17") id
| ^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0583`.
error: could not compile `user` (lib) due to 5 previous errors
make: *** [Makefile:13: elf] Error 101
可以看到lang_items模块不存在,并且所有的寄存器都被标记为invalid register. 。
我们同样去查看~/App/rCore-Tutorial-v3/user/src里的实现
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let err = panic_info.message().unwrap();
if let Some(location) = panic_info.location() {
println!(
"Panicked at {}:{}, {}",
location.file(),
location.line(),
err
);
} else {
println!("Panicked: {}", err);
}
loop {}
}
可以看到和第一章一样,实现了一个#[panic_handler]注解的函数,只不过没有使用sbi提供的shutdown和log提供的error!宏,这里注意不要思维僵化,认为要使用panic!宏就一定要实现一个名为panic的函数,而是实现了有这个注解的函数即可. 。
把这个文件拷贝过来用
cp lang_items.rs ~/workspace/user/src/
重新进行编译
cd ~/workspace/user
make build
发现仍然要报错
error: invalid register `x10`: unknown register
--> src/syscall.rs:11:13
|
11 | inlateout("x10") args[0] => ret,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: invalid register `x11`: unknown register
--> src/syscall.rs:12:13
|
12 | in("x11") args[1],
| ^^^^^^^^^^^^^^^^^
error: invalid register `x12`: unknown register
--> src/syscall.rs:13:13
|
13 | in("x12") args[2],
| ^^^^^^^^^^^^^^^^^
error: invalid register `x17`: unknown register
--> src/syscall.rs:14:13
|
14 | in("x17") id
| ^^^^^^^^^^^^
error: could not compile `user` (lib) due to 4 previous errors
make: *** [Makefile:13: elf] Error 101
可能是依赖文件出现问题,发现似乎没有依赖rust-sbi,我们查看~/App/rCore-Tutorial-v3/user/Cargo.toml,发现实际上不是rust-sbi没有依赖,而是需要risc-v的汇编依赖
[dependencies]
riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] }
此时重新在user下运行make build,仍然报错. 。
error: invalid register `x10`: unknown register
--> src/syscall.rs:11:13
|
11 | inlateout("x10") args[0] => ret,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: invalid register `x11`: unknown register
--> src/syscall.rs:12:13
|
12 | in("x11") args[1],
| ^^^^^^^^^^^^^^^^^
error: invalid register `x12`: unknown register
--> src/syscall.rs:13:13
|
13 | in("x12") args[2],
| ^^^^^^^^^^^^^^^^^
error: invalid register `x17`: unknown register
--> src/syscall.rs:14:13
|
14 | in("x17") id
| ^^^^^^^^^^^^
error: could not compile `user` (lib) due to 4 previous errors
make: *** [Makefile:13: elf] Error 101
这时候思考Makefile文件中的内容,使用cargo进行的构建,那么是不是cargo的设置出现了问题呢,对比~/App/rCore-Tutorial-v3/user,可以看到里边存在.cargo文件夹,之前我们也使用过这个文件夹
在Rust编程环境中,.cargo/config.toml 和 Cargo.toml 都是配置文件,但它们各自负责不同的任务.
.cargo/config.toml 这个文件是用来配置Rust工具链(包括Cargo)的行为的。它允许用户设置一些全局性的偏好,例如编译目标架构、编译器的默认行为(如是否开启调试信息、优化等级等)、路径到私有仓库、编译时的环境变量,以及其他各种高级配置选项,如镜像源、工具链选择等.
.cargo/config.toml 文件通常位于用户主目录下的.cargo文件夹中,但是也可以在项目的根目录下创建一个同名文件来覆盖默认配置,这样配置就会对特定项目生效.
Cargo.toml 文件是一个项目的清单文件,它包含了关于Rust项目的重要元数据,包括项目名称、版本、作者、许可证等元信息;项目的依赖库及其版本要求;构建脚本和自定义构建依赖;项目的工作区成员,即属于同一个工作区的其他项目;特定于编译配置的特性,这些特性可以开启或关闭额外的功能;目标二进制文件、库或测试模块的定义.
简而言之,Cargo.toml 描述了项目本身的结构和需求,而.cargo/config.toml 控制了Cargo如何处理项目构建过程中的各种细节。每个Rust项目都有一个Cargo.toml文件,而.cargo/config.toml则是可选的,可以全局设置或在项目目录中局部覆盖.
那么我们查看这个文件内容,果然规定了编译器和链接文件
[build]
target = "riscv64gc-unknown-none-elf"
[target.riscv64gc-unknown-none-elf]
rustflags = [
"-Clink-args=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
]
我们在/user下建立/user/.cargo/config.toml
touch /user/.cargo/config.toml
把上述内容拷贝到其中,重新运行make build
error[E0463]: can't find crate for `user_lib`
--> src/bin/01store_fault.rs:5:1
|
5 | extern crate user_lib;
| ^^^^^^^^^^^^^^^^^^^^^^ can't find crate
error[E0463]: can't find crate for `user_lib`
--> src/bin/00hello_world.rs:5:1
|
5 | extern crate user_lib;
| ^^^^^^^^^^^^^^^^^^^^^^ can't find crate
error: cannot find macro `println` in this scope
--> src/bin/01store_fault.rs:10:5
|
10 | println!("Kernel should kill this application!");
| ^^^^^^^
|
help: consider importing this macro
|
4 + use user::println;
|
error: cannot find macro `println` in this scope
--> src/bin/01store_fault.rs:9:5
|
9 | println!("Into Test store_fault, we will insert an invalid store operation...");
| ^^^^^^^
|
help: consider importing this macro
|
4 + use user::println;
|
error: `#[panic_handler]` function required, but not found
error: cannot find macro `println` in this scope
--> src/bin/00hello_world.rs:9:5
|
9 | println!("Hello, world!");
| ^^^^^^^
|
help: consider importing this macro
|
4 + use user::println;
|
For more information about this error, try `rustc --explain E0463`.
error: could not compile `user` (bin "01store_fault") due to 4 previous errors
warning: build failed, waiting for other jobs to finish...
error: could not compile `user` (bin "00hello_world") due to 3 previous errors
make: *** [Makefile:13: elf] Error 101
仍旧报错,发现要求依赖的包user_lib是不存在的,而且println是因为缺少这个依赖导致的.这里查看官方文档: 这个外部库其实就是 user 目录下的 lib.rs 以及它引用的若干子模块中。至于这个外部库为何叫 user_lib 而不叫 lib.rs 所在的目录的名字 user ,是因为在 user/Cargo.toml 中我们对于库的名字进行了设置: name = "user_lib" 。它作为 bin 目录下的源程序所依赖的用户库,等价于其他编程语言提供的标准库.
那么我们把user/Cargo.toml里的name进行修改, name = "user_lib",再进行make build,没有报错. 。
在~/workspace/user/target/riscv64gc-unknown-none-elf/release中可以找到,00hello_world,01store_fault,02power. 。
使用qemu-riscv64可以直接用用户态模拟器直接执行应用程序而不需要os. 。
到~/workspace/user/target/riscv64gc-unknown-none-elf/release文件夹下,尝试执行qemu-riscv64 ./00hello_world. 。
发现报错
Command 'qemu-riscv64' not found, but can be installed with:
sudo apt install qemu-user
尝试安装
sudo apt install qemu-user
安装成功后使用
qemu-riscv64 ./00hello_world
- Hello, world!
qemu-riscv64 ./01store_fault
- Into Test store_fault, we will insert an invalid store operation...
- Kernel should kill this application!
- Segmentation fault (core dumped)
qemu-riscv64 ./02power
- 3^10000=5079(MOD 10007)
- 3^20000=8202(MOD 10007)
- 3^30000=8824(MOD 10007)
- 3^40000=5750(MOD 10007)
- 3^50000=3824(MOD 10007)
- 3^60000=8516(MOD 10007)
- 3^70000=2510(MOD 10007)
- 3^80000=9379(MOD 10007)
- 3^90000=2621(MOD 10007)
- 3^100000=2749(MOD 10007)
- Test power OK!
可以看到执行00hello_world之后,在用户态可以正常执行,在执行01store_fault之后,因为尝试在空指针中写入0,因此出现了报错. 。
执行02power之后运行了用户态的计算和println!,而println!实际上调用了write,调用了syscall,可以看到反复在用户态和内核态转换也是可行的. 。
根据官方文档中的描述,还有两个app可以进行编译运行:03priv_inst和04priv_csr,分别验证"用户态应用直接触发从用户态到内核态的异常的原因"的两种情况
sret
指令(表示从 S 模式返回到 U 模式)sstatus
等这时候同样拷贝这两个app的源码
cp 03priv_inst.rs ~/workspace/user/src/bin/
cp 04priv_csr.rs ~/workspace/user/src/bin/
编译make build,并且运行
cd ~/workspace/user
make build
cd ~/workspace/user/target/riscv64gc-unknown-none-elf/release
qemu-riscv64 03priv_inst
- Try to execute privileged instruction in U Mode
- Kernel should kill this application!
- Illegal instruction (core dumped)
qemu-riscv64 04priv_csr
- Try to access privileged CSR in U Mode
- Kernel should kill this application!
- Illegal instruction (core dumped)
最后此篇关于[rCore学习笔记016]实现应用程序的文章就讲到这里了,如果你想了解更多关于[rCore学习笔记016]实现应用程序的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
背景: 我最近一直在使用 JPA,我为相当大的关系数据库项目生成持久层的轻松程度给我留下了深刻的印象。 我们公司使用大量非 SQL 数据库,特别是面向列的数据库。我对可能对这些数据库使用 JPA 有一
我已经在我的 maven pom 中添加了这些构建配置,因为我希望将 Apache Solr 依赖项与 Jar 捆绑在一起。否则我得到了 SolarServerException: ClassNotF
interface ITurtle { void Fight(); void EatPizza(); } interface ILeonardo : ITurtle {
我希望可用于 Java 的对象/关系映射 (ORM) 工具之一能够满足这些要求: 使用 JPA 或 native SQL 查询获取大量行并将其作为实体对象返回。 允许在行(实体)中进行迭代,并在对当前
好像没有,因为我有实现From for 的代码, 我可以转换 A到 B与 .into() , 但同样的事情不适用于 Vec .into()一个Vec . 要么我搞砸了阻止实现派生的事情,要么这不应该发
在 C# 中,如果 A 实现 IX 并且 B 继承自 A ,是否必然遵循 B 实现 IX?如果是,是因为 LSP 吗?之间有什么区别吗: 1. Interface IX; Class A : IX;
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在阅读标准haskell库的(^)的实现代码: (^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 a -> b ->a expo x0
我将把国际象棋游戏表示为 C++ 结构。我认为,最好的选择是树结构(因为在每个深度我们都有几个可能的移动)。 这是一个好的方法吗? struct TreeElement{ SomeMoveType
我正在为用户名数据库实现字符串匹配算法。我的方法采用现有的用户名数据库和用户想要的新用户名,然后检查用户名是否已被占用。如果采用该方法,则该方法应该返回带有数据库中未采用的数字的用户名。 例子: “贾
我正在尝试实现 Breadth-first search algorithm , 为了找到两个顶点之间的最短距离。我开发了一个 Queue 对象来保存和检索对象,并且我有一个二维数组来保存两个给定顶点
我目前正在 ika 中开发我的 Python 游戏,它使用 python 2.5 我决定为 AI 使用 A* 寻路。然而,我发现它对我的需要来说太慢了(3-4 个敌人可能会落后于游戏,但我想供应 4-
我正在寻找 Kademlia 的开源实现C/C++ 中的分布式哈希表。它必须是轻量级和跨平台的(win/linux/mac)。 它必须能够将信息发布到 DHT 并检索它。 最佳答案 OpenDHT是
我在一本书中读到这一行:-“当我们要求 C++ 实现运行程序时,它会通过调用此函数来实现。” 而且我想知道“C++ 实现”是什么意思或具体是什么。帮忙!? 最佳答案 “C++ 实现”是指编译器加上链接
我正在尝试使用分支定界的 C++ 实现这个背包问题。此网站上有一个 Java 版本:Implementing branch and bound for knapsack 我试图让我的 C++ 版本打印
在很多情况下,我需要在 C# 中访问合适的哈希算法,从重写 GetHashCode 到对数据执行快速比较/查找。 我发现 FNV 哈希是一种非常简单/好/快速的哈希算法。但是,我从未见过 C# 实现的
目录 LRU缓存替换策略 核心思想 不适用场景 算法基本实现 算法优化
1. 绪论 在前面文章中提到 空间直角坐标系相互转换 ,测绘坐标转换时,一般涉及到的情况是:两个直角坐标系的小角度转换。这个就是我们经常在测绘数据处理中,WGS-84坐标系、54北京坐标系
在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,我们在 .Net 7 下进行一次简单的演示. PeriodicTimer .
二分查找 二分查找算法,说白了就是在有序的数组里面给予一个存在数组里面的值key,然后将其先和数组中间的比较,如果key大于中间值,进行下一次mid后面的比较,直到找到相等的,就可以得到它的位置。
我是一名优秀的程序员,十分优秀!