rCore1

如何将程序移动到自己的平台上执行

程序执行的 目标平台三元组(CPU,操作系统,运行时库)

bash 复制代码
$ rustc --version --verbose
   rustc 1.61.0-nightly (68369a041 2022-02-22)
   binary: rustc
   commit-hash: 68369a041cea809a87e5bd80701da90e0e0a4799
   commit-date: 2022-02-22
   host: x86_64-unknown-linux-gnu
   release: 1.61.0-nightly
   LLVM version: 14.0.0

键入如上指令,CPU:X86_64,操作系统:linux,运行时库:gnu

复制代码
接下来,我们希望把 Hello, world! 移植到 RICV 目标平台 riscv64gc-unknown-none-elf 上运行。
  1. riscv64gc-unknown-none-elf 指无操作系统,无运行时库,但能生成elf文件
  2. rust基本上只有core库不依赖gnu libc。

移除标准库依赖(使cargo去新的平台编译)

rust 复制代码
# os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"
  1. 创建config后,此时在os文件夹下编译即使用如上参数。
  2. 此时pirntln!无法使用
bash 复制代码
$ cargo build
   Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: cannot find macro `println` in this scope
--> src/main.rs:4:5
  |
4 |     println!("Hello, world!");
  |     ^^^^^^^
  1. panic处理无法使用。
bash 复制代码
$ cargo build
   Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: `#[panic_handler]` function required, but not found

构建裸机执行环境

panic处理

rust 复制代码
// os/src/lang_items.rs
use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
  1. 标记panic_handler属性,告知编译器使用该函数作为panic处理。

告知函数执行入口(去除main函数)

  • 此时有报错
bash 复制代码
$ cargo build
   Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
error: requires `start` lang_item

缺少#[lang = "start"]

一个rust程序正常的执行流(by deepseek)

  • 以下是标准库std的处理
rust 复制代码
// 在 libstd 中大致是这样的(简化版)
#[lang = "start"]
fn start<T: Termination>(
    main: fn() -> T,
    argc: isize, 
    argv: *const *const u8,
    _sigpipe: u8,
) -> isize {
    // 1. 初始化Rust运行时
    init_runtime();
    
    // 2. 设置堆栈保护
    setup_stack_guard();
    
    // 3. 初始化线程局部存储(TLS)
    init_thread_local_storage();
    
    // 4. 设置panic处理
    setup_panic_handler();
    
    // 5. 处理命令行参数
    let args = parse_args(argc, argv);
    
    // 6. 调用main函数
    let result = main();
    
    // 7. 处理退出码
    result.report()
}
  • lang是language items的缩写,#[lang = "start"]意为将接下来的处理作为start语义项来处理。

最小化用户态执行环境

无操作系统程序执行流(引导加载程序bootloader)

  • Bootloader → 你的 _start 函数 → 代码------退出机制
  • 注:这是最小的执行环境

_start(),声明函数入口位置

rust 复制代码
// os/src/main.rs
#[no_mangle]
extern "C" fn _start() {
    loop{};
}
  • extern c可以保证在链接后函数名为原函数名,而不做任何重命名(rust常规会做一些重命名)。

退出机制:第一个系统调用

rust 复制代码
// os/src/main.rs

const SYSCALL_EXIT: usize = 93;

fn syscall(id: usize, args: [usize; 3]) -> isize {
    let mut ret;
    unsafe {
        core::arch::asm!(
            "ecall",
            inlateout("x10") args[0] => ret,
            in("x11") args[1],
            in("x12") args[2],
            in("x17") id,
        );
    }
    ret
}

pub fn sys_exit(xstate: i32) -> isize {
    syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
}

#[no_mangle]
extern "C" fn _start() {
    sys_exit(9);
}

第二个系统调用:输出字符串

  • 以下是AI生成的注释和system_wirte
rust 复制代码
// 执行 write 系统调用
fn sys_write(fd: i32, buffer: &[u8]) -> isize {
    let ptr = buffer.as_ptr();
    let len = buffer.len();
    let result: isize;
    
    unsafe {
        asm!(
            "syscall",                // 系统调用指令
            in("rax") 1,             // 系统调用号:write = 1
            in("rdi") fd,            // 第一个参数:文件描述符
            in("rsi") ptr,           // 第二个参数:缓冲区指针
            in("rdx") len,           // 第三个参数:缓冲区长度
            out("rcx") _,            // 被 syscall 修改的寄存器
            out("r11") _,            // 被 syscall 修改的寄存器  
            lateout("rax") result,   // 返回值(系统调用完成后)
            options(nostack, preserves_flags)
        );
    }
    
    result
}
  • rCore手册中对其进一步使用宏进行了封装

构建裸机执行环境(操作系统执行环境)

使用ecall退出(ecall可以让用户态切换到内核态)

加载

ld 复制代码
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80200000;

SECTIONS
{
    . = BASE_ADDRESS;   // . 为临时变量
    skernel = .;

    stext = .;
    .text : {
        *(.text.entry)
        *(.text .text.*)
    }

    . = ALIGN(4K);
    /*含义: . = ALIGN(4K)把当前位置指针.向上对齐到最近的4KB边界(4096字节)。
        数值公式: 对齐后的.等于(. + 4096 - 1) & ~(4096 - 1),也就是把当前值四舍五入到下一个4096的倍数。
        具体值: 取决于前面已放入段的大小。比如
        若 .text 结束时.为0x80203456,对齐后变为0x80204000。
        若刚好在边界,如.为0x80204000,对齐结果仍是0x80204000。*/
    etext = .;
    srodata = .;
    .rodata : {
        *(.rodata .rodata.*)
        *(.srodata .srodata.*)
    }   // 将文件中所有该段合并到.rodata,并将rodata置于此。
    //写下.rodata证明.rodata数据放在这里。
    . = 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)
    }
}
相关推荐
y***54881 小时前
Rust在嵌入式中的实时操作系统
开发语言·后端·rust
x***B4112 小时前
Rust unsafe代码规范
开发语言·rust·代码规范
alwaysrun2 小时前
Rust多线程编程之Thread与Channel
rust·channel·bus·mpsc·crossbeam
星释10 小时前
Rust 练习册 95:React与响应式编程
开发语言·react.js·rust
Eighteenzi11 小时前
tokio 的理解
rust
星释11 小时前
Rust 练习册 96:Rectangles与几何计算
开发语言·后端·rust
星释11 小时前
Rust 练习册 97:Run-Length Encoding 压缩算法
java·linux·rust
肖祥11 小时前
Actix-Web完整项目实战:博客 API
rust
alwaysrun11 小时前
Rust中的模式匹配
rust·match·绑定·模式匹配·解构·守卫模式