qemu-kvmの仮想ハードディスクイメージへの読み書き・その1
ゲストOSが仮想HDDイメージにデータを書き込むまでのqemu-kvmの内部動作を追ってみる.
ゲストOSがI/O命令を実行すると,CPUの仮想化支援機能が命令をトラップし,ゲストモードからカーネルモードに移行(VMExit)する.
KVMカーネルモジュール内でVMExit要因の判定を行い,I/O命令の場合はユーザプロセスのqemu-kvmのハードウェアエミュレートが必要になるためカーネルモードからユーザモードへ移行する.
qemu-kvm側では,VMExit要因に応じてエミュレートを行なって,必要であればゲストOS(と言うか仮想マシン)のCPUコンテキストを書き換えたり,割り込みを挿入したり,メモリにデータを書き込んだり,必要な処理を行った後,再びKVMカーネルモジュールにお願いして,ゲストOSの実行を再開する.
というのが,おおまかな流れ.
とりあえず,KVMからqemu-kvmへ処理が戻ったあたりから追って見る.
各仮想CPUの実行は,qemu_kvm_cpu_thread_fn()で行われる.
この関数で実行されているkvm_cpu_exec()のdo-whileループ内で,KVMで仮想CPUの実行する,VMExitで戻ってきたらI/Oイベントを処理する,また仮想CPUを実行する・・・,をずっと繰り返してる.
[qemu-kvm1.2.0]
[cpus.c]
732 static void *qemu_kvm_cpu_thread_fn(void *arg) 733 { ... 755 while (1) { 756 if (cpu_can_run(env)) { 757 r = kvm_cpu_exec(env); 758 if (r == EXCP_DEBUG) { 759 cpu_handle_guest_debug(env); 760 } 761 } 762 qemu_kvm_wait_io_event(env); 763 } ...
[kvm-all.c]
1550 int kvm_cpu_exec(CPUArchState *env) 1551 { .... 1562 do { .... 1580 run_ret = kvm_vcpu_ioctl(env, KVM_RUN, 0); .... 1598 switch (run->exit_reason) { 1599 case KVM_EXIT_IO: 1601 kvm_handle_io(run->io.port, 1602 (uint8_t *)run + run->io.data_offset, 1603 run->io.direction, 1604 run->io.size, 1605 run->io.count); .... 1608 case KVM_EXIT_MMIO: 1610 cpu_physical_memory_rw(run->mmio.phys_addr, 1611 run->mmio.data, 1612 run->mmio.len, 1613 run->mmio.is_write); .... 1616 case KVM_EXIT_IRQ_WINDOW_OPEN: .... 1620 case KVM_EXIT_SHUTDOWN: .... 1625 case KVM_EXIT_UNKNOWN: .... 1630 case KVM_EXIT_INTERNAL_ERROR: .... 1633 default: .... 1638 } while (ret == 0);
今回はI/Oを追うので,VMExitの要因が「KVM_EXIT_IO」だった時に実行されるkvm_handle_io()を追う.
I/Oポートへの読み込みか,書き込みかを判定し,読み書きサイズでスイッチしてる.
[kvm-all.c]
1431 static void kvm_handle_io(uint16_t port, void *data, int direction, int size, 1432 uint32_t count) 1433 { 1434 int i; 1435 uint8_t *ptr = data; 1436 1437 for (i = 0; i < count; i++) { 1438 if (direction == KVM_EXIT_IO_IN) { 1439 switch (size) { 1440 case 1: 1441 stb_p(ptr, cpu_inb(port)); 1442 break; 1443 case 2: .... 1449 } 1450 } else { 1451 switch (size) { 1452 case 1: 1453 cpu_outb(port, ldub_p(ptr)); 1454 break; 1443 case 2: .... 1461 } 1462 } ....
I/Oポートに対して,読み込みだった場合はioport_read(),書き込みだった場合はioport_write()が呼ばれ,各ポートのアドレスごとに関数テーブルに登録された関数が呼び出される.
[ioport.c]
60 static uint32_t ioport_read(int index, uint32_t address) 61 { 62 static IOPortReadFunc * const default_func[3] = { 63 default_ioport_readb, 64 default_ioport_readw, 65 default_ioport_readl 66 }; 67 IOPortReadFunc *func = ioport_read_table[index][address]; 68 if (!func) 69 func = default_func[index]; 70 return func(ioport_opaque[address], address); 71 } 72 73 static void ioport_write(int index, uint32_t address, uint32_t data) 74 { 75 static IOPortWriteFunc * const default_func[3] = { 76 default_ioport_writeb, 77 default_ioport_writew, 78 default_ioport_writel 79 }; 80 IOPortWriteFunc *func = ioport_write_table[index][address]; 81 if (!func) 82 func = default_func[index]; 83 func(ioport_opaque[address], address, data); 84 }
この先は,各仮想ハードウェア(仮想HDD)ごとの処理に移るはず.
次回に続く.