我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Cloud Hypervisor 虚拟 IOMMU(virtio-iommu)使用指南:从嵌套虚拟化保护到 VFIO 直通与巨页优化

Cloud Hypervisor 虚拟 IOMMU(virtio-iommu)使用指南:从嵌套虚拟化保护到 VFIO 直通与巨页优化 Cloud Hypervisor 虚拟 IOMMUvirtio-iommu使用指南从嵌套虚拟化保护到 VFIO 直通与巨页优化【免费下载链接】cloud-hypervisorA Virtual Machine Monitor for modern Cloud workloads. Features include CPU, memory and device hotplug, support for running Windows and Linux guests, device offload with vhost-user and a minimal compact footprint. Written in Rust with a strong focus on security.项目地址: https://gitcode.com/GitHub_Trending/cl/cloud-hypervisor导读本文基于 Cloud Hypervisor 官方文档 docs/iommu.md 及仓库内 virtio-iommu 设备实现virtio-devices/src/iommu.rs与设备管理代码vmm/src/device_manager.rs系统讲解如何向 Guest 暴露虚拟 IOMMU包括其设计动机嵌套虚拟机内存保护、嵌套 VFIO 设备直通、virtio-iommu 的选型理由、命令行启用方式、AArch64 下 FDT 的行为差异以及如何通过 2MiB 巨页与专用 IOMMU PCI Segment 优化映射性能并支持设备热插拔。读完本文你将掌握在 Cloud Hypervisor 中为设备打开iommuon、验证 IOMMU 分组、配置巨页后端以及使用--platform iommu_segments规划热插拔的完整实战方案。为什么需要虚拟 IOMMUCloud Hypervisor 的虚拟 IOMMU 面向两类典型场景核心思路都是在 Guest 驱动与其设备实现之间插入一层地址翻译与校验的中间层interposition。保护嵌套虚拟机L2的内存安全虚拟设备VIRTIO 设备在代表 Guest 驱动访问 Guest 内存时默认情况下地址不受约束。在单层虚拟化一个 VMM 跑一个 VM场景下由于 Guest 被视为不可信方、可能被攻破并获得 VM 内特权限制设备访问整个 Guest 内存没有实际意义因此虚拟 IOMMU 的防护在这种简单场景下并不必要。但嵌套虚拟化场景完全不同假设 Host VMM 运行了一个第一层 VML1L1 被认为是可信的用户打算在 L1 中运行多个 VM于是得到多个 L2 VM 运行在同一个 L1 之上。此时如果不向 L1 暴露虚拟 IOMMU任何 L2 Guest 都能借助 Host VMM 的设备实现来访问整个 L1 Guest 内存——这是一个跨层的内存越权漏洞。虚拟 IOMMU 会校验设备被授权访问的地址从而切断这种攻击路径。实现多级 VFIO 嵌套直通第二个动机是让物理设备能够穿透多层虚拟化。其机制是Host 侧存在物理 IOMMUL1 内运行带虚拟 IOMMU 的 VM虚拟 IOMMU 的实现负责在每次 DMA 映射变化时更新物理 DMA Remapping 表DMAR。由于 Host 上只有 VFIO 这一用户态接口可以操作物理 IOMMU因此这种更新必须经由 Host 的 VFIO 框架完成。依赖这套更新机制物理设备可以被挂到虚拟 IOMMU 上从而允许设备从 L1 直通到更下一层虚拟化L2。为什么选择 virtio-iommuCloud Hypervisor 选择实现全新的 virtio-iommu 设备来提供虚拟 IOMMU原因是半虚拟化paravirtualization方案的简洁性一侧由 Guest 自身配合处理免去了模拟物理 IOMMU 时对内存页访问进行陷入trap与影子shadow的复杂度。因此项目不会去完整模拟一个物理 IOMMU而是基于 virtio 规范实现。从源码看设备实现位于 virtio-devices/src/iommu.rs其队列配置为两个队列requestq与eventq队列大小 256见 iommu.rs并支持VIRTIO_IOMMU_F_INPUT_RANGE、VIRTIO_IOMMU_F_MAP_UNMAP、VIRTIO_IOMMU_F_PROBE、VIRTIO_IOMMU_F_BYPASS_CONFIG等特性页大小掩码同时支持 2MiB 与 4KiBVIRTIO_IOMMU_PAGE_SIZE_MASK (2 20) | (4 10)见 iommu.rs这正是后文更快映射一节的基础。设备还会通过 PROBE 属性向 Guest 声明一块 MSI 保留内存区域RESV_MEM避免 x86 平台上出现 ARM 风格的默认保留区间冲突见 iommu.rs。启用前提内核从 Linux 内核 5.14 起virtio-iommu 在 X86-64 与 AArch64 两个架构上均可用。请确认 Guest 内核版本不低于 5.14 并已启用CONFIG_VIRTIO_IOMMU相关配置。Cloud Hypervisor需使用支持 virtio-iommu 的构建版本。基本用法把设备挂到虚拟 IOMMU 上向 Guest 暴露虚拟 IOMMU需要创建一个 virtio-iommu 设备并通过 ACPI IORT 表暴露给 Guest——这可以通过至少把一个设备挂到虚拟 IOMMU 上来简单达成。具体做法是在命令行中给设备显式打上iommuon标签设备即被视为位于该 IOMMU 之后并非所有设备都支持这个额外选项默认值始终为off因为大多数用户不需要它开启会带来性能影响。可通过./cloud-hypervisor --help查看哪些设备支持挂载到虚拟 IOMMU。从 vmm/src/vm_config.rs 与 vmm/src/vm_config.rs 的iommu: bool字段可见该选项贯通了设备级公共配置与全局 VmConfig在设备管理器中handle.pci_common.iommu为 true 的设备会获得共享的iommu_mapping并被记录进 IOMMU 附加设备列表见 device_manager.rs。下面是一个把virtio-blk设备挂到虚拟 IOMMU 的简单示例./cloud-hypervisor \ --cpus boot1 \ --memory size512M \ --disk pathfocal-server-cloudimg-amd64.raw,iommuon,image_typeraw \ --kernel custom-vmlinux \ --cmdline consolettyS0 consolehvc0 root/dev/vda1 rw在 Guest 中验证 IOMMU 分组从 Guest 视角检查/sys/kernel/iommu_groups下的目录即可验证设备是否受虚拟 IOMMU 保护ls /sys/kernel/iommu_groups 0本例中只应创建一个 IOMMU 分组。在该分组下可以看到属于该组的设备的 b/d/fls /sys/kernel/iommu_groups/0/devices/ 0000:00:03.0再用lspci确认该设备正是预期的那一个lspci 00:00.0 Host bridge: Intel Corporation Device 0d57 00:01.0 Unassigned class [ffff]: Red Hat, Inc. Device 1057 00:02.0 Unassigned class [ffff]: Red Hat, Inc. Virtio console 00:03.0 Mass storage controller: Red Hat, Inc. Virtio block device 00:04.0 Unassigned class [ffff]: Red Hat, Inc. Virtio RNG可以看到00:03.0正是 Virtio block device说明它已被放置在 IOMMU 分组内。AArch64 下通过 FDT 使用虚拟 IOMMU在 AArch64 架构上即使不启用 ACPI虚拟 IOMMU 依然可用但其效果与上面 x86 的测试不同。当 ACPI 禁用时虚拟 IOMMU 通过 Flattened Device TreeFDT支持。此时 Guest 内核无法分辨哪些设备应当挂到 IOMMU 之后无论你通过iommuon挂了多少设备PCI 总线上的所有设备IOMMU 自身除外都会被挂到虚拟 IOMMU 上且每个设备会被各自加入一个 IOMMU 分组。因此/sys/kernel/iommu_groups的目录内容将是ls /sys/kernel/iommu_groups/0/devices/ 0000:00:02.0 ls /sys/kernel/iommu_groups/1/devices/ 0000:00:03.0 ls /sys/kernel/iommu_groups/2/devices/ 0000:00:04.0即 console、blk、RNG 等每个 PCI 设备各占一个独立分组。更快的大块映射用 2MiB 巨页问题背景4KiB 映射的性能瓶颈默认情况下Guest 内存以 4KiB 页映射且不启用巨页导致虚拟 IOMMU 设备只能收到 4KiB 粒度的映射请求。要建立大块映射需要发出大量请求这会拖慢物理 IOMMU 的建立过程。嵌套 VFIO 场景受影响更明显把设备直通到 L2 Guest 时运行在 L1 中的 VFIO 驱动会为该设备更新 DMAR 条目。由于 VFIO 会钉住pin整个 Guest 内存L2 Guest 的全部映射都需要以多个 4KiB 映射形式存储——L2 Guest 内存越大映射更新耗时越长。还有一个额外问题如果 L2 Guest 内存相当大大量映射可能会超过 Host 上设置的 VFIO 映射数量上限。该默认值为 65536256MiB 的 RAM 就能轻易触达这个上限。解决这两个问题耗时与超限的办法是减少描述同一大块映射所需的请求数量即使用 2MiB 巨页。把 Guest 内存看作更大的页由于虚拟 IOMMU 设备本身支持 2MiB 页Guest 需要的映射数量就会减少既避免超过上限也缩短 Host 端处理时间。这正是尽可能多地使用巨页可以加快 VM 启动时间的原理——从源码看设备的页大小掩码同时声明支持 4KiB 与 2MiBVIRTIO_IOMMU_PAGE_SIZE_MASKiommu.rs使 2MiB 映射请求得以被接受。基本用法为普通 Guest 配置巨页首先确保系统有足够的巨页覆盖整个 Guest 内存# 本示例创建 4096 个巨页 echo 4096 /proc/sys/vm/nr_hugepages接下来创建 VM。有两个要点一是希望 VM 内存以巨页承载需用/dev/hugepages作为后端二是需要在 Guest 自身内也创建一些巨页供其消耗。./cloud-hypervisor \ --cpus boot1 \ --memory size8G,hugepageson \ --disk pathfocal-server-cloudimg-amd64.raw,image_typeraw \ --kernel custom-vmlinux \ --cmdline consolettyS0 consolehvc0 root/dev/vda1 rw hugepagesz2M hugepages2048 \ --net tap,mac,iommuon关键参数说明参数作用--memory size8G,hugepageson让 VM 内存映射在 Host 巨页上配合/dev/hugepages后端hugepagesz2M hugepages2048Guest cmdline在 Guest 内预留 2MiB 巨页供 Guest 内核/驱动消耗--net ... iommuon将 virtio-net 设备挂到虚拟 IOMMU 之后嵌套用法L2 也使用巨页为达到优化性能L2 Guest 同样需要基于巨页映射。假设要直通的物理设备是0000:00:01.0步骤如下。第一步启动 L1 VM注意 cmdline 中包含嵌套虚拟化与 VFIO 相关内核参数./cloud-hypervisor \ --cpus boot1 \ --memory size8G,hugepageson \ --disk pathfocal-server-cloudimg-amd64.raw,image_typeraw \ --kernel custom-vmlinux \ --cmdline consolettyS0 consolehvc0 root/dev/vda1 rw kvm-intel.nested1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz2M hugepages2048 \ --device path/sys/bus/pci/devices/0000:00:01.0,iommuon第二步L1 VM 运行起来后在 Guest 内把设备从默认驱动解绑并绑定到 VFIO设备此时应显示为0000:00:04.0echo 0000:00:04.0 /sys/bus/pci/devices/0000\:00\:04.0/driver/unbind echo 8086 1502 /sys/bus/pci/drivers/vfio-pci/new_id echo 0000:00:04.0 /sys/bus/pci/drivers/vfio-pci/bind第三步以巨页内存后端启动 L2 Guest./cloud-hypervisor \ --cpus boot1 \ --memory size4G,hugepageson \ --disk pathfocal-server-cloudimg-amd64.raw,image_typeraw \ --kernel custom-vmlinux \ --cmdline consolettyS0 consolehvc0 root/dev/vda1 rw \ --device path/sys/bus/pci/devices/0000:00:04.0这样 L1 与 L2 两层都基于 2MiB 巨页映射DMAR 更新请求数大幅下降既规避了 VFIO 的 65536 映射上限也缩短了映射建立时间。专用 IOMMU PCI 段为热插拔铺路为了便于热插拔那些必须位于 IOMMU 之后的设备可以把整个 PCI Segment 标记为位于 IOMMU 之后。这通过--platform num_pci_segmentsnumber_of_segments,iommu_segmentsrange of segments完成API 侧则对应PlatformConfig中的等价字段vmm/src/vm_config.rs。示例启用 API socket 并创建第二个位于 IOMMU 之后的 PCI Segment./cloud-hypervisor \ --api-socket/tmp/api \ --cpus boot1 \ --memory size4G,hugepageson \ --disk pathfocal-server-cloudimg-amd64.raw,image_typeraw \ --kernel custom-vmlinux \ --cmdline consolettyS0 consolehvc0 root/dev/vda1 rw \ --platform num_pci_segments2,iommu_segments1随后即可把一个需要 IOMMU 的 VFIO 设备热插到该 Segment 上./ch-remote --api-socket/tmp/api add-device path/sys/bus/pci/devices/0000:00:04.0,iommuon,pci_segment1注意事项无法被放到 IOMMU 之后的设备例如没有iommu选项的设备不能被放到 IOMMU Segment 上从源码看所有落在iommu_segments内的 bdf每个 Segment 下设备号 031都会被并入 IOMMU 附加设备列表见 device_manager.rs因此这些 Segment 天然成为IOMMU 专属段除了num_pci_segments与iommu_segmentsPlatformConfig还支持iommu_address_widthbits默认 64 位见 vmm/src/vm_config.rs等参数用于约束 IOMMU 输入地址宽度。小结Cloud Hypervisor 通过 virtio-iommu 为多级虚拟化提供了两条关键能力一是用虚拟 IOMMU 隔离 L2 Guest 对 L1 内存的越权访问二是借助 DMAR 更新链路实现物理设备的嵌套 VFIO 直通。启用路径清晰直接——给目标设备加上iommuon即可自动创建 virtio-iommu 设备并经 ACPI IORT 暴露AArch64 无 ACPI 时则退化为 FDT 全量挂载行为。性能层面2MiB 巨页是规避 VFIO 65536 映射上限、加速大块映射建立的关键手段而--platform iommu_segments则把必须挂 IOMMU的设备约束提升到 PCI Segment 粒度为热插拔提供了干净的落点。【免费下载链接】cloud-hypervisorA Virtual Machine Monitor for modern Cloud workloads. Features include CPU, memory and device hotplug, support for running Windows and Linux guests, device offload with vhost-user and a minimal compact footprint. Written in Rust with a strong focus on security.项目地址: https://gitcode.com/GitHub_Trending/cl/cloud-hypervisor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表