KVM

What is Hypervisor?

Hypervisor 是作業系統與硬體之間的中間層,這允許多個作業系統可以作為獨立的 virtual machine(VM),運行於一個實體的電腦之上。Hypervisor 則管理硬體資源使這些 VM 可以共享之。它會在邏輯上將 VM 彼此分開,然後為每個 VM 指派本身的一部分基礎運算處理能力、記憶體和儲存容量,防止 VM 之間相互干擾

Hypervisor 可以分為兩大類型,其一是 type-1 hypervisor,其直接運行在硬體之上,如下圖是比較經典的設計。它的優點是效率很高,因為可以直接存取硬體。這也增加了安全和穩定性,因為 type-1 hypervisor 和 CPU 之間不存在額外的 OS 層,因此較為單純而不容易被介入。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Type-2 hypervisor 則不直接在硬體上執行,而是作為應用程式執行在主作業系統(host)環境上執行,如下圖所展示的。因為 type-2 hypervisor 必須透過 host OS 存取資源,因而會引發延遲問題而相對 type-1 效能較差。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Using Linux as Hypervisor with KVM

Reference

What is KVM?

KVM 是一個 Linux kernel module,可以將 Linux 轉為 type-2 hypervisor,結合硬體的虛擬化支援,使得 host machine 上可以執行多個獨立的虛擬環境,稱為 guest 或者 virtual machine。由於 KVM 直接提供 CPU 和記憶體的虛擬化,guest os 的 CPU 指令不需要額外經過軟體的 decode,而是直接交給硬體進行處理,因此可以有效的提高運行速度。而結合軟體(例如 KVM 搭配 QEMU)模擬 CPU 和記憶體以外的裝置之後,guest OS 便可以被完整的支援在 host OS 上載入並良好的運行。

對於 Linux KVM 相關 API 的基本操作,以下是有助於入門的材料:

Example

KVM API Documentation

sysprog21/kvm-host 展示了一個使用 Linux 的 kernel-based virtual machine,以實現可以載入 Linux 核心的系統級虛擬機器 (system virtual machine)的極小化實作。雖然目前的實作只能部份的啟動 Linux,但精簡的程式碼很適合作為入門 Linux KVM 相關 API 的材料,以下概略的解析程式碼的行為:

vm_init

KVM for x86 在開啟 /dev/kvm 並透過 KVM_CREATE_VM 建立之後,初始化時分別需要設置:

  1. KVM_SET_TSS_ADDR: 定義 3 個 page 的 physical address 範圍以設置 Task State Segment
  2. KVM_SET_IDENTITY_MAP_ADDR: 定義 1 個 page 的 physical address 範圍以設置 identity map (page table)
  3. KVM_CREATE_IRQCHIP: 建立虛擬的 PIC
  4. KVM_CREATE_PIT2: 建立虛擬的 PIT
  5. KVM_SET_USER_MEMORY_REGION: 為 VM 建立記憶體,將 guest physical memory 通過 host OS 的一段在 virtual 連續的空間(在 host 的 physical 不一定連續)來進行模擬

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

KVM MMU Virtualization

  1. KVM_CREATE_VCPU: 為 VM 建立 CPU

TSS 和 identity map 的這 4 個 page 的設置應該與 intel CPU 的模式切換相關,但受限於我對此了解的不夠透徹而無法作更詳盡的解釋

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

暫存器的部份也需要根據需求進行相應的初始化:

  1. KVM_GET_SREGS / KVM_SET_SREGS: 設置 x86 中的 segment register,被用來指定 code / data / stack 等記憶體內容的定址範圍,包含:
  • Code Segment (CS): Pointer to the code
  • Data Segment (DS): Pointer to the data.
  • Extra Segment (ES). Pointer to extra data ('E' stands for 'Extra').
  • F Segment (FS): Pointer to more extra data ('F' comes after 'E').
  • G Segment (GS): Pointer to still more extra data ('G' comes after 'F').
  • Stack Segment (SS). Pointer to the stack.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

x86 Segmentation for the 15-410 Student

  1. 也設置了 cr0 以開啟 protected mode
  2. KVM_GET_REGS / KVM_SET_REGS:
  • 設置 rflags: bit 1 恆為 1
  • 設置 rip: rip 儲存下個要執行的 instruction,因此需指向 kernel 被擺放的記憶體位置
  • 設置 rsp: rsp 則需設置在 boot paramters 的位置

When using bzImage, the protected-mode kernel was relocated to 0x100000 (“high memory”), and the kernel real-mode block (boot sector, setup, and stack/heap) was made relocatable to any address between 0x10000 and end of low memory.

最後,需要設虛擬的 CPU(VCPU):

  1. KVM_GET_SUPPORTED_CPUID: 取得一個 struct kvm_cpuid2 的結構單元,準備用來建立 cpuid information
  2. 對於 functionKVM_CPUID_SIGNATURE 的 entry 進行設置
  1. 最後使用 KVM_SET_CPUID2 將 entry 設置回 KVM 之中
struct kvm_cpuid_entry {
	__u32 function;
	__u32 eax;
	__u32 ebx;
	__u32 ecx;
	__u32 edx;
	__u32 padding;
};

struct kvm_cpuid2 {
	__u32 nent;
	__u32 padding;
	struct kvm_cpuid_entry2 entries[0];
};

vm_load_image / vm_load_initrd

要載入 bzImage / initrd 到模擬的記憶體中,我們需要先對 Linux 開機流程的 memory map 有足夠的認識,以及 bzImage / initrd 應該放在相應的哪個位置 (在此範例程式之中,X == 0x10000):

