首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

[rCore学习笔记 016]实现应用程序

编程知识
2024年07月20日 11:47

写在前面

本随笔是非常菜的菜鸡写的。如有问题请及时提出。

可以联系:1160712160@qq.com

GitHhub:https://github.com/WindDevil (目前啥也没有

设计方法

了解了特权级机制,实际上如果要设计一个应用程序就需要保证它符合U模式的要求,不要去访问S模式下的功能,那么其实现要点是:

  1. 应用程序的内存布局
  2. 应用程序发出的系统调用

具体设计

需要添加的功能

具体实现的时候也要按照设计方法中提供的要点来进行设计:

  1. 应用程序的内存布局
    1. 设置用户库的入口位置
    2. 配置linker.ld文件,设置好用户苦在ROM中的地址
  2. 应用程序发出的系统调用
    1. 实现可以调用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 保存程序中那些未初始化的全局数据,通常由程序的加载者代为进行零初始化,即将这块区域逐字节清零;
  •  (heap)区域用来存放程序运行时动态分配的数据,如 C/C++ 中的 malloc/new 分配到的数据本体就放在堆区域,它向高地址增长;
  •  (stack)区域不仅用作函数调用上下文的保存与恢复,每个函数作用域内的局部变量也被编译器放在它的栈帧内,它向低地址增长。

回想我们把二进制文件刷入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边界.

detail (这一部分要细看尤其是DISCARD)

  • 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

osmain.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! 宏的格式:

  1. 首先在第 6 行是我们要插入的汇编代码段本身,这里我们只插入一行 ecall 指令,不过它可以支持同时插入多条指令。
  2. 从第 7 行开始我们在编译器的帮助下将输入/输出变量绑定到寄存器。
  3. 比如第 8 行的 in("x11") args[1] 则表示将输入参数 args[1] 绑定到 ecall 的输入寄存器 x11 即 a1 中,编译器自动插入相关指令并保证在 ecall 指令被执行之前寄存器 a1 的值与 args[1] 相同。
  4. 以同样的方式我们可以将输入参数 args[2] 和 id 分别绑定到输入寄存器 a2 和 a7 中。
  5. 这里比较特殊的是 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文件,内容和第一章该模块保持一致,只修改StdoutWrite特性的实现:

// 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

hello_world

#![no_std]
#![no_main]

#[macro_use]
extern crate user_lib;

#[no_mangle]
fn main() -> i32 {
    println!("Hello, world!");
    0
}

store_fault

#![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
}

power

#![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

解析:

  1. 变量定义
    • 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 格式的二进制文件路径。
  2. 命令工具定义
    • OBJDUMP:使用 rust-objdump 工具,指定架构为 riscv64,用于查看目标文件的内部结构。
    • OBJCOPY:使用 rust-objcopy 工具,同样指定架构为 riscv64,用于从 ELF 格式转换为其他格式(如二进制)。
  3. 目标定义
    • 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提供的shutdownlog提供的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.tomlCargo.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_inst04priv_csr,分别验证"用户态应用直接触发从用户态到内核态的异常的原因"的两种情况:

  1. 指令本身属于高特权级的指令,如 sret 指令(表示从 S 模式返回到 U 模式)
  2. 指令访问了 S模式特权级下才能访问的寄存器 或内存,如表示S模式系统状态的 控制状态寄存器 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)
From:https://www.cnblogs.com/chenhan-winddevil/p/18312966
本文地址: http://shuzixingkong.net/article/218
0评论
提交 加载更多评论
其他文章 一种优秀的虚拟机内存架构 - AQ
源链接:https://www.axa6.com/zh/an-excellent-virtual-machine-memory-architecture 简介 虚拟机内存架构直接影响虚拟机的性能和占用。设计一个优秀的架构可以有效提升性能和效率。 本文将介绍AQ虚拟机使用的内存架构,以及AQ虚拟机内存
FOG Project库存管理系统命令注入漏洞
FOG是一款克隆/成像/救援套件/库存管理系统。在版本低于1.5.10.34的情况下,FOG中的packages/web/lib/fog/reportmaker.class.php文件受到命令注入漏洞的影响,该漏洞存在于/fog/management/export.php的文件名参数中。此漏洞已在版本1.5.10.34中得到修复。
FOG Project库存管理系统命令注入漏洞 FOG Project库存管理系统命令注入漏洞 FOG Project库存管理系统命令注入漏洞
FFmpeg开发笔记(三十九)给Visual Studio的C++工程集成FFmpeg
​《FFmpeg开发实战:从零基础到短视频上线》一书的“第11章 FFmpeg的桌面开发”介绍了如何在Windows环境对Qt结合FFmpeg实现桌面程序,那么Windows系统通过Visual Studio开发桌面程序也是很常见的,下面就介绍如何在Visual Studio的C++工程中集成FFm
FFmpeg开发笔记(三十九)给Visual Studio的C++工程集成FFmpeg
渐变边框文字效果?CSS 轻松拿捏!
今天,有个群友问了我这么一个问题,如果不想切图,是否有办法实现带渐变边框的字体效果?如下所示: 本文,就将尝试一下,在 CSS 中,我们可以如何尽可能的实现这种渐变边框字体效果。 元素叠加 首先,比较容易想到的写法是通过元素叠加实现。 元素本身实现文字效果本身 通过元素的伪元素,配合 backgro
渐变边框文字效果?CSS 轻松拿捏! 渐变边框文字效果?CSS 轻松拿捏! 渐变边框文字效果?CSS 轻松拿捏!
Python按条件筛选、剔除表格数据并绘制剔除前后的直方图
本文介绍基于Python语言,读取Excel表格文件数据,以其中某一列数据的值为标准,对于这一列数据处于指定范围的所有行,再用其他几列数据的数值,加以数据筛选与剔除;同时,对筛选前、后的数据分别绘制若干直方图,并将结果数据导出保存为一个新的Excel表格文件的方法~
Python按条件筛选、剔除表格数据并绘制剔除前后的直方图 Python按条件筛选、剔除表格数据并绘制剔除前后的直方图
Known框架实战演练——进销存系统需求
概述 该项目是一个开源、简易、轻量级的进销存管理系统,作为Known框架的实战演练项目。 项目代码:JxcLite 开源地址: https://gitee.com/known/JxcLite 功能模块 1. 基础数据 1.1 数据字典 框架内置模块,该模块用于维护系统下拉选项的数据,如商品类别、计量
74_搜索二维矩阵
74、搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非严格递增顺序排列。 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。 示例 1: 输入:matrix =
74_搜索二维矩阵 74_搜索二维矩阵
Redis主从配置
转载请注明出处: Redis主从配置的特点 数据同步:主库(Master)负责处理写请求,并将数据更改同步到从库(Slave)。从库主要用于读请求和数据备份。 读写分离:通过配置从库为只读,可以有效分散读请求,提升系统性能。 高可用性和容错:即使主库出现故障,从库也能继续提供读服务,并在主库恢复后重
Redis主从配置 Redis主从配置 Redis主从配置