Highlight
macOS 27 的 Virtualization 框架新增了四项核心能力:自动配置 macOS 客户机用户账户、通过 Accessory Access 框架安全直通 USB 设备、DiskImageKit 分层稀疏磁盘镜像、以及自定义 Virtio 设备,让第三方虚拟机 App 在自动化、存储效率和硬件扩展性上达到生产级水准。
核心内容
自动配置 macOS 客户机:告别手动点 Setup Assistant
以前装完 macOS 虚拟机,必须手动走一遍 Setup Assistant(设置助理)——创建用户、设密码、点同意条款。对于 CI/CD 流水线或自动化测试场景,这一步是最大瓶颈。每次新建 VM 都要人工介入,无法实现真正的无人值守。
macOS 27 引入了 VZMacGuestProvisioningOptions。启动 VM 时传入用户名、密码、全名,还可以开启自动登录和 SSH 远程登录。客户机首次启动时,这些参数直接传给 Setup Assistant,自动完成配置。
(01:56)
有个限制:这些选项只在客户机尚未配置过时生效。如果已经创建过用户,后续启动传入的 provisioning 选项会被忽略。密码处理上,Apple 建议从 Keychain 或环境变量读取,不要硬编码。
USB 设备直通:Accessory Access 框架的安全模型
虚拟机里用 U 盘、加密狗、调试器,以前是个老大难问题。要么靠底层 IOKit hack,要么体验不稳定,拔插时 VM 容易卡死。
macOS 27 引入了 Accessory Access 框架。它的核心设计是:用户始终拥有控制权。App 注册感兴趣的设备类型后,macOS 菜单栏会出现一个配件图标。用户手动选择”连接到 App”,设备才会被授权给虚拟机使用。用户也可以随时断开。
(04:39)
这种设计牺牲了全自动化的便利性,换取了安全性和稳定性。设备可以在 VM 运行时热插拔,不需要重启或修改 VM 的静态配置。对 C 端用户友好,也杜绝了恶意软件偷偷读取 USB 设备的风险。
分层磁盘镜像:DiskImageKit 解决存储空间问题
传统 raw 磁盘镜像是一比一映射的。100GB 的逻辑磁盘就是 100GB 的物理文件。做快照需要复制整个文件,克隆 VM 的成本极高。
macOS 27 的 DiskImageKit 框架引入了 Apple Sparse Image Format(ASIF),支持分层镜像栈。底层是 base layer(基础层),只读,存放干净的系统镜像。上层可以是 cache layer(缓存层)或 overlay layer(覆盖层),都是 ASIF 格式。
(11:40)
Cache layer 用于加速:底层数据在慢速存储(如网络文件系统)上时,读取过的数据会被缓存到 cache layer,后续读取直接从缓存拿。Overlay layer 实现写时复制(copy-on-write):写入操作只发生在 overlay 层,base 层保持只读不变。多个 VM 可以共享同一个 base layer,各自拥有独立的 overlay,大幅节省磁盘空间。
ASIF 是稀疏格式。逻辑大小可以远大于实际存储的数据量,未使用的块不占物理空间。
自定义 Virtio 设备:打通 Host-Guest 高性能通道
Virtualization 框架已经内置了常见的设备类型,但有些场景需要更 specialized 的通信通道。比如实现自定义协议做 Host-Guest 高性能通信,或者把机器学习加速器暴露给 Linux 客户机。
macOS 27 开放了 VZCustomVirtioDevice。Virtio 是半虚拟化设备的行业标准,通过共享内存缓冲区和 Virtio queue 实现 Host-Guest 通信,设计上最小化上下文切换次数。
(15:57)
App 配置设备的 Virtio device ID、PCI class/subclass、queue 数量,然后设置 delegate。客户机通过 Virtio queue 发送数据时,delegate 的 didReceiveNotificationFor 被调用,App 从 queue 中取出元素处理,处理完再归还到 queue。App 也可以通过触发中断来通知客户机驱动。
详细内容
自动配置 macOS 客户机
(01:56)
import Virtualization
let provisioningOptions = VZMacGuestProvisioningOptions()
provisioningOptions.fullName = fullName
provisioningOptions.username = username
provisioningOptions.password = password
provisioningOptions.logsInAutomatically = true
provisioningOptions.enablesRemoteLogin = true
let startOptions = VZMacOSVirtualMachineStartOptions()
try startOptions.setGuestProvisioning(provisioningOptions)
try await virtualMachine.start(options: startOptions)
关键点:
VZMacGuestProvisioningOptions封装了首次启动时需要传递给 Setup Assistant 的所有参数fullName、username、password用于创建用户账户logsInAutomatically = true让系统首次启动后自动登录到该账户enablesRemoteLogin = true开启 SSH 服务,方便远程管理setGuestProvisioning把配置附加到启动选项上- 这些参数只在客户机首次启动、尚未配置用户时生效
注册 USB 配件监听器
(07:12)
import AccessoryAccess
let criteria: [AAUSBAccessoryMatchingCriteria] = []
let accessories = try await AAUSBAccessoryManager.shared.registerListener(self, matchingCriteria: criteria)
for accessory in accessories {
// Handle previously attached accessories.
}
关键点:
AAUSBAccessoryMatchingCriteria用于过滤感兴趣的设备类型,可以按 device class、vendor ID、product ID 等条件筛选- 传入空数组表示对所有 USB 设备感兴趣
registerListener会返回已经授权给 App 的设备列表,需要遍历处理- 监听器对象需要实现
AAUSBAccessoryListener协议 - 需要在 Xcode Target 的 Capabilities 中添加 “Claim USB Accessory”
响应 USB 配件连接
(07:39)
import AccessoryAccess
import Virtualization
class AccessoryListener: NSObject, AAUSBAccessoryListener {
func usbAccessoryDidConnect(_ usbAccessory: AAUSBAccessory) {
virtualMachine.queue.async {
do {
let configuration = VZUSBPassthroughDeviceConfiguration(device: usbAccessory)
let device = try VZUSBPassthroughDevice(configuration: configuration)
self.virtualMachine.usbControllers.first?.attach(device: device) { error in
// Handle error if necessary...
}
} catch {
// Handle error...
}
}
}
}
关键点:
usbAccessoryDidConnect在用户通过菜单栏授权设备后触发- 所有 VM 硬件修改必须在
virtualMachine.queue上执行,这是线程安全要求 VZUSBPassthroughDeviceConfiguration把AAUSBAccessory包装成 VM 可识别的配置VZUSBPassthroughDevice是实际的直通设备对象attach(device:)把设备动态挂载到 VM 的 USB 控制器上- 用户随时可以通过菜单栏断开设备,App 需要实现
usbAccessoryDidDisconnect做清理
创建自定义 vmnet 网络
(10:04)
import Virtualization
import vmnet
var status: vmnet_return_t = .VMNET_FAILURE
guard let networkConfiguration =
vmnet_network_configuration_create(.VMNET_SHARED_MODE, &status) else { ... }
guard let network =
vmnet_network_create(networkConfiguration, &status) else { ... }
let attachment = VZVmnetNetworkDeviceAttachment(network: network)
let networkDeviceConfiguration = VZVirtioNetworkDeviceConfiguration()
networkDeviceConfiguration.attachment = attachment
virtualMachineConfiguration.networkDevices = [networkDeviceConfiguration]
let virtualMachine = VZVirtualMachine(configuration: virtualMachineConfiguration)
关键点:
vmnet_network_configuration_create创建网络配置对象,.VMNET_SHARED_MODE是共享模式- 可以通过 vmnet API 进一步配置 DHCP 参数、端口转发规则等
vmnet_network_create从配置创建网络对象VZVmnetNetworkDeviceAttachment桥接 vmnet 网络和 Virtualization 框架- 多个 VM 使用同一个
network对象就能互相通信 - vmnet 网络是引用计数的 Objective-C 对象,App 退出后网络消失,需要自行持久化配置
- 通过
vmnet_network_copy_serialization和vmnet_network_create_with_serialization可以跨 XPC 传递网络对象
DiskImageKit 分层镜像
(14:54)
import DiskImageKit
import Virtualization
let baseImage = try DiskImage(opening: .open(url: baseLayerURL, mode: .readOnly))
let cacheImage = try baseImage.appending(.asifLayer(url: cacheLayerURL, type: .cache))
let overlayImage = try DiskImage(opening: .open(url: overlayLayerURL))
let stackedImage = try cacheImage.appending(overlayImage)
let storageDeviceAttachment = try VZDiskImageStorageDeviceAttachment(diskImage: stackedImage)
let storageDeviceConfiguration =
VZVirtioBlockDeviceConfiguration(attachment: storageDeviceAttachment)
virtualMachineConfiguration.storageDevices = [storageDeviceConfiguration]
let virtualMachine = VZVirtualMachine(configuration: virtualMachineConfiguration)
关键点:
DiskImage(opening: .open(url:mode:))打开基础镜像,.readOnly确保 base layer 不会被修改.appending(.asifLayer(url:type:))追加 ASIF 格式的层,.cache类型用于性能缓存- 也可以追加另一个
DiskImage对象作为 overlay layer - 读取时 DiskImageKit 从上到下遍历层栈,找到第一个包含目标块的层
- 如果有 cache 层,读取底层数据时会自动缓存到 cache 层
- 遇到 overlay 层时,写入操作会存储在 overlay 中,实现写时复制
VZDiskImageStorageDeviceAttachment把 DiskImageKit 镜像包装成 VM 存储设备附件- 层栈越浅性能越好,建议保持最少层数
- 多个 VM 共享 base layer 时,需要各自复制辅助存储文件(auxiliary storage)和 EFI 变量存储文件
配置自定义 Virtio 设备
(17:41)
import Virtualization
let deviceConfiguration = VZCustomVirtioDeviceConfiguration()
deviceConfiguration.deviceID = 4
deviceConfiguration.pciClassID = 0x10
deviceConfiguration.pciSubclassID = 0x00
deviceConfiguration.virtioQueueCount = 1
deviceConfiguration.provider =
VZCustomVirtioDeviceDelegateProvider(deviceQueue: deviceQueue, delegate: provider)
virtualMachineConfiguration.customVirtioDevices = [deviceConfiguration]
let virtualMachine = VZVirtualMachine(configuration: virtualMachineConfiguration)
关键点:
deviceID = 4对应 Virtio entropy 设备,自定义设备需要选择未被占用的 IDpciClassID = 0x10是 PCI crypto 设备类别pciSubclassID = 0x00是网络与计算加密控制器子类virtioQueueCount设置 Virtio queue 数量,根据设备需求配置VZCustomVirtioDeviceDelegateProvider把 delegate 和 queue 绑定到设备配置- 自定义设备需要客户机侧有对应的 Virtio 驱动才能工作
Virtio 设备 Delegate
(18:20)
import Virtualization
class DeviceConfigurationDelegate: NSObject, VZCustomVirtioDeviceConfigurationDelegate {
func customVirtioConfiguration(_ deviceConfiguration: VZCustomVirtioDeviceConfiguration,
didCreateDevice device: VZCustomVirtioDevice) {
device.delegate = deviceDelegate
self.device = device
}
}
关键点:
VZCustomVirtioDeviceConfigurationDelegate在 VM 启动、设备创建完成后被调用- 在这里设置设备的 delegate,处理后续的 Virtio queue 事件
- 保留
device引用,以便后续主动触发客户机中断
处理 Virtio Queue 元素
(18:42)
import Virtualization
class DeviceDelegate: NSObject, VZCustomVirtioDeviceDelegate {
func customVirtioDevice(_ device: VZCustomVirtioDevice,
didReceiveNotificationFor queue: VZVirtioQueue) {
while let element = queue.nextElement() {
// Process element...
element.returnToQueue()
}
}
}
关键点:
didReceiveNotificationFor在客户机驱动向 queue 写入数据时触发queue.nextElement()取出 queue 中的元素,返回 nil 时 queue 为空- 处理完元素后必须调用
element.returnToQueue()归还到 queue - 自定义设备需要客户机侧编写对应的 Virtio 驱动
- 设计驱动时应遵循 Virtio 最佳实践,充分利用 queue 的批处理能力
核心启发
1. CI/CD 无人值守 macOS 测试集群
用 VZMacGuestProvisioningOptions 实现 VM 的 zero-touch 初始化。结合 DiskImageKit 的分层镜像,一个 base 镜像供所有测试节点共享,每个节点只需几 MB 的 overlay。测试结束后删除 overlay 即可恢复干净状态。
入口:VZMacGuestProvisioningOptions + VZDiskImageStorageDeviceAttachment
2. 带 USB 设备直通的开发环境
做嵌入式开发或硬件测试时,经常需要在虚拟机里用 USB 调试器、加密狗、固件烧录器。以前只能装双系统或买物理机。现在用 Accessory Access 框架,用户手动授权后设备直通到 VM,热插拔稳定可靠。
入口:AAUSBAccessoryManager.shared.registerListener + VZUSBPassthroughDevice
3. 微服务本地联调网络
用 vmnet 框架创建自定义网络拓扑,把宿主机的特定端口转发到 VM 内部。比如宿主机 8080 映射到 VM 的 80,本地浏览器直接访问宿主机的 localhost:8080 就能调试 VM 里的 Web 服务。
入口:vmnet_network_configuration_create + DHCP 配置 + 端口转发规则
4. 基于分层镜像的”快照即恢复”系统
用 DiskImageKit 的 overlay layer 做快照。每次测试前基于 clean base 创建新 overlay,测试后可以选择保留(快照)或丢弃(恢复)。overlay 是稀疏文件,创建成本极低。
入口:DiskImage + .appending(.asifLayer) + overlay layer 管理
5. Linux VM 里的自定义硬件加速
通过 VZCustomVirtioDevice 把宿主机的专用硬件(如 NPU、FPGA、自定义协处理器)暴露给 Linux 客户机。编写对应的 Linux Virtio 驱动,实现 Host-Guest 低延迟通信。
入口:VZCustomVirtioDeviceConfiguration + VZCustomVirtioDeviceDelegate
关联 Session
- 389 - Create and manage container machines with Virtualization — Container Machine 是 Virtualization 框架的另一种使用形态,与本 session 的进阶能力互补
- 260 - Explore the new Device Hub in Xcode — Device Hub 涉及设备管理与远程调试,与 VM 中的设备直通场景相关
- 243 - Profile and optimize your Metal app with Instruments — VM 中运行 Metal 应用时的性能分析工具
- 279 - What’s new in RealityKit — VM 中的 Metal 特性支持(argument buffers、indirect command buffers)与图形渲染相关
- 262 - What’s new in Swift — 本 session 的代码示例大量使用 Swift 的 async/await 和结构化并发
评论
GitHub Issues · utterances