AxVisor
AxVisor 作为虚拟机监控器(VMM)运行,构建并作为 ArceOS 独立内核应用程序运行。
如上依赖关系图所示,它提供了一个全局视角的虚拟化资源管理,作为连接 ArceOS 核心功能组件与虚拟化相关组件的桥梁。
一方面,它直接依赖于 ArceOS 提供的 axstd 库,调用 ArceOS 的核心功能。一些直接的依赖包括:
- axtask based vCpu management and scheduling
- axhal for platform-specific operations and interrupt handling
- axconfig for platform configuration
另一方面,它依赖于 axvm 来实现虚拟机管理(配置与运行时),包括:
- CRUD operations for guest VMs
- VM lifecycle control: setup, boot, notification and shutdown
- Hypercall handling for communication between hypervisor and guest VMs
VM 管理
- hypercall handler
- GLOBAL_VM_LIST
基于 axtask 的 vCPU 调度
axvcpu 仅负责虚拟化功能支持,例如通过 vmlaunch/vmexit 进入/退出客户机。
由于 ArceOS 已经提供了 axtask 用于在单一特权级别下进行运行时控制流管理,我们可以重用其调度器并与之共同发展。
在虚拟机启动和设置过程中,axvisor 为每个 vCPU 分配 axtask,将任务的入口函数设置为 vcpu_run()
,如果 vCPU 有专用的物理 CPU 集,它还会初始化 CPU 掩码。
vcpu_run()
vcpu_run()
函数是 vCPU 任务的主要例程,可以总结如下:
#![allow(unused)] fn main() { fn vcpu_run() { let curr = axtask::current(); let vm = curr.task_ext().vm.clone(); let vcpu = curr.task_ext().vcpu.clone(); loop { match vm.run_vcpu(vcpu_id) { // match vcpu.run() { Ok(exit_reason) => match exit_reason { AxVCpuExitReason::Hypercall { nr, args } => {} } AxVCpuExitReason::ExternalInterrupt { vector } => { // Irq injection logic } AxVCpuExitReason::Halt => { wait(vm_id) } AxVCpuExitReason::Nothing => {} AxVCpuExitReason::CpuDown { _state } => { // Sleep target axtask. } AxVCpuExitReason::CpuUp { target_cpu, entry_point, arg, } => { // Spawn axtask for target vCpu. vcpu_on(vm.clone(), target_cpu as _, entry_point, arg as _); vcpu.set_gpr(0, 0); } AxVCpuExitReason::SystemDown => {} _ => { warn!("Unhandled VM-Exit"); } }, Err(err) => {} } } } }
Task 扩展
该机制允许调用者在不修改 axtask 结构体源代码的情况下自定义其扩展字段,(这是一种类似于线程局部存储(TLS)的轻量级机制)。
axtask 结构体的基本字段:
- 任务执行所需的基本信息,包括函数调用上下文、栈指针以及其他运行时元数据。
使用场景
- 宏内核的扩展
- Process metadata (e.g., PID)
- Memory management informations like page table
- Resource management including fd table
- ...
- hypervisor 扩展
- vCPU state
- Metadata of the associated VM
- ...
Task 扩展设计
- 将
task_ext_ptr
引入作为扩展字段 - 利用基于指针的访问方式,实现与原生结构体字段相当的内存访问性能。
- 通过 def_task_ext 在编译时确定扩展字段的大小。
- 在堆上分配内存,将扩展字段指针
task_ext_ptr
设置为该内存块。 - 暴露引用 API 供外部访问
- 由
init_task_ext
初始化
#![allow(unused)] fn main() { // arceos/modules/axtask/src/task_ext.rs #[unsafe(no_mangle)] static __AX_TASK_EXT_SIZE: usize = ::core::mem::size_of::<TaskExt>(); #[unsafe(no_mangle)] static __AX_TASK_EXT_ALIGN: usize = ::core::mem::align_of::<TaskExt>(); pub trait TaskExtRef<T: Sized> { /// Get a reference to the task extended data. fn task_ext(&self) -> &T; } impl ::axtask::TaskExtRef<TaskExt> for ::axtask::TaskInner { fn task_ext(&self) -> &TaskExt { unsafe { let ptr = self.task_ext_ptr() as *const TaskExt; if !!ptr.is_null() { ::core::panicking::panic("assertion failed: !ptr.is_null()") }; &*ptr } } } // arceos/modules/axtask/src/task.rs impl TaskInner { /// Returns the pointer to the user-defined task extended data. /// /// # Safety /// /// The caller should not access the pointer directly, use [`TaskExtRef::task_ext`] /// or [`TaskExtMut::task_ext_mut`] instead. /// /// [`TaskExtRef::task_ext`]: crate::task_ext::TaskExtRef::task_ext /// [`TaskExtMut::task_ext_mut`]: crate::task_ext::TaskExtMut::task_ext_mut pub unsafe fn task_ext_ptr(&self) -> *mut u8 { self.task_ext.as_ptr() } /// Initialize the user-defined task extended data. /// /// Returns a reference to the task extended data if it has not been /// initialized yet (empty), otherwise returns [`None`]. pub fn init_task_ext<T: Sized>(&mut self, data: T) -> Option<&T> { if self.task_ext.is_empty() { self.task_ext.write(data).map(|data| &*data) } else { None } } } }
TaskExt
in axvisor
#![allow(unused)] fn main() { // axvisor/src/task.rs use std::os::arceos::modules::axtask::def_task_ext; use crate::vmm::{VCpuRef, VMRef}; /// Task extended data for the hypervisor. pub struct TaskExt { /// The VM. pub vm: VMRef, /// The virtual memory address space. pub vcpu: VCpuRef, } impl TaskExt { pub const fn new(vm: VMRef, vcpu: VCpuRef) -> Self { Self { vm, vcpu } } } def_task_ext!(TaskExt); // axvisor/src/vmm/vcpus.rs fn alloc_vcpu_task(vm: VMRef, vcpu: VCpuRef) -> AxTaskRef { let mut vcpu_task = TaskInner::new( vcpu_run, format!("VM[{}]-VCpu[{}]", vm.id(), vcpu.id()), KERNEL_STACK_SIZE, ); // ... vcpu_task.init_task_ext(TaskExt::new(vm, vcpu)); axtask::spawn_task(vcpu_task) } }
irq & timer
External Interrupt
所有来自外部设备的中断都通过多层次的 VM-Exit 处理例程返回给 axvisor 进行处理。因为 只有 axvisor 拥有全局资源管理视角。
axvisor 根据中断号和虚拟机配置文件识别外部中断:
-
如果中断是预留给 axvisor 的(例如 axvisor 自己的时钟中断),则由 axhal 提供的 ArceOS 中断处理例程来处理。
-
如果中断属于某个客户虚拟机(例如客户虚拟机的直通磁盘中断),则该中断会直接注入到对应的虚拟机。
- 请注意,一些架构的中断控制器可以配置为在不经过 VM-Exit 的情况下直接将外部中断注入到虚拟机中(例如 x86 提供的已发布中断)
Timer
草拟设计请参考 discussion#36。