Introduction

  • 背景

    • 容器(Docker/LXC)在工业界广泛应用: Google, AWS, Azure, GCP 都依赖容器;
    • 容器优势:
      • 快速实例化(毫秒~百毫秒)
      • 高密度运行(单机可数千~上万实例)
      • 内存占用小, 镜像体积小
      • 问题
        • 安全性不足:
          • 容器暴露 400+ syscalls, 内核攻击面巨大;
          • 隔离性不如 VM, 常需要"容器运行在 VM 内"来补强;
          • 资源滥用(fork bomb, 文件描述符耗尽)可导致宿主机 DoS;
  • 目标

    • 结合容器的轻量性与 VM 的强隔离;
    • 需求:
      • 毫秒级实例化
      • 高密度运行
      • 小镜像/低内存占用
      • 快速挂起/恢复
      • 保持 VM 的硬件隔离安全性
  • 贡献

    • 分析 Xen 性能瓶颈, 重构控制面:
      • 移除 XenStore (noxs), 消除中心化瓶颈;
      • 新工具链 chaos/libchaos + split toolstack;
    • 提出 Tinyx 工具, 生成极小 Linux VM; 支持 unikernel;
    • 实现 LightVM

Tinyx 与 OverlayFS

  • Tinyx 目标: 构建最小化 Linux VM rootfs;
  • 原理:
    1. OverlayFS = lowerdir (Debian minimal) + upperdir (空目录);
    2. 在合成目录中正常安装 apt-get 包, 写入 upperdir;
    3. 卸载 (unmount) \rightarrow 保留 upperdir 的安装结果;
    4. 将 upperdir 覆盖到 BusyBox rootfs \rightarrow 得到最小 rootfs;
  • 效果: 自动化, 轻量化, 无需完整 Debian 系统;

Noxs 与 Chaos Toolstack

  • 传统 Xen 网络启动流程 (图 7a):

    1. 工具栈写 XenStore \rightarrow 注册新 VM;
    2. 后端驱动监听 XenStore, 返回 event channel & grant ref;
    3. 前端驱动启动后去 XenStore 拉取信息;
    4. 前后端共享内存, 事件通道传递数据;
    • 缺点: 大量 XenStore 交互 (30+ 条记录), 性能瓶颈;
  • LightVM noxs/chaos 流程 (图 7b):

    1. chaos 工具链发 ioctl 请求后端创建设备;
    2. 后端返回 event channel & grant ref;
    3. chaos 通过 hypercall 将信息写入 VM 的 device page;
    4. 前端驱动启动时, 直接映射 device page, 获取通信信息;
    5. 前后端通过事件通道通知, 绕过 XenStore;
  • 优点: 去中心化, 低延迟, 可扩展;

设备页映射 (Device Page Mapping)

  • 概念: hypervisor 为每个 VM 分配一张 只读设备页;
  • 过程:
    • 前端启动时 hypercall 请求地址, 将设备页映射到虚拟地址空间;
    • 前端驱动读取其中的 backend-id, event channel, grant ref;
    • 据此完成 ring buffer 映射和 event channel 绑定;
  • 意义: 设备初始化无需 XenStore, 直接 guest\leftrightarrowhypervisor 通信;

Sysctl 伪设备

  • 用途: 处理 电源管理类操作(suspend/resume/shutdown/migration);
  • 架构:
    • sysctlback 在 Dom0
    • sysctlfront 在 VM
    • 双方通过 共享设备页 + event channel 通信;
  • 在迁移中:
    • chaos 对 sysctlback 发 ioctl \rightarrow 标记 suspend \rightarrow 触发事件;
    • sysctlfront 收到 \rightarrow guest 保存状态并解绑设备页/event channel;
    • VM 进入可迁移状态;

