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)ごとの処理に移るはず.
次回に続く.