添加 axruntime 组件提供更完整的运行环境
之所以将 helloworld 的 main 函数和 axhal 组合起来就能实现输出功能,是因为 helloworld 应用程序只用到了 axhal 提供的裸机输出功能, 如果我们需要支持更复杂的应用,axhal 提供的低级的裸机环境肯定不能满足我们的需求, 那么就需要用到 axruntime 这个更强大的组件了。
axruntime 的主要功能是在进入应用程序的 main 函数之前执行一些初始化操作, 根据所选择的不同 feature 执行相对应的初始化过程。
graph TD
helloworld --> axruntime --> axhal
在这一节中我们直接使用 ArceOS 的源代码,将之前修改的代码还原,在 axhal 执行完后不是直接跳转到应用程序的 main 函数, 而是跳转到 axruntime 这个组件的 rust_main 函数,再跳转到 helloworld 的 main 函数。
动手尝试
运行命令:
make PLATFORM=riscv64-qemu-virt A=apps/helloworld run LOG=debug
运行结果如下图,下面的调试输出信息绿色字体部分可以为我们直观地展示 axruntime 做的一些初始化的工作。

有了这三个组件,我们不仅能运行 helloworld 这样的简单程序,还能运行稍微复杂一些的程序。
例如,运行 yield 应用程序 (FIFO scheduler):
make A=apps/task/yield PLATFORM=riscv64-qemu-virt LOG=info NET=y SMP=1 run
运行结果:

运行过程分析
让我们通过流程图看看 ArceOS 的运行背后到底发生了什么。
第一步是一些初始化函数的调用过程。
Step 1
graph TD;
A[axhal::platform::qemu_virt_riscv::boot.rs::_boot] --> init_boot_page_table;
A --> init_mmu;
A --> P[platform_init];
A --> B[axruntime::rust_main];
P --> P1["axhal::mem::clear_bss()"];
P --> P2["axhal::arch::riscv::set_trap_vector_base()"];
P --> P3["axhal::cpu::init_percpu()"];
P --> P4["axhal::platform::qemu_virt_riscv::irq.rs::init()"];
P --> P5["axhal::platform::qemu_virt_riscv::time.rs::init()"];
B --> axlog::init;
B --> D[init_allocator];
B --> remap_kernel_memory;
B --> axtask::init_scheduler;
B --> axdriver::init_drivers;
B --> Q[axfs::init_filesystems];
B --> axnet::init_network;
B --> axdisplay::init_display;
B --> init_interrupt;
B --> mp::start_secondary_cpus;
B --> C[main];
Q --> Q1["disk=axfs::dev::Disk::new()"];
Q --> Q2["axfs::root::init_rootfs(disk)"];
Q2 --fatfs--> Q21["main_fs=axfs::fs::fatfs::FatFileSystem::new()"];
Q2 --> Q22["MAIN_FS.init_by(main_fs); MAIN_FS.init()"];
Q2 --> Q23["root_dir = RootDirectory::new(MAIN_FS)"];
Q2 --devfs--> Q24["axfs_devfs::DeviceFileSystem::new()"];
Q2 --devfs--> Q25["devfs.add(null, zero, bar)"];
Q2 -->Q26["root_dir.mount(devfs)"];
Q2 -->Q27["init ROOT_DIR, CURRENT_DIR"];
D --> E["In free memory_regions: axalloc::global_init"];
D --> F["In free memory_regions: axalloc::global_add_memory"];
E --> G[axalloc::GLOBAL_ALLOCATOR.init];
F --> H[axalloc::GLOBAL_ALLOCATOR.add_memory];
G --> I["PAGE: self.palloc.lock().init"];
G --> J["BYTE: self.balloc.lock().init"];
H --> K["BYTE: self.balloc.lock().add_memory"];
I --> M["allocator::bitmap::BitmapPageAllocator::init()"];
J -->L["allocator::slab::SlabByteAllocator::init() self.inner = unsafe { Some(Heap::new(start, size)) } "];
K --> N["allocator::slab::SlabByteAllocator::add_memory: self.inner_mut().add_memory(start, size);"];
下面是 helloworld 程序的运行流程,实际上 helloworld 程序最后调用的是 axhal 封装好的输出功能,本质还是依靠 sbi 进行输出。
Step 2
graph TD;
A[main] --> B["libax::println!(Hello, world!)"];
B --> C[libax:io::__print_impl];
C --> D[INLINE_LOCK=Mutex::new];
C --> _guard=INLINE_LOCK.lock;
C --> E["stdout().write_fmt(args)"];
step 2.1
graph TD;
T["stdout()"] --> A["libax::io::stdio.rs::stdout()"];
A --> B["INSTANCE: Mutex = Mutex::new(StdoutRaw)"];
A --> C["return Stdout { inner: &INSTANCE }"];
step 2.2
graph TD;
T["stdout().write_fmt(args)"] --> A["Stdout::write"];
A --> B["self.inner.lock().write(buf)"];
B --> C["StdoutRaw::write"];
C --> D["axhal::console::write_bytes(buf);"];
C --> E["Ok(buf.len())"];
D --> F["putchar"];
F --> G["axhal::platform::qemu_virt_riscv::console::putchar"];
G --> H["sbi_rt::legacy::console_putchar"];
总结
至此,我们已经完成了从 axhal 到 axruntime 到 helloworld 的组合了,并且将 helloworld unikernel 运行了起来。
附录
-
axruntime
模块简介:
-
模块源码位置:modules/axruntime
- 功能描述:在进入应用程序的 main 函数之前执行一些初始化操作, 根据所选择的 feature 执行相对应的初始化过程。
-
流程图来源: