Skip to main content

原理说明

1. 设备树在虚拟化中的作用机制

1.1 硬件抽象层的核心地位

设备树(FDT,Flattened Device Tree)在现代 ARM 系统中扮演着硬件抽象层的核心角色。它是一种描述硬件配置的数据结构,由 Bootloader 加载并传递给操作系统。

在 AxVisor 虚拟化环境中,设备树承担着三个关键角色:

  1. 硬件发现者:宿主机启动时,AxVisor 首先解析主机的设备树,了解可用的物理硬件资源,包括:

    • CPU 的数量和类型
    • 内存布局和容量
    • 中断控制器类型和配置
    • 各种 I/O 设备的地址空间和属性
  2. 资源分配器:基于对物理资源的了解,AxVisor 可以智能地为多个虚拟机分配资源:

    • 为每个 VM 分配特定的 CPU 核心
    • 划分内存区域,确保 VM 间的隔离
    • 配置中断路由,避免冲突
  3. 虚拟化构建者:AxVisor 不是简单地传递原始设备树,而是为每个 VM 构建定制的虚拟设备树,包含:

    • 分配给该 VM 的 CPU 节点
    • VM 的内存映射信息
    • 配置为直通的物理设备
    • 虚拟化的系统设备

1.2 设备树的数据结构原理

设备树采用树形层次结构,每个节点代表一个硬件设备或组件,节点属性以键值对形式描述设备特性。

/ (根节点)
├── cpus (CPU节点)
│ ├── cpu@0 (CPU核心0)
│ └── cpu@1 (CPU核心1)
├── soc (系统级芯片)
│ ├── uart@2800c000 (串口设备)
│ └── gpio@fe760000 (GPIO设备)
└── memory (内存区域)

每个节点的关键属性:

  • compatible: 设备兼容性字符串,用于驱动匹配
  • reg: 地址和大小信息,定义设备的物理地址空间
  • interrupts: 中断信息,指定中断号和触发方式
  • phandle: 节点标识符,用于其他节点的引用

2. 两种生成模式的深层原理

2.1 预定义模式的适用场景和原理

适用场景

  • 设备树经过严格验证,确保稳定性
  • 需要特定的设备配置,不希望自动处理
  • 来自硬件供应商的标准设备树

工作原理: 当配置了 dtb_path 时,AxVisor 采用最小干预策略:

  1. 加载验证:读取指定的设备树文件,验证格式正确性
  2. CPU 节点更新:从主机设备树提取 CPU 信息,根据 phys_cpu_ids 过滤和更新
  3. 内存节点更新:根据 memory_regions 配置,重新生成内存节点
  4. 直通地址处理:如果有完整的设备配置,直接应用地址映射

优势:保持原有设备树的完整性,降低引入错误的风险

2.2 动态生成模式的智能处理机制

适用场景

  • 需要根据不同 VM 配置灵活调整设备
  • 希望系统自动处理设备依赖关系
  • 需要精确控制设备直通范围

工作原理: 动态生成采用分析驱动的构建方式:

  1. 设备发现阶段

    • 解析配置中的 passthrough_devices
    • 查找每个直通设备的所有后代节点
    • 构建设备路径的完整树形结构
  2. 依赖分析阶段

    • 分析每个设备的 phandle 引用
    • 识别时钟、电源、中断等依赖设备
    • 递归解析依赖关系,确保完整性
  3. 过滤处理阶段

    • 应用 excluded_devices 配置
    • 移除指定设备及其后代节点
    • 确保最终设备列表的一致性
  4. 生成构建阶段

    • 根据 NodeAction 分类处理每个节点
    • 重新构建设备树的层次结构
    • 生成二进制的 DTB 文件

优势:高灵活性,智能依赖处理,精确控制

3. 设备直通的依赖关系原理

3.1 Phandle 机制的核心作用

Phandle(property handle)是设备树中的节点引用机制,类似于编程语言中的指针。每个节点可以有一个或多个 phandle 属性,其他节点通过引用这些 phandle 来建立依赖关系。

Phandle 声明方式

// 方式1:显式声明
clock_controller: clock@fdd20000 {
compatible = "vendor,clock";
reg = <0xfdd20000 0x1000>;
phandle = <0x100>; // 显式设置phandle值
};

// 方式2:标签声明(编译器自动生成)
clock: clock@fdd20000 { // 定义标签
compatible = "vendor,clock";
reg = <0xfdd20000 0x1000>;
// 编译器会自动分配phandle
};

Phandle 引用方式

uart0: serial@2800c000 {
compatible = "vendor,uart";
clocks = <&clock 0x14a>; // 引用时钟节点
interrupt-parent = <&gic>; // 引用中断控制器
};

编译后,&clock 会被替换为具体的 phandle 值,如 clocks = <0x100 0x14a>

3.2 依赖类型解析

AxVisor 支持 15+ 种依赖类型的自动识别和解析:

时钟依赖(Clock Dependencies)

clocks = <&clk_uart>, <&clk_apb>;
clock-names = "baudclk", "apb_pclk";

解析时会自动找到对应的时钟控制器节点,确保 UART 设备有可用的时钟源。

电源域依赖(Power Domain Dependencies)

power-domains = <&pmu 0x3>;

确保设备在使用前,对应的电源域已正确初始化。

中断依赖(Interrupt Dependencies)

interrupt-parent = <&gic>;
interrupts = <0x0 0x73 0x4>; // GIC_SPI, IRQ 115, 上升沿

建立设备与中断控制器的连接关系。

GPIO 依赖(GPIO Dependencies)

gpios = <&gpio0 0x5 0x0>;  // GPIO控制器, GPIO号, 配置标志

处理设备对 GPIO 引脚的控制需求。

3.3 依赖解析算法

AxVisor 采用工作队列算法进行递归依赖分析:

工作队列算法:
1. 初始队列:配置的直通设备
2. 循环处理:
a. 取出队首设备
b. 分析其所有 phandle 属性
c. 将依赖设备加入队列(如果未处理过)
d. 标记当前设备为已处理
3. 结束条件:队列为空

这种算法确保:

  • 完整性:所有传递依赖都被发现
  • 无重复:每个设备只处理一次
  • 无循环:通过已处理集合避免死循环

4. 设备树相关节点查找流程

设备相关节点的查找主要用于识别直通设备及其相关的祖先节点和后代节点:

  1. 解析配置:从配置文件读取直通设备列表
  2. 查找后代:遍历设备树,找出所有直通设备的子节点、孙节点等后代节点
  3. 查找依赖:分析设备属性中的 phandle 引用,找出依赖的其他设备
  4. 查找祖先:确定需要包含的祖先节点,确保设备路径完整
  5. 排除节点:移除配置中指定的排除设备及其后代
  6. 生成结果:构建最终的设备节点列表用于生成 Guest FDT

假设有以下设备树结构:

/
├── soc
│ ├── bus@10000000
│ │ ├── device@10001000
│ │ └── device@10002000
│ └── bus@20000000
│ ├── device@20001000
│ └── device@20002000
└── pci@30000000
├── pci-bridge@0
│ └── eth@0
└── usb@1

如果配置指定了直通设备 /soc/bus@10000000/device@10001000,那么:

  • 后代节点:无(该设备没有子节点)
  • 祖先节点/soc/bus@10000000/soc
  • 最终结果:包含这三个节点以确保设备路径完整

如果配置指定了直通设备 /pci@30000000,那么:

  • 后代节点/pci@30000000/pci-bridge@0/pci@30000000/pci-bridge@0/eth@0/pci@30000000/usb@1
  • 祖先节点/(根节点)
  • 最终结果:包含所有这些节点

这种机制确保了直通设备在 客户机系统中能获得完整的设备树支持,包括必要的父节点和子节点。