探索Mac mini M1的QEMU/KVM虛擬化實現

語言: CN / TW / HK

背景

目前業界雲平臺一般會提供 Windows 虛擬機器和 Linux 虛擬機器,使用者可以在這些虛擬機器上部署符合 Windows生態和 Linux 生態的服務和應用。MacOS 作為 Apple 生態的基本作業系統,MacOS 虛擬化的實現將會為使用 Apple 生態的使用者帶來很大的便利,如直接在 MacOS 的虛擬機器上進行測試、構建iOS 應用,甚至使用 MacOS 虛擬機器作為辦公環境等。

目前社群對於 Intel x86  Mac mini 平臺的 MacOS虛擬化方案已經比較成熟,隨著 2020年 Apple 推出自研的 ARM64 M1晶片,Mac mini 增加了對 ARM64 平臺的支援,後續 Apple 將逐漸從 Intel x86 晶片轉向自研的 ARM64 晶片。相對於 Intel x86 平臺,社群對 ARM64 平臺的 MacOS 虛擬化方案還在探索和起步階段,於是位元組跳動 STE 團隊和 App Infra 團隊率先進行了深入的研究,本文將與大家分享針對採用 ARM64 M1 晶片的 Mac mini M1 機型虛擬化方案的探索和實現。

方案介紹

什麼是 Mac mimi 虛擬化

Mac mini 虛擬化是指在一臺 Mac mini 物理機上模擬出多個互相隔離的 Mac mini 虛擬機器,使得每個虛擬機器使用者都認為自己在一臺真正的物理機上以獨佔的方式執行。

整個方案中,我們採用了雲平臺常見的 Linux + KVM(Kernel-based Virtual Machine)+ QEMU(Quick EMUlator) 這樣全開源的虛擬化軟體棧,以方便把控和實現對 Mac mini M1 機型(包括 CPU / MEM /片上裝置/外設等)的全方位模擬。在 Mac mini M1 的虛擬化實現中,我們基於 QEMU 既有的機型框架實現了對 Mac mini M1 的軟體模擬(TCG, Tiny Code Generator)支援,又通過 KVM 為Mac mini M1 提供了ARM64平臺硬體虛擬化能力的支援。圖片

Linux

Asahilinux 對 M1 機型上的硬體裝置做了足夠多的逆向工程分析,並根據推出來的硬體邏輯來嘗試實現linux下的各裝置驅動,目前已實現了對 M1 機型的基本支援,其中對 M1 GPU的支援也在不斷完善中。Asahilinux這部分工作也在不斷的合入Linux upstream。

我們採用Asahilinux作為執行在M1裸機上的作業系統。依賴Asahilinux,我們可以獲得M1 的 linux 執行時物理機環境,並以此為基礎逐步構建和實現M1虛擬化所需的虛擬化軟體棧。

QEMU

QEMU 作為機型/系統模擬的常用軟體,構造了機型模擬的基本框架,並在此基礎上提供了不同平臺上很多具體機型的模擬實現,比如 x86 平臺的 piix 和 Q35 機型,ARM 的 virt 和 raspi2 等。在 M1 機型的虛擬化中我們需要基於 ARM64 通用機型,構造一個新的 M1 機型,並整合到 QEMU 提供的機型模擬框架中。

QEMU 提供的基本模擬方式是純軟體的,即對於每一條虛擬機器內部執行的指令都需要退出到 QEMU 的上下文來翻譯執行(TCG),該方式效能很低。KVM 可以幫助我們解決 QEMU 純軟體模擬帶來的低效能問題。

KVM

KVM 作為 Linux 下支援虛擬化的核心模組,將虛擬化場景下的硬體加速功能通過 ioctl 的方式匯出給QEMU使用。在 KVM 的支援下,虛擬機器內部的常用指令將不再需要退出到QEMU來模擬執行,而是在虛擬機器的 CPU 上下文中直接執行,只有個別特權指令才需要退出到物理機上,由物理機來具體執行。對於虛擬機器的記憶體訪問,也不再需要 QEMU 進行頁表翻譯,而是直接依賴硬體實現的兩級頁表翻譯機制(虛擬機器內部頁表實現 Guest Virtual Address向Guest Physical Address的轉化,使用stage2 page table 實現 Guest Physical Address向Host Physical Address的轉化),從而實現記憶體的快速訪問。

M1機型的模擬

瞭解了基本的虛擬化軟體棧後,我們開始考慮如何模擬一臺真正的 Mac mini M1。那麼,模擬 M1 機型包括模擬哪些內容呢?

M1 VCPU

QEMU 中使用執行緒來模擬 VCPU,即每一個 VCPU 對應一個單獨的 QEMU 執行緒;使用必要的資料結構記錄 VCPU 相關的資訊,如暫存器/執行狀態等。區別於其他機型的 VCPU,M1 需要增加的特有支援如下:1)多個 M1 特有暫存器和對應的讀寫邏輯;2)對 Apple PMGR(電源管理模組)的模擬。M1 通過 PMGR 來實現多核(SMP,symmetric multiprocessing)的喚醒和管理;

