配置加载流程
本节介绍 AxVisor 如何发现、加载和处理虚拟机配置。
AxVisor 的配置加载是一个多阶段的过程。当 VMM(虚拟机监控器)启动时,它需要决定从哪里获取虚拟机的配置信息。
系统首先尝试从文件系统读取配置文件,这允许用户在运行时修改配置而无需重新编译。如果文件系统不可用(例如在早期启动阶段)或没有找到有效的配置文件,系统会使用编译时嵌入的静态配置作为后备方案。
整个加载过程包括以下关键步骤:
- 初始化环境:建立 DTB(设备树)缓存,为后续设备配置做准备
- 选择配置源:根据文件系统可用性决定加载策略
- 解析配置:将 TOML 文本转换为 Rust 结构体
- 验证配置:确保所有必需字段存在且值合法
- 转换配置:从用户友好的格式转换为运行时高效的格式
- 创建虚拟机:使用最终配置初始化 VM 实例
加载流程概述
AxVisor 的配置加载流程采用后备策略(fallback strategy),确保系统在各种环境下都能正常启动。下图展示了完整的加载决策链:
图解说明:
这个决策流程图展示了 AxVisor 如何智能地选择配置来源,整个过程分为以下关键阶段:
1. 初始化 DTB 缓存
- DTB(Device Tree Blob)包含硬件信息,是配置设备的基础
- 这一步在配置加载之前完成,确保设备配置可以引用 DTB 数据
- DTB 缓存避免了重复解析设备树,提升性能
2. 文件系统可用性检查(决策点 1)
- 检查是否启用了
fsfeature(编译时决策) - 如果启用,检查文件系统是否已挂载(运行时决策)
- 是:尝试从
/guest/vm_default/目录加载配置 - 否:直接使用编译时嵌入的静态配置
3. 配置文件发现(决策点 2)
- 扫描配置目录,查找
.toml文件 - 执行快速验证(检查必需的 section)
- 找到有效配置:使用文件系统配置
- 未找到配置:回退到静态配置
4. 配置处理统一路径 无论配置来自文件系统还是静态嵌入,后续处理流程完全一致:
- 解析 TOML:将文本转换为 Rust 结构体
- 验证配置:检查字段完整性和取值合理性
- 转换为运行时配置:从
AxVMCrateConfig转换为AxVMConfig - 创建虚拟机:使用最终配置初始化 VM 实例
后备策略的优势:
- 灵活性:支持运行时修改配置(文件系统)和固定配置(静态)
- 鲁棒性:即使文件系统不可用,系统仍能使用静态配置启动
- 统一性:两种配置源使用相同的处理流程,减少代码重复
配置加载优先级
AxVisor 支持两种配置来源,按优先级排序:
1. 文件系统配置(运行时)
当启用 fs feature 时,VMM 首先尝试从文件系统加载配置。这种方式允许用户在不重新编译的情况下修改虚拟机配置。下图展示了文件系统配置的发现流程:
图解说明:
1. 固定目录扫描
- VMM 从固定路径
/guest/vm_default/开始扫描 - 这个路径是约定的配置目录,用户应将虚拟机配置文件放在此处
- 目录名
vm_default表示这是默认加载的虚拟机配置集合
2. 文件名匹配
- 使用
read_dir()遍历目录中的所有条目 - 通过扩展名过滤:只处理
.toml文件 - 忽略子目录和其他文件类型(如
.txt,.md)
3. 快速验证过滤
- 读取每个
.toml文件的内容到字符串 - 执行轻量级的格式检查:
- 必须包含
[base]section(基本信息) - 必须包含
[kernel]section(内核配置) - 必须包含
[devices]section(设备配置)
- 必须包含
- 不符合要求的文件被跳过并记录警告日志
4. 加载配置内容
- 将通过验证的 TOML 文件内容存储到内存
- 返回配置字符串的集合:
Vec<String> - 这些字符串随后会进入完整的解析和验证流程
为什么需要快速验证:
- 早期失败:在完整解析之前过滤掉明显无效的文件,节省资源
- 用户友好:提供清晰的错误提示,而不是神秘的解析错误
- 性能优化:避免对非配置文件执行昂贵的 TOML 解析
扫描规则:
- 目录路径:
/guest/vm_default/ - 文件扩展名:
.toml - 快速验证:检查是否包含
[base]、[kernel]、[devices]三个必需 section
2. 静态配置(编译时)
如果文件系统不可用或没有配置文件,使用编译时嵌入的静态配置。这种机制保证系统在任何情况下都能启动。下图展示了编译时配置嵌入的流程 :
图解说明:
这个流程图展示了 Rust 的构建系统如何在编译期自动处理配置文件:
1. build.rs 执行
build.rs是 Rust 的构建脚本,在正式编译前执行- 它在编译机器上运行,可以访问文件系统
- 负责生成额外的 Rust 源代码
2. 扫描配置目录
- 查找
configs/vms/目录(源码树中的目录) - 收集所有
.toml配置文件 - 这个目录由开发者维护,包含预定义的虚拟机配置
3. 生成 Rust 代码
- 创建
vm_configs.rs文件(位于OUT_DIR临时目录) - 生成一个函数
static_vm_configs() -> Vec<&'static str> - 函数体包含多个
include_str!宏调用
4. include_str! 宏嵌入
include_str!("path/to/file.toml")在编译期读取文件内容- 将文件内容作为字符串字面量嵌入到二进制文件中
- 结果是
&'static str类型,无运行时开销
生成代码示例:
pub fn static_vm_configs() -> Vec<&'static str> {
vec![
include_str!("../../configs/vms/linux-qemu.toml"),
include_str!("../../configs/vms/arceos.toml"),
]
}
静态配置的优势:
- 零依赖启动:无需文件系统即可运行
- 早期启动支持:在内核初始化早期就能创建虚拟机
- 确定性:配置在编译时固定,避免运行时配置错误
- 无 I/O 开销:配置已在内存中,无需读取文件
静态配置通过 include_str! 宏将 TOML 文件内容嵌入二进制文件。
配置加载实现
文件系统加载
文件系统配置加载是一个迭代过程,逐个处理发现的配置文件。下图展示了 VMM 与文件系统交互的详细时序:
图解说明:
这个时序图详细展示了文件系统配置加载的交互过程:
阶段 1:目录扫描
- VMM 调用文件系统的
read_dir()接口 - 文件系统返回
/guest/vm_default/目录下的所有文件和子目录 - 返回的 是文件列表(文件名和元数据)
阶段 2:迭代处理(循环)
对每个 .toml 扩展名的文件执行以下操作:
2.1 读取文件内容
- VMM 请求读取完整的文件内容
- 文件系统将文件作为字符串返回
- 此时还未进行 TOML 解析,仅仅是文本读取
2.2 快速验证
- VMM 在本地执行快速检查(不涉及 Parser)
- 使用简单的字符串匹配查找必需的 section 标记:
[base][kernel][devices]
- 这是轻量级操作,比完整的 TOML 解析快得多
2.3 决策分支
- 验证通过:将 TOML 字符串添加到待解析列表
- 验证失败:
- 记录警告日志(如 "Skipping invalid config file: missing [base] section")
- 继续处理下一个文件
- 不中断整个加载流程
阶段 3:批量解析
- 完成所有文件扫描后,将收集到的 TOML 字符串交给 Parser
- Parser 负责完整的语法分析和类型检查
- 这个阶段在下一节("TOML 解析流程")中详细说明
设计要点:
- 早期过滤:快速验证阶段过滤掉明显无效的文件,避免浪费解析资源
- 容错处理:单个文件验证失败不影响其他配置的加载
- 批量处理:收集所有有效配置后统一解析,提高效率
文件系统配置加载的关键步骤: