依靠 axhal 组件实现从系统引导到输出
在这一节,我们尝试单纯借助 axhal 组件实现 helloworld unikernel 从系统引导到输出字符的功能,为此需要对默认的 axhal 代码做一点修改。
初探 axhal 组件
我们都知道,输出都是通过硬件完成的,正如你现在正在看的这份文档,它能显示在屏幕上都得益于我们的操作系统为我们封装好了硬件操作并能够执行它。但是我们的目标不正是写一个操作系统吗,所以很遗憾我们只能自己添加上与硬件相关的层的封装了,axhal 应运而生。
graph TD axhal
在 ArceOS 中,axhal 组件提供了一层针对不同硬件平台的硬件封装,它为指定的操作平台进行引导和初始化过程,并提供对硬件的操作。例如 modules/axhal/src/platform/qemu_virt_riscv/console.rs 里面提供了对字符输出的封装,我们可以直接调用其中的 putchar 函数进行字符的输出,而不是一次又一次地使用 sbi 这样汇编级别的代码进行输出。
其实在 ArceOS 里面,putchar函数经过了许多层的封装已经变成类似于 print 这样方便用户使用的函数了,helloworld unikernel 里面的输出调用链可以查看本章第三部分, 但其实 helloworld 输出的本质还是调用 axhal 组件的putchar函数,接下来让我们抛掉方便用户调用的封装,通过对 axhal 代码的一些修改,来直观地感受一下系统启动和调用 axhal 所提供的 putchar 函数进行输出的流程。
动手尝试
以 qemu_virt_riscv 平台为例, 我们首先关注 modules/axhal/src/platform/qemu_virt_riscv/boot.rs 这个文件, 其中的 _start() 函数被链接到 ".text.boot" 段, 作为 ArceOS 运行的第一段代码。具体的不同段的分配可以查看 modules/axhal/linker.lds.S 文件。
接下来我们尝试在里面直接调用 ArceOS 为我们封装好的 sbi 函数进行输出,首先添加 console_putchar 函数方便输出我们想要输出的结果。
#![allow(unused)] fn main() { use crate::console::putchar; unsafe fn console_putchar() { putchar(10); // 打印换行符 putchar(72); // 打印"H“ putchar(69); // 打印"E“ putchar(76); // 打印"L“ putchar(76); // 打印"L“ putchar(79); // 打印"O“ // 下面可以输出任何想要的内容, 只需更改参数即可 } }
然后我们需要在代码中加入对 console_putchar 函数的调用, 我们需要在初始化页表之后, 初始化 mmu 之前执行 console_putchar 函数。 并且添加
#![allow(unused)] fn main() { console_putchar = sym console_putchar, }
这一行, 以便汇编代码调用我们写的console_putchar函数
然后我们在初始化页表之后和初始化mmu之前调用输出:
#![allow(unused)] fn main() { call {init_boot_page_table} call {console_putchar} call {init_mmu} }
// TODO! 完整的代码 (boot.rs) 见实验部分
此时我们只需执行:
make PLATFORM=riscv64-qemu-virt A=apps/helloworld run
如无意外, 我们在打印 ArceOS 的 LOGO 之前看到了我们调用 putchar 函数进行的输出 "HELLO FROM SBI", 当然,每个字符的输出只需要在 console_putchar 中自行添加即可。
总结
至此,我们已经完成了从系统引导到输出的最小流程, 而且从开机到输出这个过程不依赖于 axhal 外的任一组件,并且是可以在真实的硬件环境中(例如 CV1811H 的 riscv 开发板)进行输出的, 这也体现了 ArceOS 的设计思路, 我们只需要复用这一个模块,就能很方便地对硬件进行操作了。
附录
axhal
模块简介:
模块源码位置:modules/axhal
功能描述: ArceOS的硬件抽象层,负责为不同操作平台提供统一的API。它为指定的操作平台进行引导和初始化过程,并在硬件上提供有用的操作。当前主要支持pc-x86、
qemu-virt-riscv、
qemu-virt-aarch64`等平台。
关于这部分的一些思路来源和背景补充:
当我们尝试在 CV1811H 开发板上移植ArceOS时,我们发现 uboot 引导后在开发板上没有任何的输出,而我们解决这个问题的办法就是抛弃掉上层的复杂的输出封装,使用最原始的 sbi_call 函数(当时也没有使用ArceOS包装好的putchar函数)在axhal 中进行输出。依靠这个最简单的输出,我们初步地移植了ArceOS到 CV1811H 开发板上。实际上,从引导部分的代码也可以看出,axhal 是一个不可分割的组件,它就像一个封装好的库供上层调用,这也意味着单独为其开一节教程不足以展示它的所有功能,我们所希望的是通过输出这一小功能,让大家感知到这个最底层的 axhal 模块。