3)Cluster 內和 Cluster 間的 Fastipi 通訊機制;

圖片

M1 記憶體佈局

M1 的記憶體佈局,是指將VM的整個地址空間按照裝置和用途進行合理的劃分,如 Flash device、CPU 相關的韌體、中斷控制器、Spec 裝置地址、MMIO 和 PCI 地址範圍以及虛擬記憶體等。在 QEMU 層設定 M1 機型的記憶體佈局需要注意以下幾個問題:1)注意 CPU 地址域和裝置地址域的關係;2)System Memory 範圍不能跟 DTB(Device Tree Blob) 中各裝置的實體地址空間衝突;3)需要連續的足夠大的地址範圍來對映 System Memory;圖片

System Memory 作為虛擬機器的主存,在啟動初期會被用來存放核心映象、裝置樹以及Ramdisk等,這些內容的載入地址也需要額外配置,注意不能存在交叉,以避免記憶體中內容互相覆蓋。

M1 裝置模擬

區別於 M1 物理機,M1 虛擬機器的儲存和網路主要採用虛擬化場景下常見的 virtio-pci 裝置,如 virtio-blk / virtio-net 裝置等。我們需要在模擬的 M1 機型上插上 PCIE 的線,其下接各種 PCI / PCIE 裝置。另外,為了讓裝置的中斷能夠順利分配、觸發並遞送到目的 VCPU 中,還需要模擬 M1 的中斷控制器 AIC。

AIC 是 M1 Apple Interrupt Controller 的簡稱, 主要負責中斷掩碼/中斷狀態設定、接受/遞送中斷等。對 AIC 的模擬主要是對上述的功能的模擬,可以通過 Asahilinux 的 AIC 驅動部分的程式碼邏輯來推斷出 AIC 具體地址空間的含義,從而實現 AIC 的模擬。對 AIC 的模擬中我們使用了 MMIO(Memory-Mapped IO) 空間來實現對 AIC 內部相關硬體地址的訪問邏輯。

對 M1 新插上的 PCIE 匯流排的模擬基於 QEMU 提供的通用 PCIE 線——gpex-pcihost 來實現,在通用的 gpex-pcihost 模型的基礎上,我們需要根據 M1 的 DTB 檔案來制定 M1 PCIE 匯流排配置空間和bar空間,以及其下 PCI 裝置的 PCI 配置空間和 Bar 地址空間範圍。

整體的中斷設計如下:

將模擬的 M1 PCIE (gpex-pcihost )匯流排的 4 個 gpio_out pin 依次連線到 AIC 的 gpio-in pin 上去。gpex-pcihost 通過該連線傳遞中斷給AIC;

AIC 有 cpu_num 個 gpio_out pin ,這些引腳分別連線到各個 CPU 上,AIC 通過該連線遞送中斷給 CPU。

圖片

至此,我們已經在 QEMU 層面實現了對 M1 的 CPU、PMGR、AIC、PCIE 匯流排等裝置的基本模擬,可以通過 QEMU 命令在 PCIE 線上插上所需的 PCI 裝置,如 virtio-blk、virtio-net 等,來嘗試啟動 MacOS 虛擬機器了。

虛擬化下的MacOS和啟動

既然已經完成了對 M1 虛擬機器所需要的基本元件的模擬,接下來我們考慮如何在模擬的 M1 機型上啟動真正的 MacOS 。

預啟動階段