TCP Connection 在迁移中的作用

  • 流程:
    1. chaos 在源端与目标宿主机的迁移守护进程建立 TCP 连接;
    2. 首先传输 guest 配置信息 \rightarrow 目标端预建 domain & devices;
    3. 通过 sysctl 触发 guest suspend;
    4. 使用 libxc 将 VM 内存+CPU 状态通过 TCP 管道传送至目标端;
    5. 目标端 resume VM;
  • 目的:
    • 简单, 通用(任何两台主机都可通信);
    • 提供可靠传输, 保证迁移状态正确;
  • 测试指标: 迁移总时延 = save + transfer + restore;

Checkpointing 与 Migration

  • Checkpoint (Save/Restore):

    • Save = VM 冻结 + 状态写出;
    • Restore = 状态加载 + VM 继续执行;
    • LightVM: Save ~30ms, Restore ~20ms
    • 标准 Xen: Save ~128ms, Restore ~550ms
    • 用途: 单机快速暂停/恢复 VM;
  • Migration:

    • 跨机器: Save + 网络传输 + Restore;
    • LightVM: ~60ms, 不随 VM 数量增加而变慢;
    • 标准 Xen: 数百毫秒~秒级;
    • 用途: 负载均衡, 故障恢复, 能耗优化, 边缘计算, serverless;

VM Migration 的应用场景

  • 负载均衡: 迁移 VM 到空闲主机, 避免过载;
  • 故障恢复 & 维护: 宿主机要重启/打补丁时, 迁移 VM 保证服务不中断;
  • 节能: 低峰时迁移 VM 到少数主机, 其余关机省电;
  • 靠近用户: 边缘计算/5G MEC 中, 把 VM 迁移到更靠近用户的节点, 降低延迟;
  • 高可用: 宿主机预测性故障 \rightarrow 提前迁移 VM;
  • Serverless/JIT 服务: 快速迁移/实例化 VM 以支持函数级弹性;

Requirements

LightVM 设计目标是满足容器的关键特征:

Fast Instantiation(快速实例化)

  • 容器: 100–200ms 内即可运行服务;
  • 传统 VM: 1–2s 启动, 过慢;
  • LightVM: 2.3ms \rightarrow 接近进程级别;
  • 应用: serverless, 按需网络功能, 防火墙;

High Instance Density(高密度实例)

  • 容器: 单机数千以上;
  • 传统 VM: 几十~几百;
  • LightVM: 数千到上万 VM, 资源消耗接近容器;

Pause/Unpause(快速挂起/恢复)

  • 容器: 毫秒级冻结/解冻, 便于资源复用;
  • VM(传统): 挂起/恢复通常要数百毫秒以上;
  • LightVM: 30ms 挂起, 25ms 恢复;

Small Image & Memory(小镜像 & 小内存)

  • 传统 VM 镜像数百 MB~GB, 启动慢;
  • 容器镜像数 MB~几十 MB;
  • LightVM: 镜像 480KB–数十 MB, 运行时内存 3.6MB;
  • 工具: Tinyx + unikernel;

Lightweight VMs

背景与动机

  • 第一步优化目标: 减少 VM 的 镜像大小内存占用;
  • 观察: 大多数容器和 VM 只运行 单一应用;
    • 如果 VM 仅包含应用所需的最小功能, 可以显著降低内存消耗;
  • 本文探索两条路径:
    1. Unikernels: 应用与极简 OS 链接 \rightarrow 专用 VM, 体积仅几 MB;
    2. Tinyx: 自动化工具构建最小化 Linux 发行版 \rightarrow 几十 MB 镜像, 约 30MB RAM 即可运行;

