回顾rCore第一章:Rust裸机程序实践

2025/6/23978 字

背景与目标

因为打算重新挑战rCore第四章的内容了,这次不打算全部看完了之后才开始做实践。

在看了第一部分的在裸机程序上启用堆的操作之后,打算先自己做一个单独的裸机程序实现堆的使用。

结果别说堆了,连栈的使用都让我犯了难。

初次尝试:基础裸机程序

首先我按照印象,写下了下面的最基本的裸机程序

rust
#![no_std]
#![no_main]

pub mod console; // 按照qemu要求实现了基于串口的 println! 宏

#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
    println!("hello world!")
    loop {}
}

use core::panic::PanicInfo;

/// 全局 panic 处理器
#[panic_handler]
pub fn panic(_info: &PanicInfo) -> ! {
    println!("PANIC: {}", _info);
} 

结果发现怎么都打印不出来想要的内容。

问题发现:栈指针未初始化

然后,通过qemu和gdb工具进行调试,才发现,sp的值为0。所以在进行函数调用操作对栈进行写入之后,程序就崩溃了。

我这才知道原来裸机程序的sp指针需要自己设置。

解决方案:汇编入口点

我按照rCore的方法编写了 entry.asm 进行正式程序之前的操作:

plaintext
    .section .text.entry
    .globl _start
_start:
    la sp, STACK_TOP
    call main

链接脚本配置

链接脚本 memory.x 如下,标记了.text.entry段为程序的最前端,同时也定义了stack段和栈顶地址 STACK_TOP

plaintext
MEMORY {
    RAM : ORIGIN = 0x80000000, LENGTH = 128M
}

ENTRY(_start)

SECTIONS {
    . = 0x80000000;
  
    .text : {
        *(.text.entry) 
        *(.text .text.*)
    } > RAM
  
    .rodata : ALIGN(4K) {
        *(.rodata .rodata.*)
    } > RAM
  
    .data : ALIGN(4K) {
        *(.data .data.*)
    } > RAM
  
    .bss : ALIGN(4K) {
        __BSS_START = .;
        *(.bss .bss.*)
        *(COMMON)
        __BSS_END = .;
    } > RAM
  
    .stack : ALIGN(4K) {
        . += 64K;
        STACK_TOP = .;
    } > RAM
  
    /DISCARD/ : {
        *(.eh_frame)
        *(.comment)
    }
}

最终实现:完整的裸机程序

此时的main.rs如下

rust
#![no_std]
#![no_main]

use core::arch::{asm, global_asm};

global_asm!(include_str!("entry.asm"));

pub mod console;
pub mod error;
pub mod system;

/// Rust 入口点(使用裸函数特性)
#[unsafe(no_mangle)]
pub fn main() -> ! {
    println!("hello world!");

    system::shutdown()
}

总算是可以调用函数了。

知识总结

本次实践,让我更加清楚了以下知识和技能:

1. 从代码到可执行文件的过程

代码的编译实际上就是把代码翻译为机器码的过程,链接是把机器码和数据整理放置到对应地址的过程(整体布局可以通过自己写的链接脚本设置)
最终就得到了一个可执行文件,这个可执行文件(除了元数据之外)布局就和链接脚本一致,本质就是在描述这个程序有什么内容和应该怎么放置到内存当中

2. gdb 调试操作

(略)

3. qemu 启动过程

bash
qemu-system-riscv64 \
	-machine virt \
	-bios none \
	-nographic \
	-kernel $(KERNEL) \
	-serial mon:stdio \
	-S \
	-gdb tcp::1234
  • -machine 就是指定当前机器使用的是名叫virt的机器(不通的机器有什么差异我还不太懂,可能是一些系统操作,比如开关机使用的寄存器和指令不太一样吧)
  • -bios 就是指定机器使用的bios程序,我的理解就是bios是一个负责加载操作系统的程序,同时还提供了一些访问硬件和寄存器的接口供操作系统使用,可以看作是操作系统的操作系统,这里none表示不指定bios,因为我这里写存依赖与机器的裸机程序,相当于单片机编程了。
  • -nographic 字面意思(虽然我还没用过qemu的图形界面)
  • -serial mon:stdio 把串口输入导向到控制台输出,就可以通过串口的方式实现print了
  • -S 开启gdb调试接口,下面是具体指定的gdb调试使用的端口

4. 函数调用的底层实现

(见rCore第一章,讲解得很棒)

结语

终于实现裸机程序的编写自由,接下来我要开始尝试实现堆了。

💬 评论区

写评论

暂无评论,来发表第一条吧。

© 2026 Corn 的自留地