开发实践 v1.0 - 引导启动 Ubuntu
linuxvirtualization
概述
本文档基于 Apple Virtualization 框架的实践经验,涵盖在 macOS 上创建和管理 Linux 虚拟机的完整技术方案。内容包括两种主流引导方式的对比、核心 API 使用、设备配置、常见问题排查及最佳实践。
1. 框架概述
Apple Virtualization 框架 (Virtualization.framework) 是 macOS 原生的虚拟化 API,支持在 Apple Silicon 和 Intel Mac 上运行 Linux 虚拟机。
1.1 核心能力
| 能力 | 描述 |
|---|---|
| CPU/内存配置 | 灵活分配虚拟 CPU 核心数和内存大小 |
| 存储设备 | 支持 virtio 块设备、USB 存储 |
| 网络 | NAT 网络、桥接网络 |
| 图形显示 | VirtIO GPU,支持 GUI 桌面 |
| 输入设备 | USB 键盘、鼠标/触控板 |
| 串口控制台 | 用于无 GUI 环境的命令行交互 |
1.2 必要权限
在 Entitlements 文件中添加:
<key>com.apple.security.virtualization</key> <true/>
2. 引导方式详解
2.1 方式对比
| 特性 | EFI 引导 (VZEFIBootLoader) | 直接内核引导 (VZLinuxBootLoader) |
|---|---|---|
| 入口 | UEFI 固件 → 引导加载器 → 内核 | 直接加载 Linux 内核 |
| 典型用途 | 完整发行版安装、桌面系统 | 云镜像、开发测试、CI/CD |
| ISO 安装 | ✅ 支持 | ❌ 不支持 |
| 启动速度 | 较慢(完整引导链) | 快速 |
| 状态持久化 | 需要 EFI 变量存储 + MachineIdentifier | 不需要 |
| 用户门槛 | 低(标准安装流程) | 高(需提供内核/initrd/cmdline) |
2.2 EFI 引导实现
适用于需要从 ISO 安装完整 Linux 发行版的场景。
// 1. 创建平台配置 let platform = VZGenericPlatformConfiguration() platform.machineIdentifier = loadOrCreateMachineIdentifier() // 2. 创建 EFI 引导器 let bootLoader = VZEFIBootLoader() bootLoader.variableStore = loadOrCreateEFIVariableStore() // 3. 应用到配置 config.platform = platform config.bootLoader = bootLoader // 4. 挂载 ISO 作为 USB 存储(首次安装) if let isoURL = installISOURL { let isoAttachment = try VZDiskImageStorageDeviceAttachment( url: isoURL, readOnly: true ) let usbStorage = VZUSBMassStorageDeviceConfiguration(attachment: isoAttachment) config.storageDevices.append(usbStorage) }
关键持久化文件:
MachineIdentifier.bin- 机器标识,确保 VM 身份一致NVRAM.bin- EFI 变量存储,保存引导配置
2.3 直接内核引导实现
适用于云镜像或需要快速迭代的开发场景。
// 1. 创建 Linux 引导器 let bootLoader = VZLinuxBootLoader(kernelURL: kernelURL) // 2. 配置 initrd(可选) bootLoader.initialRamdiskURL = initrdURL // 3. 设置内核命令行 bootLoader.commandLine = "console=hvc0 root=/dev/vda1 rw" // 4. 应用到配置 config.bootLoader = bootLoader
内核命令行常用参数:
| 参数 | 说明 |
|---|---|
console=hvc0 | 输出到 virtio 控制台 |
root=/dev/vda1 | 指定根文件系统分区 |
rw | 以读写模式挂载根文件系统 |
cloud-init=disabled | 禁用 cloud-init(避免网络等待) |
init=/bin/bash | 进入单用户模式(调试用) |
3. 核心 API 参考
3.1 配置类层次
VZVirtualMachineConfiguration ├── platform: VZPlatformConfiguration │ └── VZGenericPlatformConfiguration ├── bootLoader: VZBootLoader │ ├── VZEFIBootLoader │ └── VZLinuxBootLoader ├── storageDevices: [VZStorageDeviceConfiguration] ├── networkDevices: [VZNetworkDeviceConfiguration] ├── graphicsDevices: [VZGraphicsDeviceConfiguration] ├── keyboards: [VZKeyboardConfiguration] ├── pointingDevices: [VZPointingDeviceConfiguration] └── serialPorts: [VZSerialPortConfiguration]
3.2 虚拟机生命周期
class VMInstance: NSObject, VZVirtualMachineDelegate { private var virtualMachine: VZVirtualMachine? enum State { case stopped, starting, running, paused, error(String) } // 启动 func start() async throws { let config = try createConfiguration() try config.validate() virtualMachine = VZVirtualMachine(configuration: config) virtualMachine?.delegate = self try await virtualMachine?.start() } // 暂停 func pause() async throws { try await virtualMachine?.pause() } // 恢复 func resume() async throws { try await virtualMachine?.resume() } // 停止 func stop() async throws { try await virtualMachine?.stop() } // 代理方法 func virtualMachine(_ vm: VZVirtualMachine, didStopWithError error: Error?) { // 处理停止或错误 } func guestDidStop(_ virtualMachine: VZVirtualMachine) { // 客户机主动关机 } }
3.3 显示视图集成 (SwiftUI)
import SwiftUI import Virtualization struct VMDisplayView: NSViewRepresentable { let virtualMachine: VZVirtualMachine func makeNSView(context: Context) -> VZVirtualMachineView { let view = VZVirtualMachineView() view.virtualMachine = virtualMachine view.capturesSystemKeys = true return view } func updateNSView(_ nsView: VZVirtualMachineView, context: Context) { nsView.virtualMachine = virtualMachine } }
4. 项目架构设计
4.1 单 VM 演示架构(Apple 示例风格)
┌─────────────────────────────────────────────┐ │ AppDelegate │ │ ┌─────────────────────────────────────┐ │ │ │ VZVirtualMachineConfiguration │ │ │ │ - EFI Boot + ISO Install │ │ │ │ - Graphics/Audio/Network/SPICE │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ VZVirtualMachine │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ VZVirtualMachineView (主窗口) │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ 特点: - 应用启动即创建/启动 VM - VM 停止时退出进程 - 适合演示和单一用途
4.2 多 VM 管理架构
┌──────────────────────────────────────────────────────────┐ │ VMManager │ │ ┌────────────────────────────────────────────────────┐ │ │ │ instances: [VMInstance] │ │ │ │ - 多实例列表管理 │ │ │ │ - 配置持久化 (vms.json) │ │ │ │ - CRUD 操作 │ │ │ └────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ VMInstance │ │ VMInstance │ │ VMInstance │ │ - 状态机 │ │ - 状态机 │ │ - 状态机 │ │ - 生命周期 │ │ - 生命周期 │ │ - 生命周期 │ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ UI Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ VMList │ │ Detail │ │Display/Console│ │ │ │ View │ │ View │ │ Windows │ │ │ └──────────┘ └──────────┘ └──────────────┘ │ └─────────────────────────────────────────────┘ 特点: - 支持多 VM 同时管理 - 显式状态控制 (start/stop/pause/resume) - 独立的显示和控制台窗口 - 配置持久化到 Application Support
4.3 推荐目录结构
LinuxVMManager/ ├── Models/ │ ├── VMConfiguration.swift # VM 配置模型 │ ├── VMInstance.swift # 单个 VM 实例管理 │ └── VMManager.swift # 多 VM 管理器 ├── Views/ │ ├── VMListView.swift # VM 列表 │ ├── VMDetailView.swift # VM 详情与控制 │ ├── VMDisplayView.swift # 图形显示窗口 │ └── ConsoleView.swift # 串口控制台窗口 ├── Utilities/ │ ├── DiskImageHelper.swift # 磁盘镜像处理 │ └── CloudInitHelper.swift # Cloud-init ISO 生成 └── Resources/ └── LinuxVMManager.entitlements
5. 设备配置清单
5.1 存储设备
// 主磁盘(virtio 块设备) let diskAttachment = try VZDiskImageStorageDeviceAttachment( url: diskURL, readOnly: false ) let disk = VZVirtioBlockDeviceConfiguration(attachment: diskAttachment) config.storageDevices = [disk] // USB 存储(用于 ISO 安装) let usbAttachment = try VZDiskImageStorageDeviceAttachment( url: isoURL, readOnly: true ) let usb = VZUSBMassStorageDeviceConfiguration(attachment: usbAttachment) config.storageDevices.append(usb)
5.2 网络设备
// NAT 网络(最简单,VM 可访问外网) let network = VZVirtioNetworkDeviceConfiguration() network.attachment = VZNATNetworkDeviceAttachment() config.networkDevices = [network]
NAT 网络特点:
- VM 可访问外部网络
- 外部无法主动连接 VM
- DHCP 由 macOS 提供
5.3 图形设备
let graphics = VZVirtioGraphicsDeviceConfiguration() graphics.scanouts = [ VZVirtioGraphicsScanoutConfiguration( widthInPixels: 1280, heightInPixels: 800 ) ] config.graphicsDevices = [graphics]
5.4 输入设备
// USB 键盘 config.keyboards = [VZUSBKeyboardConfiguration()] // USB 屏幕坐标指针(推荐用于 GUI) config.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()]
5.5 串口控制台
let consolePort = VZVirtioConsoleDeviceSerialPortConfiguration() let inputPipe = Pipe() let outputPipe = Pipe() consolePort.attachment = VZFileHandleSerialPortAttachment( fileHandleForReading: inputPipe.fileHandleForReading, fileHandleForWriting: outputPipe.fileHandleForWriting ) config.serialPorts = [consolePort] // 读取输出 outputPipe.fileHandleForReading.readabilityHandler = { handle in let data = handle.availableData if let text = String(data: data, encoding: .utf8) { print(stripAnsiCodes(text)) } } // 发送输入 func sendCommand(_ command: String) { if let data = (command + "\n").data(using: .utf8) { inputPipe.fileHandleForWriting.write(data) } }
5.6 其他可选设备
// 音频(输入+输出) let audioInput = VZVirtioSoundDeviceConfiguration() audioInput.streams = [VZVirtioSoundDeviceInputStreamConfiguration()] let audioOutput = VZVirtioSoundDeviceConfiguration() audioOutput.streams = [VZVirtioSoundDeviceOutputStreamConfiguration()] config.audioDevices = [audioInput, audioOutput] // 内存气球(动态内存管理) config.memoryBalloonDevices = [VZVirtioTraditionalMemoryBalloonDeviceConfiguration()] // 熵设备(提供随机数) config.entropyDevices = [VZVirtioEntropyDeviceConfiguration()] // SPICE Agent(剪贴板共享等) let spiceAgent = VZVirtioConsoleDeviceConfiguration() let spicePort = VZVirtioConsolePortConfiguration() spicePort.name = VZSpiceAgentPortAttachment.spiceAgentPortName spicePort.attachment = VZSpiceAgentPortAttachment() spiceAgent.ports = [spicePort] config.consoleDevices.append(spiceAgent)
6. 常见问题与解决方案
6.1 VM 启动失败:Invalid kernel format
原因: vmlinuz 文件是 gzip 压缩的,需要解压。
解决方案:
# 检查文件类型 file vmlinuz # 输出: vmlinuz: gzip compressed data... # 解压 gzip -dc vmlinuz > vmlinux
6.2 VM 启动失败:Invalid disk format
原因: Virtualization 框架只支持 raw 格式磁盘,不支持 QCOW2。
解决方案:
# 转换 QCOW2 到 raw qemu-img convert -O raw ubuntu.qcow2 ubuntu.raw # 可选:扩展磁盘大小 qemu-img resize -f raw ubuntu.raw 20G
6.3 启动卡在 "Waiting for network"
原因: Cloud-init 等待网络配置超时。
解决方案: 在内核命令行添加 cloud-init=disabled
bootLoader.commandLine = "console=hvc0 root=/dev/vda1 rw cloud-init=disabled"
6.4 文件系统只读
可能原因:
- 内核命令行缺少
rw参数 - 磁盘镜像文件系统损坏(非正常关机导致)
- 磁盘空间不足
解决方案:
方案 A - 确保命令行包含 rw:
bootLoader.commandLine = "console=hvc0 root=/dev/vda1 rw"
方案 B - 进入单用户模式修复:
// 临时修改命令行 bootLoader.commandLine = "console=hvc0 root=/dev/vda1 rw init=/bin/bash"
启动后执行:
mount -o remount,rw / # 执行修复操作 sync
方案 C - 重新下载干净的镜像:
curl -L -o ubuntu.qcow2 "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img" qemu-img convert -O raw ubuntu.qcow2 ubuntu.raw qemu-img resize -f raw ubuntu.raw 20G
6.5 登录失败 (ubuntu/ubuntu 不工作)
原因: Ubuntu Cloud Image 默认禁用密码登录,需要 cloud-init 配置。
解决方案 A - 创建 Cloud-init seed.iso:
- 创建
user-data文件:
#cloud-config users: - name: ubuntu sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash lock_passwd: false plain_text_passwd: ubuntu
- 创建
meta-data文件:
instance-id: linux-vm-001 local-hostname: linux-vm
- 生成 ISO:
hdiutil makehybrid -o seed.iso -hfs -joliet -iso -default-volume-name cidata seed/
- 在配置中挂载 seed.iso
解决方案 B - 单用户模式手动设置密码:
# 使用 init=/bin/bash 启动后 mount -o remount,rw / passwd ubuntu # 输入新密码 sync # 重启,恢复正常命令行
6.6 控制台输出乱码(ANSI 转义序列)
解决方案: 添加过滤函数
func stripAnsiCodes(_ text: String) -> String { let pattern = "\\x1B\\[[0-9;]<em>[A-Za-z]|\\x1B\\][^\\x07]</em>\\x07|\\x1B[()][AB012]" guard let regex = try? NSRegularExpression(pattern: pattern) else { return text } let range = NSRange(text.startIndex..., in: text) return regex.stringByReplacingMatches(in: text, range: range, withTemplate: "") }
6.7 显示窗口文字太小
解决方案: 调整虚拟显示器分辨率
// 使用较低分辨率 graphics.scanouts = [ VZVirtioGraphicsScanoutConfiguration( widthInPixels: 800, heightInPixels: 600 ) ]
7. 最佳实践
7.1 选择合适的引导方式
| 场景 | 推荐方式 |
|---|---|
| 安装完整 Linux 桌面发行版 | EFI 引导 + ISO |
| 云镜像快速启动 | 直接内核引导 |
| CI/CD 测试环境 | 直接内核引导 |
| 长期使用的开发环境 | EFI 引导 |
7.2 正确关闭 VM
// 优先使用正常关机(保护文件系统) func gracefulShutdown() async { // 发送关机命令到 VM sendCommand("sudo shutdown -h now") // 等待 VM 自行停止 // 或设置超时后强制停止 } // 强制停止前同步数据 func forceStop() async throws { sendCommand("sync") try await Task.sleep(nanoseconds: 1_000_000_000) // 等待 1 秒 try await virtualMachine?.stop() }
7.3 磁盘镜像管理
# 创建基础镜像后,使用写时复制创建实例 # macOS 使用 APFS clone cp -c base.raw instance1.raw # 定期检查磁盘空间 df -h # 在 VM 内执行
7.4 配置持久化
struct VMConfiguration: Codable { var id: UUID var name: String var cpuCount: Int var memorySize: UInt64 var diskPath: String var kernelPath: String? var initrdPath: String? var kernelCommandLine: String var useEFIBoot: Bool } // 保存到 Application Support func saveConfigurations(_ configs: [VMConfiguration]) throws { let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent("LinuxVMManager/vms.json") let data = try JSONEncoder().encode(configs) try data.write(to: url) }
8. 附录:引导方式全景
8.1 虚拟化环境常见引导方式
| 引导方式 | 描述 | 典型场景 |
|---|---|---|
| EFI/UEFI | 现代标准固件引导 | 桌面系统、服务器 |
| 直接内核引导 | Hypervisor 直接加载内核 | 云平台、轻量 VM |
| Legacy BIOS | 传统 PC 兼容模式 | 旧系统兼容 |
| UEFI + Secure Boot | 带签名校验的安全启动 | 企业环境 |
| PXE/iPXE | 网络引导 | 数据中心批量部署 |
| 从 ISO 引导 | 光驱/USB 介质启动 | 系统安装 |
8.2 容器环境引导说明
| 平台 | 引导方式 | 说明 |
|---|---|---|
| Docker (容器) | 无引导 | 共享宿主内核的进程隔离 |
| Docker Desktop (macOS/Windows) | 直接内核引导 | LinuxKit 轻量 VM |
| WSL2 (Windows) | 直接内核引导 | 微软定制 Linux 内核 |
| OrbStack (macOS) | 直接内核引导 | 优化的轻量 Linux VM |
关键结论: 容器本身无需引导;容器运行时底层的 Linux VM 通常采用直接内核引导以优化启动速度。
参考资源
- Apple Virtualization Framework Documentation
- Running GUI Linux in a Virtual Machine on a Mac (Apple Sample Code)
- Ubuntu Cloud Images
- Cloud-init Documentation
文档版本: 1.0 | 最后更新: 2026-02-04