MacOS 的啟動流程是怎樣的呢?MacOS 在 M1 物理機上的啟動流程如下(更多詳見 M1 啟動過程

  • 機器上電,由 Boot ROM 驗證並載入位於 Flash 中的 LLB(Low Level Bootloader)
  • LLB 設定安全策略、解密/認證並載入位於 preboot volume 中的 iBoot 檔案
  • iBoot 根據 DTB 對 M1 韌體和裝置做早期的初始化,驗證並載入 KernelCache(KernelCache 是 MacOS 啟動映象,類似於 linux 的 Image.gz),更新並載入 DTB
  • 進入 MacOS 核心啟動流程。

其中無論是 LLB 還是 iBoot 都是閉源的,這為我們完整的模擬 M1 啟動帶來了難處。為了能成功的在 QEMU / KVM 虛擬化場景下啟動 M1,我們對 MacOS 的啟動過程做了簡化,割捨掉 LLB 和 iBoot 階段,直接由 QEMU 來完成早期裝置的初始化(如 CPU 狀態等)、KernelCache 檔案的解析與載入、DTB 的更新與載入等。

在 QEMU 完成早期初始化和預載入後,虛擬機器啟動時刻的 System Memory 地址空間佈局狀態如下:圖片其中,各檔案的含義如下:

  • KernelCache:包含了 XNU 核心和 kext 核心擴充套件,是 MacOS 的核心映象
  • Ramdisk:HFS 格式的 rootfs,可作為 MacOS 的啟動盤使用,非必需(當使用 virtio-blk 盤作為啟動盤時,可取消 Ramdisk 的使用)
  • DTB:M1 的裝置樹,MacOS 核心需要通過 DTB 來獲取到 M1 的硬體資訊,QEMU 模擬的 M1 裝置資訊(如中斷路由和裝置地址資訊等)要和 DTB 中維護的資訊一致
  • xnu_arm64_boot_args:Macos 核心的啟動引數

隨後 QEMU 將 Primary VCPU 的 pc 暫存器設定為 KernelCache 中的 start 函式地址,開始VCPU 的執行。

啟動流程

MacOS 在 QEMU / KVM 虛擬化場景下整體的啟動流程如圖,大致分如下四個階段:

1. QEMU 階段

該階段在 QEMU 中實現,主要是為 MacOS 的執行做準備,具體包括 VCPU 執行緒的建立,各裝置狀態的初始化,System Memory 的載入對映(如KernelCache、 Ramdisk、DTB),bootargs 的建立和 DTB 資料的更新等,隨後將 VCPU0 的 pc 暫存器指向 MacOS XNU 核心的_start 函式,VCPU0 開始執行。

2. 早期初始化階段

在 MacOS 啟動早期的彙編程式碼階段,_start 函式設定了核心啟動早期的中斷/異常向量表和頁表等,並對核心引數進行解析,配置 MMU 和 vbar 等。圖片

3. Kernel 階段

在 arm_init 和 kernel_bootstrap 中, 主要是第一個核心執行緒啟動前的準備工作,如進行 KernelCache 解析、DTB 轉化,console 設定,per cpu data 設定和 process 初始化、系統記憶體的初始化以及核心各個子系統的啟動。

kernel_bootstrap_thread/bsd 中,主要是啟動其他的系統維護執行緒構築 MacOS 的執行時環境,如初始化 I/O Kit 框架,初始化 BSD 子系統。其間還會根據 DTB 的配置來設定 SMP,喚醒其他 VCPU;其中, I/O Kit 是MacOS XNU 核心為裝置驅動程式提供的完整的執行時環境,使用者可以基於 I/O Kit 提供的面向物件能力來快速高效地編寫自己的驅動程式。

在 bsdinit 中,初始化 BSD 核心子系統, 掛載 rootfs 並啟動 /sbin/launched。

4. 使用者態服務和程序

/sbin/launched 程序的 pid 為1,為第一個使用者態程序,它根據預定的安排或者實際的需要載入位於 /System/Library/LaunchAgents 和 /System/Library/LaunchDeamons 下的其他應用程式或作業,包括守護程式如 atd、crond、inetd 等,以及代理程式如 GUI shell、 Terminal shell 等。

至此 MacOS M1 基本啟動已經完成。

虛擬化下完整的MacOS 軟體棧

QEMU/KVM 虛擬化場景下的 MacOS 整體架構圖如下,從下到上依次是M1物理機、支援 M1 各型別裝置的 Asahilinux 核心、提供硬體加速功能的核心模組 KVM、實現 M1 機型模擬的使用者態 QEMU,執行在 QEMU 上的 M1 Guest OS。圖片

其中,M1 Guest OS 在 XNU 核心的基礎上會載入多個 Kext 以實現完整的 MacOS 功能;既支援 APFS 格式的根檔案系統,也支援 HFS 格式的 Ramdisk 根檔案系統;dyld 類似於 linux 上的 ld,負責載入和連結應用程式,dyld_shared_cache 提供了系統中各程序共享的基礎庫快取,有效地降低了各程序通用庫的記憶體佔用率。

後續規劃和展望

目前我們已經完成了 QEMU / KVM 虛擬化場景下 MacOS M1 的基本啟動,後續需要有更多的優化從功能完備性、效能以及相容性的角度來展開。

更完善的MacOS 執行時環境

目前我們的方案在 QEMU 層實現了 M1 基本裝置的模擬、在 KVM 層實現了硬體加速功能、在 MacOS 中實現了部分系統服務的啟動,然而整個方案與一臺真正的 MacOS 還有一定的距離,如預設啟動的數百個系統服務、友好的 GUI 介面、更完備的裝置支援以及更好的使用者支援度等等,後續將持續優化。

相容性

在整個虛擬化方案中為了實現 MacOS 的啟動,我們對 KernelCache 裡的 XNU 核心和 Kext 部分進行了熱 Patch。後續需要實現合理的熱 patch 的工具來完成不同的 MacOS 版本熱 Patch 的高效定製。

效能問題

模擬 M1 機型時,像 PMGR 和 AIC 這樣的裝置,我們目前是直接在 QEMU 裡進行模擬的,這會使得虛擬機器對 PMGR 和 AIC 的操作,都需要退回到 QEMU 中來進行具體行為的模擬,後續可考慮將這些元件整合到 KVM 中,減少使用者態和核心的切換,減少虛擬化開銷,提高 VM 的效能。