3.1 Unikernels

  • 定义: 将目标应用与极简 OS(如 MiniOS)直接链接, 生成专用 VM;
  • 特点:
    • 没有用户/内核分离, 没有多进程, 极度精简;
    • 适合运行单一任务应用;
  • 实例:
    • ClickOS: 运行 Click modular router 配置;
    • Mirage: 将 OCaml 应用打包为 VM;
    • Mini-OS: Xen 自带的玩具操作系统, 功能有限, 但可快速构建 unikernel;
  • 示例: Daytime unikernel
    • 基于 Mini-OS + lwIP, 仅需 50 行代码实现返回时间的 TCP 服务器;
    • 镜像大小: 480KB (未压缩);
    • 内存占用: 3.6MB(需要轻微修改 Xen toolstack 绕过 4MB 最小限制);
  • 更多应用:
    • TLS 终止代理;
    • Minipython(基于 Micropython 的 unikernel, 用于 AWS Lambda 类似服务);
    • 这些应用镜像约 1MB, 运行内存 ~8MB;
  • 局限:
    • 需要把应用移植到 Mini-OS, 开发成本高;
    • 对依赖 Linux syscall API 的现有应用, 移植困难;

3.2 Tinyx

  • 定位: 在性能与通用性之间的折中:
    • 比 unikernel 通用(无需重写应用);
    • 比完整 Linux VM 更轻量;
  • 输入:
    1. 应用(例如 nginx);
    2. 目标平台(例如 Xen);
  • 输出:
    • 一个最小化的 Linux 发行版 rootfs;
    • 一个优化过的 Linux 内核;

文件系统构建

  • 包含:
    • 应用本体
    • 应用依赖
    • BusyBox(提供基础工具)
  • 依赖解析方法:
    • objdump \rightarrow 提取应用所需库;
    • Debian 包管理器 \rightarrow 解析依赖;
  • 优化:
    • 黑名单: 剔除运行时不必要的安装类依赖(如 dpkg);
    • 白名单: 用户可强制保留某些包;
  • OverlayFS 构建机制:
    1. 在 Debian minimal debootstrap 上挂载一个空 OverlayFS;
    2. 在挂载目录中安装依赖(行为与正常 Debian 相同);
    3. 卸载 OverlayFS \rightarrow upperdir 保留完整, 已配置好的文件;
    4. 删除缓存, dpkg/apt 文件, 多余目录;
    5. 将结果叠加在 BusyBox rootfs 下, 形成最终 Tinyx 发行版;
    6. 在 BusyBox init 中加入 glue 代码启动应用;

内核构建

  • 基线配置: tinyconfig;
  • 根据目标平台加入选项(如 Xen/KVM);
  • 默认禁用:
    • 内核模块支持;
    • 对虚拟化无用的驱动(如裸机硬件驱动);
  • 可选优化: 逐一禁用用户指定的内核选项 \rightarrow 重编译 \rightarrow 启动测试 \rightarrow 不影响运行则保留禁用;
  • 效果:
    • Tinyx 内核体积比 Debian kernel 小一半;
    • 运行内存占用: 1.6MB (Tinyx) vs 8MB (Debian);

Virtualization Today

背景

Xen 是一种在生产环境广泛使用的 type-1 hypervisor(如 Amazon EC2);

  • Xen 的 hypervisor 本身仅有约 11.4K LoC, 可信计算基较小, 隔离性强;
  • 对比: KVM 基于 Linux 内核, 可信计算基更大;
  • 本节以 Xen 为研究对象, 分析现有虚拟化技术在轻量 VM 场景下的性能表现;

4.1 Short Xen Primer

  • Xen hypervisor 只负责最基本的资源管理(CPU, 内存等);
  • Xen 启动后会自动创建 Dom0:
    • Dom0 一般运行 Linux, 包含工具栈 (xl, libxl, libxc) 用于 VM 创建/迁移/关闭;
    • Dom0 还运行 XenStore: 一个中心化注册表, 记录 VM 与设备信息, 提供 watch/callback 机制;
    • Dom0 还托管物理设备驱动, 以及软件交换机(如 Open vSwitch);
  • Xen 采用 split driver 模型:
    • 后端驱动 (netback) 在 Dom0;
    • 前端驱动 (netfront) 在来宾 VM;
    • 两者通过共享内存和 event channels(软件中断) 通信;

