參考資料:
kvm-host
kvm-host 的改進
Linux 核心專題: 系統虛擬機器開發和改進
linux-riscv-dev/exercises2/kvm/kvm.md
Virtio-networking series
KVM: Linux 虛擬化基礎建設
Virtio: An I/O virtualization framework for Linux
Introduction to VirtIO
virtio-v1.2-cs01.pdf
Universal TUN/TAP device driver
Linux 核心專題: RISC-V 系統模擬器
semu
Linux 支援多種不同的虛擬化系統,比如說
而每個系統都會有其特有的驅動裝置,比如說 block, console, network 等等,而 virtio 作為一個虛擬化的 io,其功能在於作為前端與後端的通訊,而此處所提之前端即為虛擬化系統的驅動裝置,後端則為由虛擬化系統 KVM 所模擬的裝置。
在 Linux 中,virtio 的前端的驅動裝置 Front-end drivers 都分別被寫為核心模組 kernel modules,並且可以直接通過 virtio 來通訊。
比如說 virtio-net 就是通過將其寫為核心模組的形式來啟動。
而這些前端的任務為
從以上所述可以了解到,virtio-net 屬於前端的驅動裝置,並且透過 virtio 傳輸資料,但是在 kvm-host 中,驅動裝置都是建立在 virtio-pci 上(後面會介紹),並不是透過註冊核心模組的方式來啟動。
virtio 傳輸資料透過結構體 virtqueue,virtqueue 主要分為兩種形式,一種為 Split virtqueue,另一種為 Packed virtqueue,在 kvm-host 中實作的形式為 Packed virtqueue,所以這裡主要討論的是 Packed virtqueue。
Virtio 裝置為提供一個虛擬界面來交換資訊,而一個 Virtio 虛擬界面應該包含以下幾個部份
Device status field 為指示此裝置的一些裝態,比如說
ACKNOWLEDGE
(0x1) 為此裝置已被系統承認DRIVER
(0x2) 為表示此裝置已經被初始化DRIVER_OK
(0x4),FEATURES_OK
(0x8) 代表著此驅動或裝置已經可以開始進行通訊DRIVER_NEEDS_RESET
(0x40),FAILED
(0x80) 代表此裝置遇到錯誤Packed virtqueue 跟 Split virtqueue 的主要區別在 Split virtqueue 將 descriptor ring,available ring 和 used ring 分開來實作,而 Packed virtqueue 則是結合在一起,合併的優點在
在 Packed virtqueue 的形式中,首先要理解的是 descriptor ring,available ring 和 used ring。
descriptor ring 由四個變數組成,分別為 addr
,len
,id
和 flags
,其主要目的是為了描述從 guest 傳遞的資料並且在完成 guest 的請求後由 used buffer 通知已完成。
addr
為資料在 guest 中的地址len
為資料的大小flags
為此資料的運作方式,選項有 write-only 或 read-onlyavailable ring 的功能是將 available descriptor 寫入 descriptor ring 當中並且完成 io 的請求,used ring 則是在完成請求後會向驅動發起 notification。
由於在 kvm-host 中的 driver 都是建立在 virtio-pci 上,所以有必要理解 virtio-pci 的實作和原理。
一個 PCI 的架構如上,在此專案中每個 PCI 裝置都被設定為需要通過 PCI host bridge 來存取資訊,繼而透過 PCI host bridge 來與 cpu 和記憶體溝通。
此專案中,每個 Virtio 裝置都實作為 PCI 裝置,啟動時也會以 PCI 裝置啟動,比如說 virtio-blk
PCI 裝置分配了 256 個 bytes 來進行設定,前 64 個 bytes 為共通設定如下圖所示,後 128 個 bytes 為自行設定,而前 64 個 bytes 則是需要從 virtio-v1.2-cs01.pdf 來得知其中訊息
Transitional PCI Device ID | Virtio Device |
---|---|
0x1000 | network card |
0x1001 | block device |
0x1002 | memory balloning |
0x1003 | console |
0x1004 | SCSI host |
0x1005 | entropy source |
0x1009 | 9P transport |
首先先來觀察 kvm-host 是如何定義 PCI 裝置
從結構體 pci_dev
可以看到此結構體提供了一塊區域設定 PCI 裝置,但是如何去設定此 PCI 裝置 ? 答案為結構體的第二個成員 hdr
。
從上方的定義可以看出兩個訊息,一是 PCI_HDR_READ
為讀取 hdr + offset
中的資料,一是 PCI_HDR_WRITE
為對 hdr + offset
中的資料進行寫入,在 PCI 的裝置中的成員 hdr
就是指向 PCI 裝置地址的起點,也就是 cfg_space
的起始位置。
初始化完 PCI 裝置後,必須要針對 virtio-pci 介面進行初始化,首先第一行的 0x40 為 PCI 裝置設定區域的起始地址,接著將個別裝置的設定以 PCI 裝置中的 hdr
寫入 PCI 中。
Virtio 裝置初始化需要透過 virtio-pci 將裝置建立到 pci 上,
TUN/TAP 為 Linux 核心模擬出來的虛擬網路裝置,並且提供可以使用 userspace 來接收跟傳輸數據包。 TUN/TAN 為完全由軟體支援的網路設備,差別在於 TUN 位於 Network layer 運行專門處理 IP 封包,而 TAP 在 Data Link layer 運行專門處理 Ethernet 封包。
TUN/TAP 是如何與外界溝通,可以從下面這張圖得知。
圖中 APP 為如 Firefox 等網路瀏覽器將網路封包由 Read 將封包資訊經過在 kernel 中模擬出來的 character device 送到 Process 中,而 Write 則是將封包傳輸出去。
從 Universal TUN/TAP device driver 可以知道如何開啟 TUN/TAP 作為虛擬網路裝置,以下簡單解釋各成員。
virtio_pci_dev
將 virtio-net 驅動裝置以 virtio-pci 的形式開啟。virtq
來溝通,而這裡考慮到一個封包會有接收端 RX 和傳輸端 TX,所以將結構體 virtq
的陣列長度乘上 2。下方程式碼為將網卡開啟的方法,並且在 vm_arch_init_platform_device
開啟此網卡,之後的封包資料會透過此檔案來讀取和寫入。
vm_load_diskimg
此一函式為將一映像檔來作為 pci 裝置上的 virtio-blk 開啟的地方,那如果是 virtio-net 呢,virtio-net 應該在哪裡開啟?記憶體上?
透過 PCI 啟動虛擬網卡需要設定一些有關 PCI 的參數,以下為我所設定的參數,首先為 virtqueue 的數量和 virtio-net 在 PCI 裝置上的 class code。
接著定義 PCI 裝置上的 device ID,從 virtio-v1.2-cs01.pdf 中可得知為 0x1041。
接著將這些設定註冊至 PCI 中
直接在 main.c
中啟動虛擬機並且將 virtio-net 註冊到 pci 上,應該可以看到以下畫面
上面的畫面可以看出 pci 開啟了一塊區域 0000:00:01.0
給 virtio-net,至於 0000:00:00.0
則是給 virtio-blk,而 1041 則是上面所定義的 device ID。
要透過虛擬網卡傳輸資料首先需要在 Linux 或 Busybox 的設定檔中加入可傳送封包的指令如 ip 和 ping,目的在於讓虛擬機和外界中可以互傳網路封包