Kata Containers Dragonball 的 dbs-allocator:基于区间树的 VMM 资源分配器设计与实践
发布时间:2026/9/25 3:42:00来源:尧图网络
云原生容器运行时【免费下载链接】kata-containersKata Containers is an open source project and community working to build a standard implementation of lightweight Virtual Machines (VMs) that feel and perform like containers, but provide the workload isolation and security advantages of VMs. https://katacontainers.io/项目地址https://gitcode.com/gh_mirrors/ka/kata-containers点击查看免费下载本文以 DragonballKata Containers 生态中的 Rust 微虚机监视器VMM中的dbs-allocatorcrate 为主线系统讲解它如何用Constraint约束描述 IntervalTree区间树这两个核心组件为虚拟机统一分配 MMIO 地址、PIO 端口、legacy IRQ、MSI/MSI-X 向量、KVM 内存槽等资源。读完本篇你将掌握该分配器的完整 API 与三段式使用流程并理解其节点分裂、释放合并与 AVL 自平衡的实现细节以及它在 ResourceManager 中的真实落地方式。dbs-allocator 的定位VMM 的资源账本根据 crate README 的设计说明Dragonball Sandbox 中的资源管理器需要为沙箱即虚拟机管理和分配多种不同性质的资源内存映射 I/OMMIO地址空间、端口 I/OPIO地址空间、legacy IRQ 中断号、MSI/MSI-X 中断向量、设备实例 ID 等。这些资源的共同特点是都是可以用整数标识的连续区间地址段、中断号段、ID 段因此可以统一抽象为在一个闭区间集合上做带约束的分配与释放问题。dbs-allocator正是为这一问题设计的专用 crate其 模块文档 也明确写道它不是通用区间树而是专门面向 VMM 资源管理实现的除了常规的insert()/delete()/get()/update()之外还额外提供了allocate()/free()这对资源分配原语。从工程形态看该 crate 极为轻量Cargo.toml 显示其版本为 0.1.1唯一运行时依赖是thiserror用于错误类型派生采用 Apache-2.0 协议。它作为 workspace 成员被纳入仓库根 Cargo.toml 管理供 Dragonball 主 crate 引用。核心数据结构一Range——统一的资源区间表示一切分配操作的键key都是Range类型它表示一个闭区间[min, max]两个端点均为u64pub struct Range { pub min: u64, pub max: u64, }围绕Range源码提供了多种构造方式与区间运算全部定义在 interval_tree.rs 中方法语义边界行为Range::new(min, max)直接指定区间两端点构造若min max或min 0 max u64::MAX保留为非法的全空间区间则 panicRange::with_size(base, size)以基址 长度构造等价于[base, base size]若base size溢出则 panicRange::new_point(value)构造只含单点的区间[value, value]用于 ID 类点资源len()返回区间长度max - min 1—intersect(other)判断两个闭区间是否相交max(a.min, b.min) min(a.max, b.max)contain(other)判断other是否被完全覆盖a.min b.min a.max b.maxalign_to(align)将区间左端向上对齐到align的倍数align为 0 或 1 时原样返回align非 2 的幂如 3时返回None对齐后超出max也返回None几个值得注意的实现细节align_to要求对齐值必须是 2 的幂。其实现用align (align - 1) ! 0判断并在checked_add保护下做位运算对齐测试用例 test_range_align_to 验证了[2, 6].align_to(8)返回None、[2, 6].align_to(3)返回None、以及靠近u64::MAX处的溢出安全行为。Range实现了Ord先比min再比max这使树可以按字典序组织节点Debug输出格式为[ {:016x}, {:016x} ]方便日志排障。is_empty()恒返回false——因为按new()的约束一个合法的Range至少包含一个点。核心数据结构二Constraint——一次分配请求的描述README 中给出的Constraint结构体与 源码实现 完全一致它声明了一次资源分配的全部约束#[derive(Copy, Clone, Debug)] pub struct Constraint { /// Size of resource to allocate. pub size: u64, // 要分配的资源大小 /// Lower boundary for resource allocation. pub min: u64, // 分配下界 /// Upper boundary for resource allocation. pub max: u64, // 分配上界 /// Alignment for allocated resource. pub align: u64, // 对齐要求 /// Policy for resource allocation. pub policy: AllocPolicy, // 分配策略 }构造时采用builder 风格链式调用Constraint::new 与设置方法所有端点参数都通过u64: FromT泛型接受u8~u64任意整型let constraint Constraint::new(region.len()) // 只指定 size .min(region.start_addr()) // 下界 .max(region.last_addr()) // 上界 .align(0x1000) // 4KB 对齐 .policy(AllocPolicy::FirstMatch);Constraint::new(size)的默认值为min 0、max u64::MAX、align 1、policy AllocPolicy::Default。分配策略定义在 AllocPolicypub enum AllocPolicy { /// Default resource allocation policy. Default, /// Return the first available resource matching the allocation constraints. FirstMatch, }从源码结构看find_candidate 中两个策略分支目前都映射到同一个first_match搜索左子树优先的深度优先搜索因此现阶段两种策略行为一致policy字段更像是为后续差异化策略预留的扩展点。边界校验通过validate()完成lib.rs L101-L106当max min时返回Error::InvalidBoundary(min, max)。这是 crate 定义的唯一错误类型Error 枚举错误信息与 test_set_invalid_boundary 测试中的断言相互印证。IntervalTree区间树本体及其节点状态机IntervalTreeT对外暴露的 API 与 README 的列述一致pub struct IntervalTreeT { pub(crate) root: OptionNodeT, } pub fn allocate(mut self, constraint: Constraint) - OptionRange pub fn free(mut self, key: Range) - OptionT pub fn insert(mut self, key: Range, data: OptionT) pub fn update(mut self, key: Range, data: T) - OptionT pub fn delete(mut self, key: Range) - OptionT pub fn get(self, key: Range) - OptionNodeStateT泛型参数T用于携带与已分配区间关联的业务数据如设备对象、内存 region 描述无需数据时可用()。除了上述 API源码还提供了 README 未列举的若干辅助接口实践中同样常用get_superset(key)/get_superset_mut(key)返回完整覆盖key的节点及其状态而不是要求精确匹配get_by_id(id)/get_by_id_mut(id)把标量 ID 转成点区间后查找覆盖它的节点数据适合用地址反查归属设备的场景is_empty()树是否为空。节点状态机每个树节点的数据部分是一个NodeStateT三态枚举源码注释L186-L194明确给出了合法的状态迁移None - Free或None - Valuedinsert()Free - Allocatedallocate()Allocated - Valued(T)或Valued - Valued(T)update()Allocated - Free或Valued(T) - Freefree()* - Nonedelete()update()会主动拒绝从Free/Allocated反向更新的非法迁移见 Node::update 中直接panic!(try to update unallocated interval tree node)的分支。NodeState还实现了FromNodeStateT for OptionTFree/Allocated转为NoneValued(d)转为Some(d)这让free()/delete()能自然地把旧数据以OptionT形式返回。一个必须注意的并发契约模块级文档示例interval_tree.rs 头部中特别注明——caller needs to protect from concurrent access between allocate() and the first call to update()即allocate()之后节点处于Allocated中间态在首次update()之前必须保证没有其他线程操作同一棵树。Dragonball 的落地方法后文详述是给每棵池树套Mutex来满足这一契约。三段式使用流程从 README 到可运行代码README 的 Usage 章节把使用流程归纳为三步这也是 Dragonball 中所有资源池的标准用法。第 1 步建树并灌入资源总量。先创建一棵IntervalTree然后把该资源类型的最大可用范围作为根节点插入。这里的范围可以是地址段、ID 段或中断号段let mut resources_pool IntervalTree::new(); resources_pool.insert(Range::new(MIN_RANGE, MAX_RANGE), None);第 2 步用约束分配资源。构造Constraint指定 size必要时再给 min/max/align调用allocate()树会返回一个满足约束的具体区间let constraint Constraint::new(SIZE); let resources_range resources_pool.allocate(constraint);第 3 步用分配到的区间去创建/维护设备如vm-pci、vm-device等 crate随后通常调用update(range, data)把设备句柄等数据绑定到该区间将其状态从Allocated推进到Valuedlet device Device::create(resources_range, ..); resources_pool.update(resources_range, device);README 中给出的两个完整示例分别对应ID 类点资源和内存地址段两类典型场景示例一从 PCI 设备 ID 池中分配一个未占用的 IDuse dbs_allocator::{Constraint, IntervalTree, Range}; // Init a dbs-allocator IntervalTree let mut pci_device_pool IntervalTree::new(); // Init PCI device id pool with the range 0 to 255 pci_device_pool.insert(Range::new(0x0u8, 0xffu8), None); // Construct a constraint with size 1 and alignment 1 to ask for an ID. let mut constraint Constraint::new(1u64).align(1u64); // Get an ID from the pci_device_pool let mut id pci_device_pool.allocate(constraint).map(|e| e.min as u8); // Pass the ID generated from dbs-allocator to vm-pci specified functions to create pci devices let mut pci_device PciDevice::new(id as u8, ..);示例二在客户机物理内存范围内分配一段内存use dbs_allocator::{Constraint, IntervalTree, Range}; // Init a dbs-allocator IntervalTree let mut mem_pool IntervalTree::new(); // Init memory address from GUEST_MEM_START to GUEST_MEM_END mem_pool.insert(Range::new(GUEST_MEM_START, GUEST_MEM_END), None); // Construct a constraint with size, maximum addr and minimum address of memory // region to ask for an memory allocation range. let constraint Constraint::new(region.len()) .min(region.start_addr().raw_value()) .max(region.last_addr().raw_value()); // Get the memory allocation range from the pool let mem_range mem_pool.allocate(constraint).unwrap(); // Update the mem_range in IntervalTree with memory region info mem_pool.update(mem_range, region); // After allocation, we can use the memory range to do mapping // and other memory related work. ...深入实现allocate 的节点分裂与 free 的邻接合并allocate约束匹配与区间分裂IntervalTree::allocate的执行路径如下constraint.size 0直接返回None自根节点做find_candidate按AllocPolicy分派到 first_match先递归左子树左子树没有候选时检查本节点再递归右子树——因此默认倾向于分配地址/编号较低的区间单个节点是否可作为候选由 check_constraint 判定只有节点处于Free态才参与先求节点区间与约束边界的交集[max(node.min, c.min), min(node.max, c.max)]若align为 0/1 则要求交集长度不小于size否则先align_to(align)再比较长度命中候选后最终分配结果为[aligned_min, aligned_min size - 1]size为 1 时即单点。命中之后分两种情况写回树整节点恰好被消费node.min aligned_min且节点长度等于size不分裂直接把该节点标记为Allocated并返回需要分裂源码中的注释坦承 following algorithm is not optimal in preference of simplicity以简单为先的非最优算法——先delete掉候选节点再按需把分配段之前的空闲左段分配段置为Allocated分配段之后的空闲右段分别重新insert回树中。free删除并合并相邻空闲段IntervalTree::free先delete目标区间取回关联数据然后检查左右紧邻位置min - 1、max 1是否存在处于Free态的覆盖节点若有则把合并区间扩展过去删除被吸收的相邻空闲段后以一个更大的空闲区间重新insert。这一机制保证了长时间运行后空闲段不会碎片化成大量小节点。测试用例 test_allocate_free 显式验证了这一点释放后再次分配能一次性拿回完整的[0x200, 0x2ff]段verify that adjacent free nodes have been merged。底层结构AVL 平衡与缓存树节点InnerNode除键与数据外还缓存了height子树高度与max_key子树覆盖的最大键值由 update_cached_info 在每次结构性变更后同步。平衡维护是经典 AVL 套路rotate 在左右高度差达到 ±2 时执行对应旋转含 LR/RL 双旋每次insert/delete/update沿回溯路径调用updated_node()完成刷新缓存 旋转。insert对正确性非常强硬新键与任一既有节点相交包括完全相等都会直接 panic见 Node::insert对应测试 test_tree_insert_equal / test_tree_insert_intersect_on_left / on_right 均为#[should_panic]。这保证了树的不变量任何时刻树中不存在两个相交的区间节点free()的按点反查相邻段逻辑也因此成立。真实落地Dragonball ResourceManager 的六大资源池dbs-allocator的主消费者是 resource_manager.rs 中的ResourceManager。它持有六棵IntervalTree()池树每棵都包在Mutex里ResourceManager 结构体——这正是上文提到的allocate 与 update 之间需调用方保护并发契约在 Dragonball 里的落地方式。各池的初始化范围如下ResourceManagerBuilder资源池初始化区间来源说明legacy_irq_pool[LEGACY_IRQ_BASE 1, LEGACY_IRQ_MAX]共享 IRQLEGACY_IRQ_BASE不参与分配不插入树中msi_irq_pool[MSI_IRQ_BASE, MSI_IRQ_MAX]MSI/MSI-X 向量pio_pool[PIO_MIN, PIO_MAX]端口 I/O 地址mmio_pool低段 MMIO 客户内存之上的高段地址低段还会为 x86 系统设备预留一块 MMIO 空间见下文mem_pool[GUEST_MEM_START, GUEST_MEM_END]扣除低段 MMIO 与物理内存重叠部分客户机物理内存kvm_mem_slot_pool[0, max_kvm_mem_slot)KVM 内存槽编号几个典型分配函数展示了Constraint各字段如何映射到实际需求allocate_legacy_irqConstraint::new(1)申请单个中断号若调用方指定了固定 IRQ则把min与max同时压成该值实现定点分配共享 IRQ 则直接短路返回不进树。allocate_msi_irq_alignedConstraint::new(count).align(count)即分配 count 个向量且基址按 count 自然对齐——注释说明这是为满足 PCI MSI 对自然对齐的要求与之相对的 allocate_msi_irq 不要求对齐。init_mmio_pool_helperresource_manager.rs L97-L120演示了用分配器做预留的技巧在低段 MMIO 池建成后立即用Constraint::new(MMIO_SPACE_RESERVED).min(...)从中切出一块给 x86 系统设备并update标记占用若分配失败则panic!(failed to reserve MMIO address range for x86 system devices)。allocate_mem/free_mem与kvm_mem_slot_pool的分配同理均遵循allocate → update 占位 → free 归还的模式。此外address_space_manager.rs 也直接引用了Constraint用于客户机内存/地址空间相关资源的约束式分配说明dbs-allocator在 Dragonball 内存管理路径中同样是一等公民。关键行为与注意事项小结结合源码与测试使用dbs-allocator时需要牢记以下事实性行为区间是闭区间长度 max - min 1Range::with_size(base, size)构造的也是[base, base size]见 test_with_size写约束时要小心off by oneallocate返回的区间尚未绑定数据处于Allocated态调用方应及时update绑定设备/region 数据且在首次update前需自行保证串行化Dragonball 用Mutex池锁实现对齐必须是 2 的幂0 和 1 视为不对齐否则align_to返回None该节点会直接被排除出候选insert不容忍相交重复初始化同一资源段会导致 panic 而非静默覆盖这既是保护也是使用纪律——每个资源段只能insert一次释放会合并相邻空闲段因此先分配再释放不会造成空闲区碎片化这是 test_allocate_free 明确回归测试过的行为分配顺序倾向低地址/低编号first_match采用左子树优先的深度优先搜索同一约束下重复分配会得到地址递增的结果序列size 0的约束直接返回None不会分配出空区间。小结dbs-allocator用不到千行代码把VMM 资源记账这件杂事收敛成了两个概念用Constraint描述我要什么用IntervalTree回答从哪里给。它放弃通用区间树的表达能力不支持任意相交区间集合查询换取了与 VMM 需求严丝合缝的allocate()/free()原语、AVL 自平衡带来的可预期复杂度以及对节点分裂/合并等边界行为的完整测试覆盖interval_tree.rs 测试模块 中test_allocate_free、test_tree_get_superset等用例可直接作为行为参照。在 Kata Containers 的 Dragonball 组件中MMIO/PIO/IRQ/内存/内存槽六大资源池全部建立在这套原语之上理解本 crate 也就掌握了 Dragonball 硬件资源布局的底层逻辑。赞分享云原生容器运行时【免费下载链接】kata-containersKata Containers is an open source project and community working to build a standard implementation of lightweight Virtual Machines (VMs) that feel and perform like containers, but provide the workload isolation and security advantages of VMs. https://katacontainers.io/项目地址https://gitcode.com/gh_mirrors/ka/kata-containers点击查看免费下载相关推荐kata-containers Dragonball 虚拟机的地址空间管理dbs-address-space 源码解析kata containers Dragonball 虚拟机的地址空间管理dbs address space 源码解析 Dragonball 是 Kata C云原生容器运行时Kata Containers Dragonball Sandbox面向容器负载的轻量级 KVM 虚拟机管理器Kata Containers Dragonball Sandbox面向容器负载的轻量级 KVM 虚拟机管理器 Dragonball Sandbox 是 Ka云原生容器运行时Kata Containers 多 Hypervisor 技术解析QEMU、Cloud Hypervisor、Firecracker、Dragonball 与 StratoVirt 选型与配置详解Kata Containers 多 Hypervisor 技术解析QEMU、Cloud Hypervisor、Firecracker、Dragonball 与云原生容器运行时上一篇MATH 数据集零基础完全指南从0到1掌握数学问题解决模型训练与评估下一篇Excel MCP Server图表创建指南如何用代码生成专业数据可视化图表创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网