1: 苹果对虚拟化技术的支持
硬件方面: 苹果的M1芯片提供了特殊的硬件来支持虚拟的硬件和CPU, 并且一个SoC(System on Chip, 系统级芯片)上面可以运行多个操作系统.
软件方面: 用户无需自己手动去实现kernel extension(KEXTs) 等, 这些都已经在framework中内置好了. 为了在我们的app中增加虚拟机功能, 可以用Hypervisor framework来虚拟化内存和CPUs. 但是Hypervisor framework是一套比较底层的API, 我们需要手写大量的代码来描述所需的虚拟环境.
所以在WWDC22中介绍了了另一种虚拟化框架叫做Virtualization framework.
2: 在工程中引入Virtualization framework
为了能够使用Virtualization API, app必须要加上com.apple.security.virtualization
entitlement.
- 1:
project->Signing & Capabilities->App Sandbox会创建出一个entitlement文件, 如果你的app已经有了entitlement文件就忽略这个步骤
- 2: 选中entitlement文件, 点击
+增加新的entitlement: 把com.apple.security.virtualization设置值为YES.
- 3:在code中
import Virtualization
然后就可以使用对应的API了
3: 利用Virtualization framework框架制作MacOS 虚拟机
Virtualization framework支持:
- 1: 在装有
Apple silicon芯片的Mac上制作MacOS虚拟机 - 2: 在装有
Apple silion或者Intel芯片的电脑上制作Linux虚拟机 当使用该框架时, 我们需要处理/实现两种objects
1: 虚拟机的配置
可以通过configuration来定义我们所需虚拟机的属性(properities), 可以定义虚拟机的需要多少个CPU, 需要多大的内存, 键盘/显示器信息等等. 如同下图所示的一些信息.
接下来, 我们了解一下如何做代码里完成一个虚拟机的简单配置:
import Cocoa
import Virtualization
class AppDelegate: NSObject {
private func setupVM(with image: VZMacOSRestoreImage) {
let config = VZVirtualMachineConfiguration()
config.cpuCount = 4
// gigabytes 千兆
config.memorySize = (4 * 1024 * 1024 * 1024) as UInt64
let keyboard = VZUSBKeyboardConfiguration()
config.keyboards = [keyboard]
let pointingDevice = VZUSBScreenCoordinatePointingDeviceConfiguration()
config.pointingDevices = [pointingDevice]
}
}
2: 虚拟机对象
定义了我们应该如何和虚拟机交互
在代码里可以先create出一个virtual machine 实例, 然后在调用start()函数启动它.
- 1: 我们需要将虚拟机的内容显示出来, 所以需要在
view层实现VZVirtualMachineView(跟NSView类似)这个对象.
let virtualMachineView = VZVirtualMachineView()
virtualMachineView.virtualMachine = virtualMachine
- 2: 配置
platform, platform是一个对象用来存储虚拟机硬件的所有properties, 他包含上面所说的configuration, 例如来设置hardwareModelandauxiliaryStorage.
let platform = VZMacPlatformConfiguration()
// 硬件模型: 制定了我们想要什么版本的虚拟机
let hardwareModel = VZMacHardwareModel(dataRepresentation: savedHardwareModel)
platform.hardwareModel = hardwareModel!
// 辅助存储器: 辅助存储器是系统使用的一种非易失性存储器
// 在虚拟机中, 辅助存储器是由本地电脑上的文件所支持, 所以直接用本地文件的url给它初始化
let auxiliaryStorage = VZMacAuxiliaryStorage(contentsOf: auxiliaryStorageURL)
platform.auxiliaryStorage = auxiliaryStorage
// 机器的标识符, 就像每台实体的电脑都会有自己的序列号一样
let machineIdentifier = VZMacMachineIdentifier(dataRepresentation: savedIdentifier)
platform.machineIdentifier = machineIdentifier!
configuration.platform = platform
- 3: 配置好
platform之后, 还需要增加一点代码去启动我们的虚拟机, 这里用到的API是VZMacBootLoader.
configuration.bootLoader = VZMacOSBootLoader()
经过上面三个步骤, 我们给虚拟机完成了配置并且启动了它, 接下来还要试着给虚拟机安装存储镜像:
- 4: 下载虚拟机的存储镜像
我们可以在
developer website网站上下载restore images(https://updates.cdn-apple.com/2022SpringFCS/fullrestores), 现在也可以通过API:VZMacOSRestoreImage来实现
WWDC22中给出的代码:
// 得到一个Mac os最新稳定版本的restore images
let restoreImage = try await VZMacOSRestoreImage.latestSupported
// restoreImage有url的属性
try await download(restoreImage.url)
但是如果你只写这两行肯定会面临着编译失败或者运行异常, 这里download可以用URLSession来实现. 这里我贴出可以成功编译运行的代码块:
// 这里fatalError和completeHandler都需要自己去定义
// 如果需要高端一点, 可以实现一个下载进度条, 因为这个RestoreImage.ipsw很大, 大概有13多个G
// 获取到restoreImage对象
guard let restoreImage = try? await VZMacOSRestoreImage.latestSupported
else
{
fatalError("No restore image is supported.")
return;
}
print("\(restoreImage.url)")
// 下载。。。。
guard let (location, _) = try? await
URLSession.shared.download(from: restoreImage.url) else {
fatalError("Failed to download the macOS image from the network.)
return
}
// 定义下载好的restoreImage存储到本地的哪个位置
let restoreImageURL = URL(fileURLWithPath: NSHomeDirectory() + "/RestoreImage.ipsw")
// 保存restoreImage
guard ((try? FileManager.default.moveItem(at: location, to: restoreImageURL)) != nil)
else
{
fatalError("Failed to move the macOS image to its destination.")
return
}
completeHandler();
- 5: 创建一个和虚拟机
兼容的configuration, 这个configuration和上面步骤二里的configuration并不相同, 这里的configuratio是软件的配置, 步骤二里面的是硬件配置.
// 可以通过restoreImage的 object来check configuration要求
let requirements = restoreImage.mostFeaturefulSupportedConfiguration
// 通过requirement, 可以知道镜像上可以运行的Mac os的版本
guard let requirements = requirements else {
// No compatible configuration.
fatalError("No compatible configuration")
return
}
platform.hardwareModel = requirements.hardwareModel
// requirement同时也包含了两个有用的properties
// 1: 运行这个版本的Mac OS系统需要多少CPUs
configuration.cpuCount = requirements.minimumSupportedCPUCount
// 2: 运行这个版本的Mac OS系统需要多少内存
configuration.memorySize = requirements.minimumSupportedMemorySize
小建议: 这里需要多少个的CPU或者内存, 其实可以不用在代码里面写死. 可以在UI层增加一些button让用户自己去选, 例如下面这样:
- 6: 安装虚拟机到
restore image上
// 1: 创建虚拟机
let virtualMachine = VZVirtualMachine(configuration: configuration)
// 2: 创建安装器, 包含两个参数. 第一个是虚拟机对象, 第二个是前面下载好的restore image
let installer = VZMacOSInstaller(virtualMachine: virtualMachine,
restoringFromImageAt: imageURL)
// 3: 开始安装
try await installer.install()
- 7: GPU加速
通过上面的步骤, 已经构建了一个具有
GPU的虚拟机. 这意味着可以在虚拟中使用Metal渲染 并且在机器上可以得到出色的图像性能, 下面来实现一下:
// 新建一个VZMacGraphicsDeviceConfiguration
let graphicsConfiguration = VZMacGraphicsDeviceConfiguration()
// 显示出来
graphicsConfiguration.displays = [
VZMacGraphicsDisplayConfiguration(widthInPixels: 1920,
heightInPixels: 1200,
pixelsPerInch: 80)
]
configuration.graphicsDevices = [graphicsConfiguration]
- 8: 给虚拟机添加
trackpad在MacOS 13 Ventura里, 我们可以给虚拟机添加trackpad, 这意味着我们可以用一些手势, 例如旋转,缩小放大等. 这个代码比较简单:
let trackpad = VZMacTrackpadConfiguration()
configuration.pointingDevices = [trackpad]
- 9: 小应用: 在虚拟机和本地电脑之间传递文件
在
MacOS 12里,苹果引入了Virtio file-system来实现Linux虚拟机上共享文件, 在MacOS 13 Ventura里, WWDC22里苹果宣布Mac虚拟机也可以实现共享文件. 可以选择要与虚拟机共享的文件夹, 那么对主机(Host mac)系统所做的任何更改都会立即反映在虚拟机中,反之亦然。API为VZShareDirectory代码如下:
let sharedDirectory = VZSharedDirectory(url: directoryURL, readOnly: false)
// 共享单个的文件夹用`VZSingleDirectoryShare`
// 共享多个文件夹用`VZMultipleDirectoryShare`
let share = VZSingleDirectoryShare(directory: sharedDirectory)
// 创建device
// 创建tag, 通过tag, 可以告诉虚拟机自动挂载这个device
let tag = VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag
let sharingDevice = VZVirtioFileSystemDeviceConfiguration(tag: tag)
sharingDevice.share = share
// 添加到configuration
configuration.directorySharingDevices = [sharingDevice]
4: 利用Virtualization framework框架制作Linux 虚拟机
1: 安装Linux驱动
当我们想在实体机上安装Linux系统时, 首先要做的是用installer下载ISO文件, 然后用ISO文件擦除闪存驱动器, 最后在电脑上安装驱动并启动它. 当把Linux安装到虚拟机上时,整个步骤差不多, 只是我们会用虚拟的USB驱动.
接下来用代码实现一下.
// 1: 先下载好linux.iso, 然后得到得到下载好的iso文件的path url
let diskImageURL = URL(fileURLWithPath: "linux.iso")
// 2: 然后从iso create出一个磁盘映像的附件, 这个附件代表我们可以附加一些存储空间到device上
let attachment = try! VZDiskImageStorageDeviceAttachment(url: diskImageURL, readOnly: true)
// 3: 想使用USB存储驱动, 所以用VZUSBMassStorageDeviceConfiguration
let usbDeviceConfiguration = VZUSBMassStorageDeviceConfiguration(attachment: attachment)
// 4: 将device加到上面创建出来main configuration中
configuration.storageDevices = [usbDeviceConfiguration, createBlockDevice()]
现在我们有了一个USB的驱动, 现在需要从USB里面去启动Linux.
2: 启动Linux
在MacOS Ventura中, Apple增加了对EFI的支持, EFI是一套启动Intel或者Arm硬件设备的行业标准.
EFI有一个启动发现机制, 也就是说会自动发现installer或者USB 驱动. EFI会遍历所有的驱动找到一个可以启动的. 所以installer会告诉EFI接下来会用什么驱动. 安装之后EFI会启动Linux.
接下来在代码里实现这一过程:
// 创建EFI对象
let efi = VZEFIBootLoader()
// EFI需要非易失性内存来寻处启动器之间的信息, 对于虚拟机来说我们可以把这些存储在本地的文件系统里
efi.variableStore = VZEFIVariableStore(creatingVariableStoreAt: storeURL,
options: [])
// 放到main configuration里.
configuration.bootLoader = efi
4: Linux VM的图形功能.
在MacOS Ventura中, 苹果增加了对Virtio GPU 2D的支持. Virtio GPU 2D 是一个半虚拟化设备, 可以在实体Mac os里面显示出Linux的界面. Linux 会渲染内容, 并把渲染的帧提供给Virtualization framework, 然后Virtualization framework会显示出来. 接下来我们来在代码里配置一下:
let virtioGPU = VZVirtioGraphicsDeviceConfiguration()
// 在Virtio的术语中, 虚拟显示器的术语是scanout.
virtioGPU.scanouts = [
VZVirtioGraphicsScanoutConfiguration(widthInPixels: 1280, heightInPixels: 720)
]
configuration.graphicsDevices = [virtioGPU]
5: Rosetta 2
Rosetta 2是一个翻译器, 我们现在可以在M1的机器上运行x86_64的bundle都是因为这个软件的存在. Rosetta 2 可以在虚拟机内部翻译x86_64的Linux. 如果虚拟机上面安装了Rosetter 2 , 这意味着我们也可以在上面运行Arm的Linux. 同时Rosetter翻译的速度很快, 所以Linux系统会有出色的性能表现. 下面用代码来给之前配置的Mac Os虚拟机来添加Rosetter功能:
let rosettaDirectoryShare = try! VZLinuxRosettaDirectoryShare()
let directorySharingDevice = VZVirtioFileSystemDeviceConfiguration(tag: "RosettaShare")
directorySharingDevice.share = rosettaDirectoryShare
configuration.directorySharingDevices = [directorySharingDevice]
下面我们将Rosetter和Linux系统关联起来, 可以使用update-binfmts告诉系统使用Rosetter来处理任何的x86_64文件.
sudo /usr/sbin/update-binfmts --install rosetta /mnt/Rosetta/rosetta \
--magic "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00" \
--mask "\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff" \
--credentials yes --preserve no --fix-binary yes
5: 总结:
通过Virtualization framework 可以轻松的创建出了一个Mac OS的虚拟机, 并且创建出来的虚拟机拥有很快的速度. github上有一些出色的例子,可以自己试一下: github.com/ming900518/…