在 x86 BIOS 環境保留了固定的地址作為畫面顯示使用(0Xb8000) 則對應彩色的文本寫入 (80 * 25),而 0xA0000 則用於圖形模式顯式。而現代的設備的 framebuffer 地址會透過 VBE/UEFI 提供另外這個地址式可配置的,不過為了簡單起見這裡我們先寫死,先實現 0xB8000 的文本顯示。
由於顯示設備通常以 MMIO(Memory-Mapped I/O) 方式把畫面緩衝區 (frame buffer) 暴露給 CPU,對 CPU 而言更新畫面只是向某個地址寫入記憶體而已。因此,如果這段位址被註冊為一般的 guest RAM,guest 的寫入不會觸發 VM-exit,我們的 exit handler 就無法直接捕捉這類事件。要想在 host 端觀察到 framebuffer 的變更,以下提供簡單的觀察方法:
1.把那段地址當作 MMIO(不註冊為 RAM)或用 EPT 寫保護來強制 VM-exit
如果不把那段 GPA 註冊到 KVM 做為 RAM 使用,任何訪問會被視為 MMIO,讓 KVM 產生 KVM_EXIT_MMIO 的 VM-exit。或者我們可透過把頁的寫入權限清除(即 read-only),讓 Guest 寫入時觸發 EPT violation 並產生 VM-exit 讓 Host 可以即時處理。
但是頻繁的像素更新會造成大量的 VM-exit 這將造成大量的性能損耗。
2.在 Host 建立 timer 定期輪詢並刷新 guest frame buffer
把 framebuffer 當作正常的 guest RAM 映射回 host,host 以固定時間間隔 (如 10–50 ms) 取樣該記憶體區塊即可,這種做法簡單甚至可天然的透過 DMA 去做頁面處理。
由於 KVM 在建立 memory slot 時支援 dirty logging,因此我們可把方法 2 進一步改成差量更新,每次刷新前只需向 KVM 確認哪些頁被 Guest 修改過,讓 Host 的任務從刷新整個畫面變成刷新被修改頁的局部更新。
在先前的實作中,我們以 epoll 為核心建立了 host I/O 模組,我們可以簡單地將 timerfd 加入到 host I/O 模組中,並將畫面更新的邏輯掛接到處理方法中。這樣就可以在已有的 I/O thread 中定期處理畫面更新。