4.2 Overhead Investigation

实验环境: Intel Xeon E5-1630 v3 (3.7GHz) + 128GiB DDR4, Xen 与 Linux 版本 4.8;

测试方法

  • 顺序启动 1000 个 VM, 测量:
    • 创建时间 (create time): toolstack 准备 VM 的耗时;
    • 启动时间 (boot time): VM 内核启动到可用的耗时;
  • 三类 VM:
    • Debian minimal VM(1.1GB);
    • Tinyx VM(9.5MB);
    • Daytime unikernel(480KB);
  • 另外对比: Docker 容器, Linux 进程;
  • 镜像存放在 RAM disk, 避免 I/O 干扰;

实验结果

  • Debian VM: 创建 ~500ms, 启动 ~1.5s;
  • Tinyx VM: 创建 ~360ms, 启动 ~180ms;
  • Unikernel: 创建 ~80ms, 启动 ~3ms;
  • Docker 容器: 启动 ~200ms;
  • Linux 进程 (fork/exec): 启动 ~3.5ms(均值), 90 分位数 ~9ms;

随着 VM 数量增加, 创建时间显著上升:

  • 创建第 1000 个 VM: Debian 需 42s, Tinyx 需 10s, Unikernel 需 700ms;
  • 进程/容器的创建时间几乎不随数量增加而变化;
  • 结论: 随着 VM 变得轻量, VM 实例化 (instantiation) 成为主要瓶颈;

开销分解

通过对 xllibxl 插桩, 发现创建 VM 的开销主要分布在:

  • 解析配置文件;
  • hypervisor 交互(分配内存, 创建 vCPU);
  • XenStore 写入(例如 VM 名称, 设备信息);
  • 虚拟设备的创建与配置;
  • 内核镜像解析与加载;
  • 工具栈内部状态维护;

结果(Figure 5):

  • 主要瓶颈: XenStore 交互 + 虚拟设备创建;
  • 其他部分(解析 config, hypervisor 交互等)可以忽略不计;

XenStore 成本原因

  • 协议开销大: 每次读写都需要多次消息往返和软件中断;
  • 写入 VM 名称时需与所有已有 VM 比较 \rightarrow O(n) 成本;
  • 设备初始化需要写多个记录, 并保证事务原子性;随着 VM 增多, 事务冲突频繁, 导致重试;
  • XenStore 会记录所有访问到 20 个日志文件, 定期 log rotation \rightarrow 导致性能尖峰;

LightVM

目标与总体设计

  • 目标: 让 VM 启动时间接近进程启动时间(毫秒级), 而不仅是优化现有 Xen 代码路径;
  • 痛点: XenStore 的集中式, 类文件系统 API 太慢, VM 创建/启动期间需要几十次软件中断与特权域切换; 与之对比, fork 只需一次用户态\rightarrow内核态跨越;
  • 方案: 重构 Xen 控制面, 形成 LightVM 架构(见 Figure 6):
    • noxs: 取消 VM 创建/启动时对 XenStore 的依赖, 前后端驱动通过共享内存直接交换信息, 减少中断与域切换;
    • chaos/libchaos: 精简的工具栈, 替换 xl/libxl;
    • split toolstack: 将创建流程拆为 prepareexecute 两阶段, 显著减少"命令触发时"的工作量;
    • xendevd: 二进制守护进程, 替换慢速的 bash 热插拔脚本, 快速把虚拟接口加入软件交换机或完成块设备设置;
    • libxc: 仍用于保存/恢复/迁移的数据面传输;

