在本章中,我们将把上一章创建的独立可执行程序编译到内核中,并与bootloader链接,成为一个可以被qemu加载的bootimage。为此,我们将介绍:
使用目标三元组描述目标操作系统。使用cargo xbuild和目标三元组.编译内核,将内核和bootloader链接到bootimage.修改_start,以便它可以做一些简单的堆栈初始化。目标三元组:
Cargo在编译内核时可以使用- target target triple来支持不同的系统。包括:cpu架构、供应商、操作系统和ABI。
因为我们正在编写我们自己的操作系统,所有当前的target triple都不适用。幸运的是,rust允许我们用目标三元组文档定义自己的JSON。首先,让我们看看目标三元组'sx86_64-unknown-linux-gnu的文件:
{ ' llvm-target ' : ' x86 _ 64-unknown-Linux-GNU ',' data-layout ' : ' e-m : e-I 64:64-f 803:128-n 83:32:64-S128 ',' arch': 'x86_64 ',' target-endian': 'little ',' target-pointer-width ' 338
//in riscv 32-xy _ OS . JSON { ' llvm-target ' : ' riscv 32 ',' data-layout ' : ' e-m :32:32-I 64:64-n32-S128 ',' target-endian': 'little ',' target-pointer-width': '32 ',' target-c-int-width': '32在这里,只解释前置链接参数:
pre-link-args ' 3360 { ' LD . lld ' 3360 '-tsrc/boot/linker.ld ' },这里需要用到指定的链接器,这里也直接给出了linker . LD的实现。请创建您自己的JSON文件:
/* Copy from bbl-ucore : https://ring00.github.io/bbl-ucore *//* Simple linker script for the ucore kernel. See the GNU ld 'info' manual ("info ld") to learn the syntax. */OUTPUT_ARCH(riscv)ENTRY(_start)BASE_ADDRESS = 0xC0020000;SECTIONS{ . = 0xC0000000; .boot : { KEEP(*(.text.boot)) } /* Load the kernel at this address: "." means the current address */ . = BASE_ADDRESS; start = .; .text : { stext = .; *(.text.entry) *(.text .text.*) . = ALIGN(4K); etext = .; } .rodata : { srodata = .; *(.rodata .rodata.*) . = ALIGN(4K); erodata = .; } .data : { sdata = .; *(.data .data.*) edata = .; } .stack : { *(.bss.stack) } .bss : { sbss = .; *(.bss .bss.*) ebss = .; } PROVIDE(end = .);}运行 cargo build --target riscv32-xy_os.json ,发现编译失败了:
error
如果我们想为其他系统编译代码,我们需要为这些系统重新编译整个 core 库 。这就是为什么我们需要 cargo xbuild 。
Cargo xbuild这个工具封装了 cargo build。同时,它将自动交叉编译 core 库 和一些 编译器内建库(compiler built-in libraries) 。我们可以用下面的命令安装它:
cargo install cargo-xbuild现在运行 cargo xbuild --target riscv32-xy_os.json ,我们的内核已经可以正确编译了。接下来的任务就是将他和 bootloader 链接,得到可以被 qemu 加载的 os 。
创建引导映象(Bootimage)编写一个 bootloader 并将其与内核链接成 引导映像 并不是一个简单的事情,所以我们直接使用已有的 bootloader :
下载 并将其中名为 related items in lab2 的文件夹中的两个子文件夹拷贝至 Cargo.toml 的同级目录下。
有了 bootloader ,那么只需要将其与我们的内核链接就可以了。这里我们需要使用到 riscv-pk 中的 configure 。为了以后能够方便的进行编译链接,我们需要编写一个 Makefile 文件(与 Cargo.toml 位于同级目录):
target := riscv32-xy_osbbl_path := $(abspath riscv-pk)mode := debugkernel := target/$(target)/$(mode)/xy_osbin := target/$(target)/$(mode)/kernel.bin.PHONY: all clean run build asm qemu kernelall: kernel$(bin): kernel mkdir -p target/$(target)/bbl && \ cd target/$(target)/bbl && \ $(bbl_path)/configure \ --with-arch=rv32imac \ --disable-fp-emulation \ --host=riscv64-unknown-elf \ --with-payload=$(abspath $(kernel)) && \ make -j32 && \ cp bbl $(abspath $@)build: $(bin)run: build qemukernel: @cargo xbuild --target riscv32-xy_os.jsonasm: @riscv64-unknown-elf-objdump -d $(kernel) | lessqemu: qemu-system-riscv32 -kernel $(bin) -nographic -machine virtdocker: sudo docker run -it --mount type=bind,source=$(shell pwd)/..,destination=/mnt panqinglin/rust_riscv bash未安装 Prebuilt RISCV GCC Toolchain 会导致编译错误,请参考 how to use "Prebuilt RISCV GCC Toolchain" 下载使用;
执行 make kernel 生成的 kernel.bin 就是我们需要的 可以被 qemu 加载的 os 。执行 make run :
> make run...qemu-system-riscv32 -kernel target/riscv32-xy_os/debug/kernel.bin -nographic -machine virtbbl loader至此,我们的 最小内核 已经“成功”跑起来了!!!吗???
Hello World!我们将用最简单的方法来验证 os 是否已经正确的被加载了:打印 Hello World! :
#!
“你已经是一个成熟的 _start 了,需要学会自己设置堆栈。”“我不是,我没有,别瞎说!”一个 成熟的 _start 需要能够设置一些简单的堆栈信息,然后跳转至 main 函数。所以我们需要使用 汇编语言 重写 _start 。在 src/boot 中创建 entry.asm :
.section .text.entry .globl _start_start: add t0, a0, 1 slli t0, t0, 16 lui sp, %hi(bootstack) addi sp, sp, %lo(bootstack) add sp, sp, t0 call rust_main .section .bss.stack .align 12 #PGSHIFT .global bootstackbootstack: .space 4096 * 16 * 8 .global bootstacktopbootstacktop:然后在 main.rs 中通过 global_asm 引入 _start ,并实现 rust_main 。现在 main.rs 应该长成这样:
#!
那么,接下来,就是见证奇迹的时刻:
> make run...qemu-system-riscv32 -kernel target/riscv32-xy_os/debug/kernel.bin -nographic -machine virtbbl loaderHello World!以后若无特殊说明,编译运行的命令就是make run
预告最黑暗的日子已经过去,我们已经完成了一个可以正常运行的 最小内核 !下一章我们将在此基础上,实现 rust 中最经典的宏: println! ,以便于后续的调试输出。