我的第一个 Hello World 级别操作系统(1)

318 阅读5分钟

本文由编者复现 rust 操作系统 第一章内容的过程整理.

本文价值在于读者大体感知原教程第一章的主线内容,不被各种小插曲模糊目标,耗费耐心无法进行实验;也是读者做过同样的实验下,可以对比验证实验过程,或产生新的理解与感受,或巩固实验操作要点;

感谢提供这么好的操作系统实践教程资料,教程内容足够丰富有趣,实验足够小巧,我想如果认真进行复现操作,操作系统入门这个层面必然融会贯通,得心应手.

教程地址 rcore-os.cn/rCore-Tutor…

目标

rust 语言编写操作系统,执行打印函数,输出 hello world

原理

开发环境

image.png

核心思想

image.png

应用程序执行环境

image.png

代码结构

image.png

  • /.cargeo/config 设置我们的默认编译目标(默认会使用 riscv64gc 作为目标平台而不是原先的默认 x86_64-unknown-linux-gnu),同时调整编译选项设置链接脚本以及强制打开 fp 选项,这样才会避免 fp 相关指令被编译器优化掉 (rust-objdump 工具反汇编内核或者应用程序可执行文件,并找到某个函数的入口)
  • /bootloader/rustsbi-qemu.bin 对应的 RustSBI 文件
  • /src/entry.asm 分配启动栈空间,并在控制权被转交给 Rust 入口之前将栈指针 sp 设置为栈顶的位置
  • /src/linker.ld 链接脚本,调整链接器的行为,使得最终生成的可执行文件的内存布局符合Qemu的预期,即内核第一条指令的地址应该位于 0x80200000(.bss.stack 段最终会被汇集到 .bss 段中)
  • /src/main.rs 操作系统入口
  • /src/console.rs 重新定义输出,调用sbi.rs中的打印文本接口
  • /src/lang_items.rs 处理致命错误
  • /src/sbi.rs 调用 sbi
  • Makefile 一些方便构建、运行、编译项目的脚本
  • rust-toolchain.toml 它代表整个项目采用的 Rust 工具链版本。请务必保持其与原版仓库对应分支一致
  • Cargo.toml 引入包 sbi-rt, 内核和 RustSBI 链接到一起

image.png

  • /target/riscv64gc-xxxx/cloneOS.bin 编译生成的os镜像文件

核心流程

  • 环境准备
  • 构建内核镜像
  • 执行输出 hello world

Windows 下操作步骤

注意: 主操作流程的每一步,实际都可能需要花费半个小时甚至几个小时完成,因为需要时间理解每步的含义,或者需要验证步骤是否正确,或者需要时间解决步骤中奇奇怪怪 bug 等等,详细流程请移步文头引入的原始教程文档地址

环境准备

  • 安装 Rust
  • 安装 MEMU
  • 安装 IDE(编者使用 RustRover)
  • 下载 RustSBI 二进制文件 (编者直接到教程的项目下面拷贝,理论上自己去下载一个也可以)

构建内核镜像

  • 准备好系统代码
  • 直接通过编译代码
➜ cargo build
  • 丢弃内核可执行文件中的元数据得到内核镜像
➜ rust-objcopy --strip-all target/riscv64gc-unknown-none-elf/release/cloneOS -O binary target/riscv64gc-unknown-none-elf/release/cloneOS.bin

注解: 上面得到的内核可执行文件完全符合我们对于内存布局的要求,但是我们不能将其直接提交给 Qemu , 因为它除了实际会被用到的代码和数据段之外还有一些多余的元数据, 这些元数据无法被 Qemu 在加载文件时利用,且会使代码和数据段被加载到错误的位置, 所以需要丢弃内核可执行文件中的元数据得到内核镜像

执行输出hello world

  • 在 os 目录下通过以下命令启动 Qemu 并加载 RustSBI 和内核镜像
➜ qemu-system-riscv64 -machine virt -nographic -bios ./bootloader/rustsbi-qemu.bin -device loader,file=target/riscv64gc-unknown-none-elf/release/cloneOS.bin,addr=0x80200000 -s -S
  • 观察到控制台输出了 hello world image.png

附录

编译器环境相关命令

Rust 编译器默认配置信息
$ rustc --version --verbose

Rust 编译器支持哪些基于 RISC-V 的目标平台
$ rustc --print target-list | grep riscv

Ubuntu 监视程序系统调用请求的工具 strace 来重新执行一下,看程序执行过程中的大量动态信息
$ strace ./donothing

rustc 编译器缺省生成RISC-V 64的目标代码
$ rustup target add riscv64gc-unknown-none-elf


分析目前的程序
文件格式
$ file target/riscv64gc-unknown-none-elf/debug/os

文件头信息
$ rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os


反汇编导出汇编程序
$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os


stat 工具来比较内核可执行文件和内核镜像的大小

stat target/riscv64gc-unknown-none-elf/release/cloneOS
  File: target/riscv64gc-unknown-none-elf/release/cloneOS
  Size: 5272            Blocks: 8          IO Block: 65536  regular file
Device: ea9d99a5h/3936197029d   Inode: 562949954437586  Links: 2
Access: (0644/-rw-r--r--)  Uid: (2753560/W9041345)   Gid: (1049089/ UNKNOWN)
Access: 2024-01-29 14:39:19.351366200 +0800
Modify: 2024-01-29 11:43:37.799889600 +0800
Change: 2024-01-29 11:43:37.834887200 +0800
 Birth: 2024-01-29 11:43:37.798906300 +0800
 
 
 ➜  stat target/riscv64gc-unknown-none-elf/release/cloneOS.bin
  File: target/riscv64gc-unknown-none-elf/release/cloneOS.bin
  Size: 4               Blocks: 1          IO Block: 65536  regular file
Device: ea9d99a5h/3936197029d   Inode: 1125899907859077  Links: 1
Access: (0644/-rw-r--r--)  Uid: (2753560/W9041345)   Gid: (1049089/ UNKNOWN)
Access: 2024-01-29 14:47:10.963529900 +0800
Modify: 2024-01-29 14:46:57.317414900 +0800
Change: 2024-01-29 14:46:57.317414900 +0800
 Birth: 2024-01-29 14:46:57.315408400 +0800

概念

RustSBI 是什么

SBI 是 RISC-V 的一种底层规范,RustSBI 是它的一种实现。 操作系统内核与 RustSBI 的关系有点像应用与操作系统内核的关系,后者向前者提供一定的服务。只是SBI提供的服务很少, 比如关机,显示字符串,读入字符串等。