在之前的實作裡,我們讓 Guest 可執行程式碼,並透過 VM-exit 把發生的事件傳回 Host。
不過對 Guest 來說,單純能跑程式是不夠的,它還需要與外部世界互動,比如:鍵盤、滑鼠、硬碟等。這些設備在硬體上會透過中斷通知 CPU 觸發對應的 ISR 處理。
而在虛擬化環境中,這些事件必須經過 Host 才能轉換成 Guest 可理解的 IRQ。
為了做到這點,我們需要一個簡易的 I/O 模組,讓輸入事件捕捉可以通知 Hypervisor 並觸發設備對應的 GSI 引腳,讓 Guest 感受到中斷的發生,從而處理這些外部輸入的 I/O 事件。
為了統一處理這些輸入,我們設計一個 Host IO 模組,透過 epoll 將外部設備的 FD 與對應的 callback 綁定起來,形成事件導向的抽象。這樣 Hypervisor 只需要處理事件,不需要知道底層是鍵盤、socket 還是其他裝置。
struct host_io_handle {
int fd;
uint32_t events;
host_io_callback callback;
void *opaque;
struct host_io_handle *next;
};
struct host_io {
int epoll_fd;
struct host_io_handle *watchers;
};
typedef void (*host_io_callback)(int fd, uint32_t events, void *opaque);
int host_io_init(struct host_io *io);
struct host_io_handle *host_io_register(struct host_io *io, int fd, uint32_t events,
host_io_callback cb, void *opaque);
int host_io_unregister(struct host_io *io, struct host_io_handle *handle);
int host_io_poll(struct host_io *io, int timeout_ms);
host_io_handle: 用於紀錄被監控的 FD 檔案描述符、期望事件、對應的處理方法 callback、此事件的資料 opaque,另外我預期會實作的設備不多,所以這裡用簡單的 linked list 串接。
同時我們大致上會實作幾個 api,分別用於初始化、註冊 io 模組、取消 io 模組,而觸發事件時只需要開一個 thread 調用 host_io_poll 即可。
在接下來 2 天,我們會以鍵盤輸入當範例,把 stdin 封裝成事件來源,並透過這個 IO 模組註冊。