Reg   PhysAddr                            Binary image
              |  Protected-mode kernel |
RIP   100000  +------------------------+  bzImage [+ (setup_sects + 1) * 512]
              |  I/O memory hole       |
      0A0000  +------------------------+
              |  Reserved for BIOS     |      
              ~                        ~
              |  Command line          |      
      X+10000 +------------------------+      
              |  Stack/heap            |      
      X+08000 +------------------------+
              |  Kernel setup          |      
              |  Kernel boot sector    |      
RSI   X       +------------------------+  bzImage [+ 0]
              |  Boot loader           |     
      001000  +------------------------+      
              |  Reserved for MBR/BIOS |
      000800  +------------------------+
              |  Typically used by MBR |
      000600  +------------------------+
              |  BIOS use only         |
      000000  +------------------------+
     
... where the address X is as low as the design of the boot loader permits.

vm_load_image 所做即為設定一些必要的 boot_params 後,按照此 memory map 將 bzImage 載入到相對應的位置。此外,還需要設置 e820 table 以確保 initrd 所在位址是可以被合法使用的。需要設定哪些 memory address 可以為 OS 所用,而哪些要留給 bios / memory-mapped device 等:

  • 設置兩個 struct boot_e820_entry,type 皆為 E820_RAM 表示 availible,簡單的把 ISA_START_ADDRESSISA_END_ADDRESS 以外的位置都設定成可用

vm_load_initrd 則載入 initrd。boot_paramsinitrd_addr_max 會記錄 initrd 會允許載入的最高位置,我們可以根據 initrd_addr_max、initrd 本身的大小、以及我們所模擬的 VM 擁有的記憶體大小來找到合適的載入位置。

  • 並通過設置 ramdisk_imageramdisk_size 使 booting 可以正確運作

vm_run

在進行了必要的設置並且載入 image 後,就可以透過 KVM_RUN ioctl 來執行 guest virtual cpu 了。關於指令的 decode、execute、和對記憶體的存取操作將會在 KVM 中進行,我們所設計的虛擬機器不需要實作相關的模擬。我們只需要處理會使得 KVM_RUN ioctl 返回的特殊事件(大部分是 I/O 設備操作的模擬)。

Boot Linux

The Linux/x86 Boot Protocol

要讓 kvm-host 可以啟動 Linux 並開啟與使用者互動的介面,涉及了幾個關鍵的設施。

bzImage

引用自 vmlinux

As the Linux kernel matured, the size of the kernels generated by users grew beyond the limits imposed by some architectures, where the space available to store the compressed kernel code is limited. The bzImage (big zImage) format was developed to overcome this limitation by splitting the kernel over non-contiguous memory regions

因應 Linux kernel 的大小的日益增加,bzImage 因此被設計以將 kernel 切割成多段不連續的記憶體區塊,而得以分段載入記憶體之中,包含了 object file bootsect.o + setup.o + misc.o + piggy.o,其中 piggy.o 的 data section 中就放著壓縮後的 vmlinux 檔(編譯出的原始 kernel image)。

initrd

大量內容引用自: 深入理解 Linux 2.6 的 initramfs 機制 (上)

file system 在 UNIX-based 的作業系統具有關鍵的地位。其中,為了讓 kernel 在完成開機後,可以進入 user mode 執行使用者程式,或者新增新的 file system,root filesystem 的存在是必要的。

而 initrd 是 initial ramdisk 的縮寫,它借用了 RAM 空間建立暫時的 root file system。當 Linux kernel 被載入並執行時,由於 initrd 所在的空間存在檔案系統,且通常會包含 init 等程式,故可用以掛入某些特別的驅動程式,比方說 SCSI,完成階段性目標後,kernel 會將真正的 root file system 掛載,並執行 /sbin/init 程式。總而言之,initrd 提供了「兩階段開機」的支援。

為何需要此等迂迴的開機途徑呢?原因是,root file system 可能所存在於非本機的外部儲存裝置。另一方面,如果要將所有裝置驅動程式直接編譯到核心中,儲存裝置很可能極難尋找。比方說 SCSI 裝置就需要複雜且耗時的程序,若用 RAID 系統更是需要看配置情況而定,同樣的問題也發生在 USB storage 上,因為 kernel 得花上更長的等待與配置時間,或說遠端掛載 rootfs,不僅得處理網路裝置的問題,甚至還得考慮相關的伺服器認證、通訊往返時間等議題。我們會希望 root file system 的裝置驅動程式能具有像是 udevd 的工具可以實現自動載入。但是這裡就存在矛盾了,udevd 是一個 executable file,因此在 root file system 被掛載前,是不可能執行 udevd 的,但是如果 udevd 沒有啟動,那就無法自動載入 root file system 存在裝置的驅動程式了。

initrd 的出現得以解決這個矛盾。第一階段啟動的 initrd 提供了初步啟動 user space 程式的環境,然後其中可以確定最後真正 root file system 所在裝置究竟為何,因此 initrd 可以把負責啟動該 root file system 的 driver 載入。最後再掛到真正的 root 去把其他 init 給完成。

更重要的是,我們可在 initrd 放置基礎的工具,一來作為掛載 rootfs 作準備,比方說硬體初始化、解密、解壓縮等等,二來提示使用者或系統管理員目前的狀態,這對於消費性電子產品來說,有很大的意義。

在 initrd 之後又延伸出 initramfs 的機制。細節強力推荐深入閱讀 jserv 老師的 深入理解 Linux 2.6 的 initramfs 機制 (上) 這篇文章。實在是寫得太詳盡了 orz

其他引用:

e820

e820

Reference

UART

KVMTOOL

QEMU