12.xen
背景
应用设计需要一个稳定的环境, 而传统的 OS 隔离技术不足以支持这种要求, 比如一个程序消耗了大量的算力并且被 scheduler 赋予了非常高的优先级, 或者系统被程序攻击了, 从而被某个其他进程消耗了很多资源导致当前进程的性能受损, 因此需要更高层的 VM 设计来实现工业界需要的 isolation 级别
同时为了能够承载各种不同来源的软件 (有些软件跑在 linux 上, 有些在 BSD/Windows XP 上), 所以这个虚拟机的设计应当让多种 guest OS 都能以 minimal effort porting 来运行, 也就是不应该强迫所有运行的系统都要提供一致的 app interface
传统的 VM 设计都会带来显著的 Performance Overhead, 但是随着硬件平台的发展, 我们应该尽量设计出一个低 overhead 的 VM 方案
关键词:
- isolation
- performance (limited overhead)
- portability
- tradeoff btw Heavyweight and Isolation
Intro + Design Ideas
- 传统 VMM 的问题
- 传统 VMM 提供与真实硬件完全相同的抽象(full virtualization), 虽然能支持未修改的操作系统, 但在 x86 架构下效率低, 复杂度高;
- 例如
x86架构中一些特权指令执行失败时不会自动 trap, 导致难以拦截;- 高效虚拟化 MMU 困难;
- VMware ESX 通过动态二进制翻译和 shadow page tables 来解决, 但代价很大(代码重写, 缓存, 频繁 trap);
- full-virtualize 的额外缺陷
- 某些情况下, guestOS 需要 同时 看到真实与虚拟资源 (如真实时间用于 TCP 超时估算, 真实物理地址用于 superpages 或 page coloring 优化);
- 完全隐藏真实资源会影响正确性与性能;
- Xen 的解决思路: 半虚拟化(paravirtualization)
- Xen 提供的虚拟机抽象与真实硬件"相似但不完全相同";
- 需要对客体 OS 做修改, 但 无需修改应用二进制接口 (ABI), 因此应用无需改动;
- 好处是性能更高, 资源隔离更强;
-
Xen 的设计原则
-
必须支持未修改的应用程序二进制; 也就是当前系统应该支持完全没有修改的 guest OS 生态
-
必须支持完整的多应用操作系统, 以便在单个 VM 中运行复杂服务器;
-
在 x86 等"难虚拟化"架构上, 高性能和强隔离依赖
paravirtualization; -
即便在 “好虚拟化” 的架构上, 完全屏蔽虚拟化效果也会带来正确性与性能风险;
机制与策略分离
- 机制(mechanism) = 执行面/数据面: 必须在 hypervisor 内完成, 并且需要强隔离与强一致的动作, 例如:
- 把 CPU 时间片真正分给哪个域(调度执行)
- 过滤即将发送的网络包(按已设定的规则逐包检查)
- 读写磁盘块时做访问控制(检查是否有权限访问这个块)
- 策略(policy) = 决策面/控制面: 不需要也不应该放在 hypervisor 里, 例如:
- "CPU 要如何分配? "(比例/权重/上限)
- "哪些报文类型允许发送? "(ACL, 反欺骗, 限速)
- "谁能挂载哪个虚拟磁盘? 只读还是可写? "
好处: hypervisor 变小, 可信计算基小(TCB 小), 可维护性与可扩展性强;策略更新在上层软件改就行, 无需动 hypervisor;
简单地说, 就是 Xen 只管 mechanism, 而把 policy 留给上层的管理域(Domain0)去实现
管理结构
- Xen (hypervisor): 只提供基础控制原语(create/destroy, 设置权重/上限, 授予/回收资源, 建立/删除虚拟设备等)和必需的数据面检查/执行;
- Domain0 (管理域): 开机创建的特权域, 被授予访问控制接口(control interface)的权限;它运行一个普通的客体 OS(常见是 Linux)+ 一整套管理工具链(管理守护进程/CLI/脚本), 在这里实现"策略逻辑":
- 如 admission control(是否允许再创建一个域? ), 配额/计费, 服务编排, 报警与自动伸缩, 迁移编排等;
- 其他域(DomU): 运行业务的普通 VM, 由 Domain0 通过控制接口创建, 配置与监管;
控制接口的功能
- domain 生命周期与资源配额
- 创建/销毁域
- 设置调度参数(如权重, 上限, 亲和/绑核等)
- 分配物理内存(以及后续 balloon driver/回收)
- 授权对物理网卡/磁盘的间接访问权限(通过虚拟设备)
- 虚拟网络接口 (VIF) 与虚拟块设备 (VBD)
- 创建/删除 VIF: 定义这个 domain 的虚拟网卡, 并配置访问控制(例如: 允许的协议/端口, 是否开启源地址 anti-spoofing, 带宽上限/过滤规则等)
- 创建/删除 VBD: 把后端的物理分区/文件卷暴露成该 domain 可见的"虚拟磁盘", 可设置 只读/读写 等限制
- 观测与统计
- hypervisor 向控制接口提供系统状态与画像(per-domain 的网络/CPU/内存指标, 按包/按流的网络统计等)
- Domain0 的管理工具据此动态决策 (例如发现拥塞或超配时自动调权, 限速或迁移)
Design Details
控制转移机制
Xen 定义了两种机制:
- Hypercalls (同步调用)
- Events (异步通知)
Hypercalls (类比系统调用)
- 是什么: Guest OS 主动调用 Xen 的接口, 类似 应用程序调用系统调用进入内核;
- 本质: 一次软件 trap, 从较低特权级跳到 Xen(Ring 0);
- 用途:
- 用于需要立即处理的 特权 (priviledge) 操作;
- 例如: Guest OS 想更新页表 发起 hypercall Xen 验证并应用更新 返回结果给 Guest OS;
- 用于需要立即处理的 特权 (priviledge) 操作;
- 特点:
- sync: 调用必须等 Xen 处理完成后才能继续执行;
- 受限接口: 只能调用 Xen 提供的有限操作, 保证安全;
Events (类比中断/信号)
- 是什么: Xen 异步通知 Guest OS 某个事件发生;
- 类似于: 中断 (interrupt) 通知设备完成/有新数据
- Unix 信号 (signal) 通知进程发生特定事件
- 用途:
- Guest OS 不需要主动轮询, 而是等事件触发:
- 网络: 新数据包到达
- 磁盘: I/O 请求完成
- 控制: VM 被请求终止
实现机制:
- 每个 domain 维护一个 事件 bitmask, 标记待处理的事件;
- Xen 在事件发生时更新 bitmask, 然后调用 Guest OS 注册的回调函数 (event-callback handler);
- 回调函数负责清除 pending 事件, 并执行相应处理逻辑;
bitmask 指的是每一位对应一种事件类型, 例如:
-
bit0 网络有数据到达
-
bit1 磁盘 I/O 完成
-
bit2 domain 被请求终止
-
延迟处理: Guest OS 可以显式"屏蔽事件"(设置一个 Xen 可读的软件标志位), 类似"关中断", 等准备好时再处理;
-
好处: 避免在高负载或临界区时被频繁打断;
I/O Rings
- 在 Xen 架构中, Guest OS 不能直接操作硬件设备, 必须通过 Xen 这个中间层;
- 这就需要一种高效, 安全的数据传输机制, 让数据能在 Guest OS Xen 硬件 之间流动;
I/O ring 的结构 (循环队列 + 描述符)
I/O ring 是一个 环形队列 (circular queue), 存放的是 I/O 描述符 (descriptor):
- 描述符 (descriptor):
- 不存放实际 I/O 数据, 只是一个指针/引用, 指向 Guest OS 已分配的 buffer;
- buffer 在 Guest OS 的内存中, Xen 通过描述符找到并操作它;
- 双向 ring(请求和响应):
- 请求队列 (request queue):
- Guest OS 在 ring 中写入请求描述符(比如"读磁盘第 100 块到 buffer A");
- Xen 取走请求, 执行操作;
- 响应队列 (response queue):
- Xen 把结果写入 ring(比如"磁盘第 100 块读完了"), 并返回请求的 ID;
- Guest OS 读取响应, 确认完成;
- 请求队列 (request queue):
- 指针机制(类似生产者-消费者模型):
- 请求生产者 (Request Producer): Guest OS 写入请求时更新;
- 请求消费者 (Request Consumer): Xen 取走请求时更新;
- 响应生产者 (Response Producer): Xen 写入响应时更新;
- 响应消费者 (Response Consumer): Guest OS 读到响应时更新;
设计优点:
- 异步操作 (Asynchronous operation)
- Guest OS 发出请求后, 不必等待立即完成, 可以继续执行别的工作;
- Xen 完成后再通过响应告诉 Guest OS;
- 乱序处理(Out-of-order execution)
- 请求带有唯一 ID;
- Xen 可以为了性能(比如磁盘调度时做顺序优化)而调整执行顺序, 但最终会通过 ID 匹配响应;
- 零拷贝 (Zero-copy)
- 因为数据直接存放在 Guest OS 的 buffer 里, Xen 不用复制, 只需把硬件 DMA/结果写入那块内存;
- 通用性
- 网络 I/O: Guest OS 先提供 buffer, Xen 在接收到网络包时写入这些 buffer, 并在响应中通知;
- 磁盘 I/O: Guest OS 提交"读写请求", Xen 调度后把结果写回 buffer 并回应;
- 延迟与吞吐的权衡 (latency vs throughput trade-off)
- Guest OS 可以积累多个请求后, 再一次性 (batch) hypercall 通知 Xen 减少交互次数, 提升吞吐;
- 同样, Xen 可以延迟事件通知, 等响应累计到一定数量再通知 避免 Guest OS 被频繁唤醒;
Subsystem Virtualization
CPU Scheduling
- Xen 使用 Borrowed Virtual Time (BVT) 调度算法;
- 特点:
- Work-conserving: 如果有空闲 CPU, 总是会调度可运行的域, 不会浪费资源;
- 低延迟唤醒 (fast dispatch): 当某个域收到事件(例如网络包), 能快速被调度运行;
- 这是通过 virtual-time warping 技术实现的: 临时打破"理想公平调度", 优先运行刚被唤醒的域;
- 好处: 对 TCP 协议很重要, 能及时发 ACK, 不会因为延迟而误判 RTT;
- 可扩展性: 调度器是通用框架, 其他算法也可以很容易替换或实现;
- 策略接口: 每个域的调度参数(如权重, 配额)可由 Domain0 的管理软件动态调整;
Time and Timers
- Xen 提供三种时间概念:
- Real time: 自机器启动以来的纳秒数(可与外部时钟源同步);
- Virtual time: 域在 CPU 上运行的时间(只在执行时前进, 用于 Guest OS 调度);
- Wall-clock time: 实际时钟(real time + offset, 可随时调整而不影响 real time);
- 每个 Guest OS 可以设置两个闹钟(alarm timer):
- 一个基于 real time
- 一个基于 virtual time
- Guest OS 自己维护内部的定时器队列, 最早的超时由 Xen 的 alarm 来触发;
- 超时事件通过 event 机制 交付给 Guest OS;
Virtual Address Translation(虚拟地址转换)
- 问题: x86 使用硬件页表, 难以高效虚拟化;
- VMware 的做法: 用 shadow page tables, Guest OS 看一个假的页表, hypervisor 同步更新到真实页表;
- 缺点: 复杂 & 慢(创建新地址空间时开销大, 还要同步 accessed/dirty 位);
- Xen 的做法:
- 避免 shadow page table, 直接让 Guest OS 管理真实页表;
- 限制: Guest OS 只能读, 写操作必须通过 hypercall 交给 Xen 验证;
- 安全机制:
- Xen 给每个物理页框分配 类型 (type) 和 引用计数 (ref count):
- 类型包括 Page Directory (PD), Page Table (PT), LDT, GDT, RW(可写)
- 页框在同一时间只能属于一种类型
- 例如: 不能既是 PT 又是 RW
- 页框分配给页表用途时, 需要一次性验证所有条目 然后"pin"成 PT/PD 类型
- 只有当 引用计数=0 且 unpin 时, 页框才能改用途
- 批量更新优化: Guest OS 可以先在本地缓存页表修改, 再用一个 hypercall 一次性提交, 减少开销;
- 正确性保证: 通常 Guest OS 会在 切换地址空间前 执行 TLB flush 确保缓存一致;
- 如果没 flush, 可能会触发缺页异常, Guest OS 的 fault handler 会检查是否有未提交的更新并立即提交;
Physical Memory(物理内存虚拟化)
- 初始分配: 每个域在创建时就被分配一部分物理内存(reservation);
- 动态调整:
- 可以在 reservation 上限内申请更多内存;
- 也可以主动释放内存回给 Xen;
- Balloon Driver(气球驱动):
- XenoLinux 使用 balloon driver 在 OS 内部动态调整内存占用;
- 通过 OS 自己的内存分配接口"吹大或放气", 而不是改 Linux 内核深处;
- 进一步扩展: guest OS 的 OOM 机制可以自动触发向 Xen 申请更多内存;
- 稀疏内存模型 (sparse memory model):
- Xen 不保证连续物理内存;
- Guest OS 自己维护一个"伪物理内存"的映射表, 把稀疏的 machine memory 映射为连续的 pseudo-physical memory;
- Xen 提供 machinephysical 的共享转换表, 保证 guest OS 查表时能拿到正确结果;
- 优化:
- Guest OS 在少数情况下会用真实 machine 地址(比如 cache 优化, superpage 分配);
- 大多数时候, OS 只用 pseudo-physical 地址, 不感知底层稀疏分配;
Network(虚拟网络)
- Virtual Firewall-Router (VFR)
- Xen 把网络抽象为一个 虚拟防火墙路由器 (VFR);
- 每个域(VM)通过一个或多个 虚拟网卡 (VIF) 接入 VFR;
- VIF = 现代网卡的简化版:
- 传输 (Transmit) 队列
- 接收 (Receive) 队列
- 每个方向都有一组 规则 (
, ), 用于过滤和转发;
- 策略配置
- Domain0 负责安装/删除规则;
- 常见规则:
- 防止 IP 源地址欺骗 (spoofing)
- 根据目的 IP/端口做正确的分流 (demultiplexing)
- 防火墙规则, 例如禁止某些端口的连接
- 发送 (Transmit) 流程
- Guest OS 把 buffer descriptor 放入 transmit ring;
- Xen 拷贝描述符, 并拷贝 header (不是整个包);
- Xen 检查匹配规则, 决定如何处理;
- payload 不用拷贝 直接用 scatter-gather DMA 从 buffer 传输;
- 为了安全, 包所在的页必须 pin 住直到传输完成;
- Xen 用 简单的 round-robin 在不同 VIF 之间调度, 保证公平;
- 接收 (Receive) 流程
-
Guest OS 必须预先提供 页对齐的空 buffer, 放在 receive ring;
-
当网络包到达:
- Xen 检查接收规则 找到目标 VIF;
- 把这个包直接放入 Guest OS 提供的 buffer(实现零拷贝);
- 如果没有可用 buffer, 则丢弃数据包;
Disk(虚拟磁盘)
- Virtual Block Devices (VBD)
- 只有 Domain0 能直接访问物理磁盘 (IDE/SCSI);
- 其他域通过 虚拟块设备 (VBD) 使用存储;
- VBD 的组成:
- 一组 extents(磁盘区域)
- 每个 extent 带有 所有权和访问控制信息
- VBD 的创建/配置由 Domain0 完成(通过控制接口);
- 好处: Xen 本身保持简单, 不用像 Exokernel 那样搞复杂的 UDF(用户级设备框架);
- 工作流程
- Guest OS 的磁盘请求通过 I/O ring 传递给 Xen;
- Guest OS 在提交前可能自己做 请求重排(如优先元数据写, 延迟预读);
- Xen 接收到请求:
- 根据 VBD ID + offset 查 翻译表 (translation table), 得到实际物理扇区;
- 做 权限 检查(确认该域有权访问该区域);
- 使用 DMA 在磁盘和 pinned memory pages 之间做 zero-copy 数据传输;
- Xen 会做 进一步的调度与批处理 (batching):
- 从不同域收集请求 round-robin 批处理保证公平;
- 最终交给硬件层 由标准的 电梯算法 (elevator scheduler) 优化磁头移动;
- Guest OS 可以下发 reorder barrier, 告诉 Xen “这些请求必须按顺序执行”(例如写日志);
