StratoVirt 基于 Rust 的 balloon 功能实践
StratoVirt 是计算产业中面向云数据中心的企业级虚拟化 VMM,实现了一套架构统一支持虚拟机、容器、Serverless 三种场景。StratoVirt 在轻量低噪、软硬协同、Rust 语言级安全等方面具备关键技术竞争优势。
背景介绍:
通常,在同一台服务器上存在着不同的用户,而多数用户对内存的使用情况是一种间断性的使用。也就是说用户对内存的使用率并不是很高。在服务器这种多用户的场景中,如果很多个用户对于内存的使用率都不高的话,那么会存在服务器实际占用的内存并不饱满这样一种情况。实际上各个用户使用内存的分布图可能如下图所示(黄色部分表示 used 部分,绿色部分表示 free 的部分)。
解决方案:
为了解决上述服务器上内存使用率低的问题,可以将虚拟机中暂时不用的内存回收回来给其他虚拟机使用。而当被回收内存的虚拟机需要内存时,由 host 再将内存归还回去。有了这样的内存伸缩能力,服务器便可以有效提高内存的使用率。在 StratoVirt 中,我们使用 balloon 设备来对虚拟机中的空闲内存进行回收和释放。下面详细了解一下 StratoVirt 中的 balloon 设备。
balloon 设备简介:
由于 StratoVirt 只是负责为虚拟机分配内存,只能感知到每个虚拟机总的内存大小。但是在每个虚拟机中如何使用内存,内存剩余多少。StratoVirt 是无法感知的,也就无法得知该从虚拟机中回收多少内存了。为此,需要在虚拟机中放置一个“气球(balloon)”设备。该设备通过 virtio 半虚拟化框架来实现前后端通信。当 Host 端需要回收虚拟机内部的空闲内存时,balloon 设备“充气”膨胀,占用虚拟机内部内存。而将占用的内存交给 Host 使用。如果虚拟机的空闲内存被回收后,虚拟机内部由于业务要求突然需要内存时。位于虚拟机内部的 balloon 设备可以选择“放气”缩小。释放出更多的内存空间给虚拟机使用。
balloon 实现:
balloon 的具体代码实现位于 StratoVirt 项目的/virtio/src/balloon.rs 文件中,相关细节可阅读代码理解。代码架构如下:
virtio
├── Cargo.toml
└── src
├── balloon.rs
├── block.rs
├── console.rs
├── lib.rs
├── net.rs
├── queue.rs
├── rng.rs
├── vhost
│ ├── kernel
│ │ ├── mod.rs
│ │ ├── net.rs
│ │ └── vsock.rs
│ └── mod.rs
├── virtio_mmio.rs
└── virtio_pci.rs
由于 balloon 是一个 virtio 设备,所以在前后端通信时也使用了 virtio 框架提供的 virtio queue。当前 StratoVirt 支持两个队列:inflate virtio queue(ivq)和 deflate virtio queue(dvq)。这两个队列分别负责 balloon 设备的“充气”和“放气”。
气球的充放气时,前后端的信息是通过一个结构体来传递。
struct VirtioBalloonConfig {
/// Number of pages host wants Guest to give up.
pub num_pages: u32,
/// Number of pages we've actually got in balloon.
pub actual: u32,
}
因此后端向前端要内存的时候,只需要修改这个结构体中的 num_pages 的数值,然后通知前端。前端读取配置结构体中的 num_pages 成员。并与本身结构体中的 actual 对比,判断是进行 inflate 还是 deflate。
-
inflate
如果是 inflate,那么虚拟机以 4k 页为单位去申请虚拟机内存,并将申请到的内存地址保存在队列中。然后通过 ivq 将保存了分配好的页面地址的数组分批发往后端处理(virtio queue 队列长度最大 256,也就是一次最多只能传输 1M 内存信息,对于大于 1M 的内存只能分批传输)。后端通过得到信息后,找到相应的 MemoryRegion,将对应的 page 标记为”WILLNEED“。然后通知前端,完成配置。
-
deflate
如果是 deflate 则从保存申请到的内存地址队列中弹出一部分内存的地址。通过 dvq 分批次传输给后端处理。后端将 page 标记为“DONTNEED"。
下面结合代码进行说明:
定义 BalloonIoHandler 结构体作为处理 balloon 事件的主体。
struct BalloonIoHandler {
/// The features of driver.
driver_features: u64,
/// Address space.
mem_space: Arc<AddressSpace>,
/// Inflate queue.
inf_queue: Arc<Mutex<Queue>>,
/// Inflate EventFd.
inf_evt: EventFd,
/// Deflate queue.
def_queue: Arc<Mutex<Queue>>,
/// Deflate EventFd.
def_evt: EventFd,
/* 省略 */
}
其中包含上述的两个 virtio 队列inf_queue
和def_queue
,以及对应的触发事件描述符(EventFd)inf_evt
和def_evt
。两个队列均使用了Mutex
锁,保证了队列在同一时刻只有一个使用者对该队列进行操作。保证了多线程共享的数据安全。
fn process_balloon_queue(&mut self, req_type: bool) -> Result<()> {
let queue = if req_type {
&mut self.inf_queue
} else {
&mut self.def_queue
}; //获得对应的队列
let mut unlocked_queue = queue.lock().unwrap();
while let Ok(elem) = unlocked_queue
.vring
.pop_avail(&self.mem_space, self.driver_features)
{
match Request::parse(&elem) {
Ok(req) => {
if !self.mem_info.has_huge_page() {
// 进行内存标记
req.mark_balloon_page(req_type, &self.mem_space, &self.mem_info);
}
/* 省略 */
}
Err(e) => {
/* 省略错误处理 */
}
}
}
/* 省略 */
}
当相应的EventFd
被触发后process_balloon_queue
函数将会被调用。通过判断请求类型确定是“充气”还是”放气“,然后再从相应的队列中取数据进行内存标记。其中while let
是 Rust 语言提供的一种循环模式匹配机制。借助该语法可以将队列中 pop 出来的所有数据遍历取出到elem
中。
内存标记及优化:
标记内存在mark_balloon_page
函数中进行实现,起初的实现思路为:将虚拟机传送过来的地址逐个进行标记。即,从队列中取出一个元素,转化为地址后立即进行标记。后来经过测试发现:balloon 设备在对页地址进行一页一页标记内存时花费时间巨大。而同时也发现通过虚拟机传回来的地址中有大段的连续内存段。于是通过改变标记方法:由原来的一页一页标记改为将这些连续的内存统一标记。大大节省了标记时间。下面代码为具体实现:
fn mark_balloon_page(
&self,
req_type: bool,
address_space: &Arc<AddressSpace>,
mem: &BlnMemInfo,
) {
let advice = if req_type {
libc::MADV_DONTNEED
} else {
libc::MADV_WILLNEED
};
/* 略 */
for iov in self.iovec.iter() {
let mut offset = 0;
let mut hvaset = Vec::new();
while let Some(pfn) = iov_to_buf::<u32>(address_space, iov, offset) {
offset += std::mem::size_of::<u32>() as u64;
let gpa: GuestAddress = GuestAddress((pfn as u64) << VIRTIO_BALLOON_PFN_SHIFT);
let hva = match mem.get_host_address(gpa) {
Some(addr) => addr,
None => {
/* 略 */
}
};
//将hva地址保存在hvaset的vec中
hvaset.push(hva);
}
//对hvaset进行从小到大排序。
hvaset.sort_by_key(|&b| Reverse(b));
/* 略 */
//将hvaset中连续的内存段进行标记
while let Some(hva) = hvaset.pop() {
if last_addr == 0 {
free_len += 1;
start_addr = hva;
} else if hva == last_addr + BALLOON_PAGE_SIZE {
free_len += 1;
} else {
memory_advise(
start_addr as *const libc::c_void as *mut _,
(free_len * BALLOON_PAGE_SIZE) as usize,
advice,
);
free_len = 1;
start_addr = hva;
}
if count_iov == iov.iov_len {
memory_advise(
start_addr as *const libc::c_void as *mut _,
(free_len * BALLOON_PAGE_SIZE) as usize,
advice,
);
}
count_iov += std::mem::size_of::<u32>() as u64;
last_addr = hva;
}
/* 略 */
}
}
}
首先将 virtio 队列中的地址全部取出,并保存在 vec 中,然后将该 vec 进行从小到大的排序。有利于快速找出连续的内存段并进行标记。由于 hvaset 中的地址是按照从小到大排列的,因此可以从头开始遍历 hvaset,遇到不连续的地址后将前面的连续段进行标记。这样就完成了由原来逐页标记到连续内存段统一标记的优化。
经过测试,StratoVirt 的 balloon 速度也有了极大的提高。
关注我们
StratoVirt 当前已经在 openEuler 社区开源。后续我们将开展一系列主题分享,如果您对 StratoVirt 的使用和实现感兴趣,欢迎您围观和加入。
项目地址:https://gitee.com/openeuler/stratovirt
项目 wiki:https://gitee.com/openeuler/stratovirt/wikis
您也可以订阅邮件列表:https://mailweb.openeuler.org/postorius/lists/virt.openeuler.org/
如有疑问,也欢迎提交 issue:https://gitee.com/openeuler/stratovirt/issues
入群
如果您对虚拟化技术感兴趣,可以进入 openEuler StratoVirt 主页查找相关资源(点击阅读原文进入项目主页,https://www.openeuler.org/zh/other/projects/stratovirt/),包括安装指导、虚拟机配置、代码仓库、学习资料等。也欢迎加入Virt SIG 技术交流群,讨论 StratoVirt、KVM、QEMU 和 Libvirt 等相关虚拟化技术。感兴趣的同学可以添加如下微信小助手,回复 StratoVirt 入群。
本文分享自微信公众号 - openEuler(openEulercommunity)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
倪光南院士:欧拉与鸿蒙协同发展构建未来操作系统新生态
近年来,产业界在国家政策方针的指导下,在相关部门的带领下,通过产业链协同共建,通过开源开放共享的方式,打通操作系统产业“政产研学用”各环节,形成了有效合力,极大地促进了我国操作系统产业的跨越式发展。 “欧拉和鸿蒙两大操作系统相继开源。日前,在操作系统产业峰会 2021 上,华为携手社区全体伙伴共同将欧拉开源操作系统(openEuler,简称‘欧拉’)正式捐赠给开放原子开源基金会。”中国工程院院士倪光南表示,这是我国国产操作系统发展的新的历史时刻,欧拉将进入快速发展和推广阶段,为我国各行各业大量关乎国计民生的数字基础设施提供强大的支撑。 欧拉是一个开源操作系统,支持服务器、云计算、边缘计算、嵌入式等应用场景,支持多样性计算,提供安全、稳定、易用的操作系统。通过为应用提供确定性保障能力,支持 OT 领域应用及 OT 与 ICT 的融合。 “今年 6 月,华为已正式推出鸿蒙 2.0 系统。鸿蒙作为面向万物互联时代的智能终端操作系统,为不同设备的智能化、互联与协同提供了统一语言。鸿蒙实现了不同硬件的深度融合,通过无线连接、通过软件定义这些设备可以灵活地构成一个功能强大的超级终端,消费者使用体验...
- 下一篇
StratoVirt 的 virtio 设备模拟是如何实现的
virtio 是一种通用的半虚拟化的 I/O 通信协议,提供了一套前后端 I/O 通信的的框架协议和编程接口。根据该协议实现的设备通过前后端的配合,相比全模拟设备可以大幅减少陷入陷出以及内存拷贝的次数,使 guest 获得高效的 I/O 性能。作为目前虚拟化标准的通用协议规范,经历了 0.95、1.0、1.1 三个版本的演进。根据 0.95 版本实现的称为传统 virtio 设备,1.0 版本修改了一些 PCI 配置空间的访问方式和 virtioqueue 的优化和特定设备的约定,1.1 版本则增加了 packed virtqueue 的支持,详细可以参考官方发布的 virtio 协议规范。 之所以称 virtio 是一种半虚拟化的解决方案,是因为其首先需要在主机侧通过软件创建 virito 的后端设备,其次在 Guest 要有对应的设备前端驱动,前后端通过共享内存进行通信。virtio 规范定义了设备的控制和数据面接口,控制面接口包括设备状态、feature 的协商等,数据面则包括共享内存的数据布局定义以及前后端的通知方式。基于 virtio 协议,目前已衍生出了 virtio-bl...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS8编译安装MySQL8.0.19
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7设置SWAP分区,小内存服务器的救世主