到目前為止,我們的 Guest 雖然能跑起來,但它沒有辦法真實地「感受到」外部事件。
沒有 IRQ、沒有鍵盤輸入,等於只是一個單純的 while(1) loop。
I/O thread 的目的:讓 Host 能在背景持續監聽外部事件(timer、鍵盤),並把這些事件透過 fd 或共享記憶體注入給 Guest。這是 Guest 能「和外界互動」的第一步。
昨天我完成了 I/O 模組的實作,讓 Host 可以定期送出 timer 中斷,也能把鍵盤事件注入給 Guest。但目前這些事件只能靠 vm-exit 被動處理,對 Guest 來說還不是真正的「中斷」。為了達到和硬體行為一致的效果,我需要額外建立一個獨立的 I/O thread,把所有事件集中監聽、轉換成 IRQ,並即時注入 Guest。
今天的任務,就是要在 Guest 端補齊這個缺口:建立 IDT、初始化 PIC,讓這些來自 I/O thread 的事件,能夠以「中斷」的形式被正確接收並交給 ISR。這樣 Hypervisor 的基礎互動路徑才算真正完成。
static void start_io_thread(struct vm_runtime *vm)
{
if (vm->io_thread_running)
return; // 確保只建立一次,避免重複啟動
vm->io_thread_stop = 0;
if (pthread_create(&vm->io_thread, NULL, io_thread_main, vm) != 0)
die("pthread_create io_thread");
vm->io_thread_running = 1; // 標記執行緒已經成功啟動
}
這個函式只負責一件事:啟動 I/O thread,而且只啟一次。旗標 io_thread_stop 必須在 pthread_create 之前清為 0,這樣新 thread 才能在正確狀態下進入主迴圈。
static void *io_thread_main(void *opaque)
{
struct vm_runtime *vm = opaque;
if (!vm)
return NULL;
while (!vm->io_thread_stop) {
int ready = host_io_poll(&vm->host_io, 100); // 對接 epoll
if (ready < 0) {
if (errno == EINTR)
continue;
perror("host_io_poll");
break;
}
}
return NULL;
}
這個迴圈是 epoll 的唯一擁有者,所有註冊的事件(timerfd、eventfd、鍵盤輸入)都由這條 thread 處理。
由於僅有 I/O thread 處理外部事件,因此不必加鎖。
host_io_poll 內部呼叫 epoll_wait,timeout 設為 100ms。這個 timeout 有兩個目的:
讓關閉流程最久 100ms 就能生效,不需要另建喚醒機制。
保證執行緒即使沒有事件,也能週期性醒來檢查停止旗標。
如果 epoll_wait 被訊號打斷(errno = EINTR),則直接重試;其它錯誤則輸出訊息後跳出,避免卡住。