5.1 Noxs(No XenStore)与 Chaos 工具栈

  • 标准 Xen(Figure 7a)中:
    • 工具栈向 XenStore 写入"需要网卡"的条目; 后端(netback)监听目录, 分配 event channelgrant reference 并回写到 XenStore; 来宾启动后由前端(netfront)到 XenStore 取回参数完成初始化;
    • 实际创建流程会交互 30+ 个 XenStore 项目, VM 数量越多越慢;
  • 核心洞见: hypervisor 已经掌握大多数必要信息(如 VM id), 可把其扩展为"集中存储"的实现载体, 规避 XenStore;
  • LightVM(Figure 7b)做法:
    • libchaos/chaos 替换 libxl/xl, 彻底移除 libxs/XenStore 依赖;
    • 为每个 VM 分配一页"设备页 (device page)": 记录该 VM 的设备信息(block/net 的 backend-id, event channel, grant ref);
    • 新增 hypercall: Dom0 可写该页(受控), 来宾以只读方式映射;
    • 创建流程:
      1. chaos create 先通过 noxs 内核模块 ioctl 向后端请求创建设备, 后端返回通信细节(event channel, grant ref等);
      2. 工具栈调用 hypercall 将这些细节写入该 VM 的设备页;
      3. 来宾启动时通过 hypercall 向 hypervisor 要到设备页地址并 映射到自身地址空间(Linux 与 Mini-OS 均做了前端修改);
      4. 前端用设备页里的信息完成 grant 映射event channel 绑定, 随后前后端互换状态(如网卡 MAC)并通过事件通道做通知(取代 XenStore watches);
    • 无 XenStore 的迁移: 新增 sysctl 伪设备(front/back + 设备页 + 事件通道)承载电源类操作:
      • chaos 与目标机 迁移守护进程建立 TCP 连接, 先发送客体配置让对端预创建 domain 与设备;
      • 源端通过对 sysctlback 发 ioctl 触发 suspend(在共享页打上"suspend"理由并触发事件通道);
      • 来宾 保存内部状态并解绑 noxs 相关资源; 随后用 libxc 传输来宾数据到远端;

5.2 Split Toolstack(准备/执行分离)

  • 观察: 大量创建时执行的代码 对所有 VM 通用或对一类配置通用, 没必要在每次"创建命令"时现算;
  • 做法(Figure 8):
    • libchaos 替换标准工具栈, 并拆为两阶段:
      • Prepare(后台守护进程 chaos daemon 周期执行): 预做与 VM 通用的工作(如让 hypervisor 生成 ID, 准备管理信息, 分配 CPU 资源等), 生成一批 VM shells 放入池中, 数量可配置, 始终保持可用;
      • Execute(收到创建命令时): chaos 解析配置, 向守护进程要一个匹配需求的 shell, 在其上完成剩余的 VM 专属步骤(装载内核镜像, 设备收尾初始化), 然后 boot;
  • 效果: 将大量与"具体 VM 无关"的慢路径前移, 极大缩短创建命令的关键路径;

5.3 替换热插拔脚本: xendevd

  • 标准 Xen 中, 虚拟设备创建常需用户态脚本(xl 直接调 bash 或 udevd 触发同样脚本), 单次脚本启动与执行要数十毫秒, 放大启动时延;
  • 方案: 实现 xendevd 二进制守护进程, 直接监听后端的 udev 事件并执行 预定义的快速设置, 无需 fork/bash, 显著减小设备接入软件交换机/块设备准备的时间;

组合效果(Figure 9)

  • 对 daytime unikernel, 1,000 个实例创建时间对比:
    • xl(标准 Xen): 最慢;
    • chaos [XS](仅换工具栈, 仍用 XenStore)\rightarrow 更快;
    • chaos [XS+split](再加拆分工具栈)\rightarrow 继续下降;
    • chaos [NoXS](去 XenStore)\rightarrow 明显下降;
    • LightVM(NoXS + split + xendevd 全开)\rightarrow 最快, 且随已运行 VM 数量增长更平缓;
  • 直观结论: 去 XenStore, 拆分工具栈, 替换热插拔脚本 三者叠加, 才能把 VM 创建时延压低到毫秒级并具备良好扩展性;