CubeSandbox Hypervisor virtio-balloon 深度指南:参数配置、内存回收原理与运行时弹性调整
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
本文以 CubeSandbox 仓库中 hypervisor/docs/balloon.md 为骨架,结合 virtio-balloon 设备源码、参数解析实现 与 运行时调整 API 展开,系统讲解该 Cloud Hypervisor 分支中 virtio-balloon 设备的完整配置项、底层内存回收机制与动态调整方法。读完本文,你将掌握
--balloon三个参数(size、deflate_on_oom、free_page_reporting)的语义与取值规则,理解 inflate/deflate/free-page-reporting 三条 virtqueue 背后的madvise/fallocate实现,并学会通过 HTTP API 在运行时安全地调整 balloon 大小。
一、virtio-balloon 在 CubeSandbox Hypervisor 中的定位
CubeSandbox 的 hypervisor 目录基于 Cloud Hypervisor 实现,其中 balloon 设备严格遵循 VIRTIO 规范实现。它的核心价值在于:为主机(Host)提供一条回收客户机(Guest)内存的途径——通过控制 Guest 可见内存的数量,让宿主机能够在不关闭虚拟机的前提下取回空闲内存。除此之外,它还为 Guest 内存管理提供了若干实用特性(如 OOM 时自动放气、空闲页上报)。
与传统的静态内存分配相比,balloon 设备的意义在于“可变性”:VM 的总内存(RAM)与 balloon 占用的内存之差,才是 Guest 实际可用的内存;而 balloon 的大小既可以在启动时指定,也可以在运行时通过管理 API 调整,从而在“多租户资源超卖”与“单租户突发内存需求”之间取得平衡。
二、BalloonConfig 参数总览
BalloonConfig在命令行视角下即--balloon选项,它包含三个参数。官方文档给出如下 Rust 结构体定义(见 hypervisor/docs/balloon.md):
struct BalloonConfig { pub size: u64, pub deflate_on_oom: bool, pub free_page_reporting: bool, }该结构体在仓库源码中亦有对应定义,位于 hypervisor/vmm/src/vm_config.rs#L381-L390:
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct BalloonConfig { pub size: u64, /// Option to deflate the balloon in case the guest is out of memory. #[serde(default)] pub deflate_on_oom: bool, /// Option to enable free page reporting from the guest. #[serde(default)] pub free_page_reporting: bool, }两个布尔字段均带#[serde(default)],与“可选参数、默认关闭”的语义一致。
从 CLI 角度看,命令行为:
--balloon <balloon> Balloon parameters "size=<balloon_size>,deflate_on_oom=on|off,free_page_reporting=on|off"该语法定义同样可以在 hypervisor/vmm/src/config.rs#L1350-L1353 的BalloonConfig::SYNTAX常量中看到,CLI 参数注册位于 hypervisor/src/main.rs#L222-L223。
参数汇总如下表:
| 参数 | 类型 | 是否必填 | 默认值 | 作用 |
|---|---|---|---|---|
size | 64 位无符号整数(字节) | 必填 | — | balloon 设备大小,从 VM 总内存中扣除 |
deflate_on_oom | 布尔(on/off) | 可选 | off | 允许 Guest 在 OOM 时放气(缩小 balloon)自救 |
free_page_reporting | 布尔(on/off) | 可选 | off | 允许 Guest 上报已释放的空闲页,供 VMM 通知宿主回收 |
参数解析实现细节
参数解析由 hypervisor/vmm/src/config.rs#L1355-L1386 的BalloonConfig::parse完成,可以佐证上文表格中的默认值语义:
pub fn parse(balloon: &str) -> Result<Self> { let mut parser = OptionParser::new(); parser.add("size"); parser.add("deflate_on_oom"); parser.add("free_page_reporting"); parser.parse(balloon).map_err(Error::ParseBalloon)?; let size = parser .convert::<ByteSized>("size") .map_err(Error::ParseBalloon)? .map(|v| v.0) .unwrap_or(0); let deflate_on_oom = parser .convert::<Toggle>("deflate_on_oom") .map_err(Error::ParseBalloon)? .unwrap_or(Toggle(false)) .0; let free_page_reporting = parser .convert::<Toggle>("free_page_reporting") .map_err(Error::ParseBalloon)? .unwrap_or(Toggle(false)) .0; Ok(BalloonConfig { size, deflate_on_oom, free_page_reporting, }) }值得注意的实现细节:
size使用ByteSized类型解析,因此命令行中可以直接书写1G、512M等带单位的值,而结构体内部以字节(u64)存储;deflate_on_oom与free_page_reporting使用Toggle类型解析,接受on/off,缺省时回退为false;size未指定时回退为0,但在启动配置校验阶段,balloon 大小不允许大于等于 RAM 大小(见下文“配置校验”一节),并且 balloon 设备通常需要显式指定size。
三、三个参数逐一详解
3.1size:balloon 设备的初始大小
size表示 balloon 设备的大小,它会被从 VM 的总内存中扣除。举例来说:如果创建一个 4GiB RAM 的 VM,同时配置 1GiB 的 balloon,那么 Guest 实际可访问的内存为 3GiB。
需要强调一个容易误解的点:Guest 在启动时看到的是全部 RAM(即 4GiB),除非 Guest 内运行了具备 balloon 感知能力(balloon enlightened)的驱动/代理,否则它有权使用全部内存。balloon 的职责正是通过这种“可见但被扣减”的机制,促使 Guest 主动把内存交还给宿主机。
该参数必填,取值为 64 位无符号整数,单位是字节。
示例
--balloon size=1G从设备初始化源码看(hypervisor/virtio-devices/src/balloon.rs#L351-L402),size会被右移 12 位(即除以 4KiB 页大小,对应常量VIRTIO_BALLOON_PFN_SHIFT = 12)转换为num_pages写入 virtio 配置空间,让 Guest 驱动知道宿主期望它交出多少页:
let config = VirtioBalloonConfig { num_pages: (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32, ..Default::default() };3.2deflate_on_oom:Guest 内存不足时的自动放气
deflate_on_oom允许 Guest 在自身发生 Out Of Memory(OOM)时缩小(放气)balloon。只要 balloon 当前大小大于 0,Guest 就可以在 OOM 恢复需要时,将 balloon 一路缩小到 0,从而取回被扣减的内存。
该参数可选,取值为布尔类型,默认off。
示例
--balloon size=2G,deflate_on_oom=on从源码层面看,该选项对应 virtio-balloon 协议中的特性位VIRTIO_BALLOON_F_DEFLATE_ON_OOM(值 2,见 hypervisor/virtio-devices/src/balloon.rs#L52-L56)。设备创建时仅在该选项开启时向 Guest 通告这一特性:
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; if deflate_on_oom { avail_features |= 1u64 << VIRTIO_BALLOON_F_DEFLATE_ON_OOM; } if free_page_reporting { avail_features |= 1u64 << VIRTIO_BALLOON_F_REPORTING; }可以推断:该特性依赖 Guest 内的驱动配合——只有当 Guest 驱动协商并实现了 deflate 能力时,OOM 场景下的自动放气才真正生效;作为宿主侧策略,建议为承载高负载、内存抖动明显的业务 VM 开启此选项。
3.3free_page_reporting:空闲页上报与宿主回收
free_page_reporting允许 Guest 向 VMM 报告“已释放的空闲页列表”。该特性不要求 balloon 有特定大小,因为它不影响 balloon 的尺寸——即使size=0,只要开启本选项,Guest 依然可以在页面被使用过后,主动告知 VMM 哪些页已经空闲。基于这些信息,VMM 可以向宿主内核建议“这些页已经不再需要”,从而触发宿主侧的内存回收。
该参数可选,取值为布尔类型,默认off。
示例
--balloon size=0,free_page_reporting=on这个例子非常实用:它展示了不占用任何 Guest 内存、纯靠空闲页上报实现宿主回收的轻量方案,适合那些不需要固定扣减内存、但希望回收 Guest 闲置内存的场景。
四、底层实现剖析:三条 virtqueue 与内存回收调用链
virtio-balloon 设备的完整实现位于 hypervisor/virtio-devices/src/balloon.rs。设备使用独立的 epoll 线程处理队列事件(balloon.rs#L248-L262),其中 inflate、deflate 两条队列各自绑定一个 eventfd,free-page-reporting 队列在启用该特性时动态注册。
4.1 队列布局与大小
const QUEUE_SIZE: u16 = 128; // inflate / deflate 队列深度 const REPORTING_QUEUE_SIZE: u16 = 32; // free page reporting 队列深度 const MIN_NUM_QUEUES: usize = 2;默认创建 2 条队列(inflate、deflate);仅当free_page_reporting开启时追加第 3 条 reporting 队列(balloon.rs#L360-L384):
let mut queue_sizes = vec![QUEUE_SIZE; MIN_NUM_QUEUES]; // ... if free_page_reporting { queue_sizes.push(REPORTING_QUEUE_SIZE); }4.2 inflate(充气):真正把内存还给宿主
Guest 驱动通过 inflate 队列(索引 0)把要交还的页帧号(PFN)发给 VMM。处理逻辑见process_queue(balloon.rs#L164-L222):
let range_base = GuestAddress((pfn as u64) << VIRTIO_BALLOON_PFN_SHIFT); let range_len = 1 << VIRTIO_BALLOON_PFN_SHIFT; match queue_index { 0 => { Self::release_memory_range(desc_chain.memory(), range_base, range_len)?; } 1 => { Self::advise_memory_range( desc_chain.memory(), range_base, range_len, libc::MADV_WILLNEED, )?; } _ => return Err(Error::InvalidQueueIndex(queue_index)), }inflate 路径最终调用release_memory_range(balloon.rs#L137-L162),其回收动作分两步:
- 文件后备内存:若内存区域有后备文件(
region.file_offset()),则通过fallocate64以FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE在文件对应偏移处打洞,真正释放底层存储; - 匿名/映射内存:对回收区间执行
madvise(..., MADV_DONTNEED),提示宿主内核这些页可以立即丢弃。
4.3 deflate(放气):把内存归还给 Guest
deflate 队列(索引 1)用于 Guest 需要更多内存时请求放气。从上述代码可见,deflate 对目标区间执行的是madvise(..., MADV_WILLNEED),即提示宿主“这些页即将被访问”,内核据此优先保留或提前准备这些页。
4.4 free page reporting(空闲页上报)
reporting 队列(索引 2)的处理逻辑独立实现于process_reporting_queue(balloon.rs#L224-L246)。与 inflate 不同,reporting 请求中描述符本身携带的是内存区间(基地址 + 长度),VMM 直接对每个区间调用release_memory_range完成回收。这也解释了为什么该特性不改变 balloon 大小——它走的是独立的队列与独立的回收语义。
4.5 配置空间的num_pages与actual
设备配置空间结构VirtioBalloonConfig(对应 Linux 内核头文件virtio_balloon.h,见 balloon.rs#L84-L98):
pub struct VirtioBalloonConfig { // Number of pages host wants Guest to give up. num_pages: u32, // Number of pages we've actually got in balloon. actual: u32, }num_pages:宿主期望 Guest 交出的页数(即上文由size换算而来);actual:Guest 实际已放入 balloon 的页数,由 Guest 驱动写回,偏移为 4 字节处(常量CONFIG_ACTUAL_OFFSET)。设备只允许 Guest 写入该字段,其他偏移的写入会被拒绝(balloon.rs#L465-L495);get_actual()把actual左移 12 位还原为字节数,供 VMM 查询当前实际回收量(balloon.rs#L417-L419)。
五、运行时动态调整:HTTP API 与resize流程
balloon 的价值不仅体现在启动参数,更在于运行时不中断 VM 即可调整大小。CubeSandbox Hypervisor 通过管理 HTTP API 的resize接口暴露该能力。
VmResizeData结构体(hypervisor/vmm/src/api/mod.rs#L225-L230)同时支持 CPU、内存与 balloon 三项的调整:
#[derive(Clone, Deserialize, Serialize, Default, Debug)] pub struct VmResizeData { pub desired_vcpus: Option<u8>, pub desired_ram: Option<u64>, pub desired_balloon: Option<u64>, }运行时调整的完整调用链为:
- 客户端向
PUT /api/v1/vm.resize发送{"desired_balloon": <字节数>}(API 定义见 hypervisor/vmm/src/api/openapi/cloud-hypervisor.yaml); - VMM 在 hypervisor/vmm/src/vm.rs#L1414-L1426 中调用
device_manager.resize_balloon(desired_balloon); resize_balloon最终落到设备层(hypervisor/vmm/src/device_manager.rs#L4434-L4440 → balloon.rs#L404-L414):
pub fn resize(&mut self, size: u64) -> Result<(), Error> { self.config.num_pages = (size >> VIRTIO_BALLOON_PFN_SHIFT) as u32; if let Some(interrupt_cb) = &self.interrupt_cb { interrupt_cb .trigger(VirtioInterruptType::Config) .map_err(Error::FailedSignal) } else { Ok(()) } }设备更新num_pages后通过Config 中断通知 Guest 驱动,驱动随即按新的目标值通过 inflate/deflate 队列执行实际的页面移交,整个过程无需重启或暂停 VM。
另一个值得注意的细节在 hypervisor/vmm/src/vm.rs#L1421-L1425:VMM 会同步更新 VM 配置中的 balloon 大小,确保 VM 重启后仍沿用最近一次调整后的值,避免运行时调整在重启后丢失。
六、配置校验与边界条件
在启动配置阶段,VMM 会对 balloon 与内存的关系做一致性校验。从 hypervisor/vmm/src/config.rs#L2612-L2627 的源码可见,若balloon.size >= ram_size,会触发BalloonLargerThanRam错误——即balloon 的大小必须小于 RAM 总大小。这与文档中“size 会被从 VM 总内存中扣除”的语义一致:扣减量不可能等于或超过总量本身。
此外还需留意一个实践细节:deflate_on_oom与free_page_reporting是否真正生效,取决于 Guest 内驱动是否协商了对应特性位。宿主机侧开启特性只是前提条件,完整链路需要 Guest 内核 ≥ 相应版本并启用对应配置(例如 Linux 内核的CONFIG_VIRTIO_BALLOON与 free page reporting 支持)。
七、典型实践场景小结
| 场景 | 推荐配置 | 说明 |
|---|---|---|
| 多租户超卖,需要明确扣减 Guest 内存 | --balloon size=1G | 固定回收 1GiB,Guest 可用内存 = RAM − 1GiB |
| 高负载 VM,担心 OOM | --balloon size=2G,deflate_on_oom=on | 平时扣减 2GiB,Guest OOM 时允许自动取回 |
| 纯空闲页回收,不占用 Guest 内存 | --balloon size=0,free_page_reporting=on | 借助 reporting 队列回收 Guest 已释放页 |
| 运行中按负载伸缩 | PUT /api/v1/vm.resize携带desired_balloon | 无需重启,Config 中断驱动动态充放气 |
本文所述内容均可在 CubeSandbox 仓库对应源码与文档中交叉验证:官方参数说明见 hypervisor/docs/balloon.md,设备实现见 hypervisor/virtio-devices/src/balloon.rs,参数解析见 hypervisor/vmm/src/config.rs,运行时调整逻辑见 hypervisor/vmm/src/vm.rs 与 hypervisor/vmm/src/api/mod.rs。若需了解内存管理的整体设计(包括内存热插拔与 virtio-mem 等更灵活的方案),可继续阅读同目录下的 hypervisor/docs/memory.md 与 hypervisor/docs/hotplug.md。
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考