QEMUコードリーディングメモ

主にイベントループとKVM周り.

QEMU Main Loop

  • QEMUの主処理はGLibを使ったイベントループ.
  • ゲストのvCPUは別のスレッドで実行(後述)
vl.c:main() =>
  vl.c:main_loop() =>
    util/main-loop.c:main_loop_wait() =>
      loop {
        slirp_pollfds_fill();
        os_host_main_loop_wait() =>
          g_main_context_acquire(context)
          glib_pollfds_fill() =>
            g_main_context_prepare()
            g_main_context_query()

          qemu_mutex_unlock_iothread()
          qemu_poll_ns() =>
            g_poll()
          qemu_mutex_lock_iothread()

          glib_pollfds_poll() =>
            g_main_cotext_disapatch()
          g_main_context_release(context)
        slirp_pollfds_poll()
      }
  • QEMUはiothreadに関してlockを持っている
    • イベントのpoll中はこのiothreadのlockを外す
  • イベントループ処理の流れを理解するにはGLibの動作を知る必要がある.

GLibメモ

  • GSource

    • http://maemo.org/api_refs/4.0/glib/glib-The-Main-Event-Loop.html#GSource
    • GLibにおけるイベントを表現
      • file descriptors
        • file
        • pipe
        • sockets
        • ...
      • timeout event
    • sourceの主要な関数
      • prepare()
        • poll()の前に呼ばれる
        • sourceが(pollすることなく)準備完了であればtrueを返す
            • timeout eventでtimeoutしてたらtrue
            • file descriptorの場合は,通常はpollするのでfalse
      • check()
        • poll()後に呼ばれる
        • dispatchすることができるならtrueを返す
      • dispatch()
        • イベントに対応付けられたcallback関数を実行
    • 各sourceにはpriorityが存在
  • 基本的なイベント処理の流れ

    • g_source_attach()でcontextに対してsourceを追加
    • メインループ
      • g_main_context_prepare(context, &max_priority)
        • コンテキストが持つ各GSourceのprepare()を呼ぶ
        • ここで,max_priorityにprepare()がtrueの中から最大のpriorityが設定される.
      • g_main_context_query(context, max_priority, ..., fds)
        • max_priorityを持つsourceのfile descriptorを取得
        • この関数によって実際にpoll()するfdを選択する
      • g_poll()
        • poll
      • g_main_context_check()
        • dispatch可能なfdをチェック
      • g_main_cotext_disapatch()
        • コールバック関数を呼ぶ

I/O handler

  • 仮想デバイスを処理するためのハンドラ (GSource)
  • 初期化
    • util/main-loop.c:qemu_init_main_loop()
      • iohandler_init() => util/async.c:aio_context_new()
    • g_source_attach()でiohandlerをdefault contextのsourceに追加
  • コールバック関数の登録
    • util/iohandler.c:qemu_set_fd_handler()

KVM 初期化

  • 全体の初期化
    • accel/kvm/kvm_all.c:kvm_init()
vl.c:main() =>
  accel/accel.c:configure_accelerator() =>
    accel_init_machine() =>
       acc->init_machine() = accel/kvm/kvm_all.c:kvm_init()
  • マシン初期化時に,vCPUごとにスレッドが作成される.
target/i386/cpu.c:x86_cpu_realizefn() =>
    cpus.c:qemu_init_vcpu() =>
        qemu_kvm_start_vcpu()
            qemu_kvm_start_vcpu() =>
                qemu_thread_create(..., qemu_kvm_cpu_thread_fn, ...)

KVM vCPU event loop

cpus.c:qemu_kvm_cpu_thread_fn =>
  qemu_mutex_lock_iothread()
  loop {
      accel/kvm/kvm-all.c:kvm_cpu_exec() =>
         qemu_mutex_unlock_iothread()
         do {
            kvm_vmrun_ioctl(cpu, KVM_RUN, 0)
            ret = handle_vmexit()
         }while(ret == 0);
         qemu_mutex_lock_iothread()
      qemu_wait_io_event()
  }
  qemu_mutex_unlock_iothread()
  • 各vCPUはiothreadのロックを持つ
  • KVM_RUN実行中はiothreadのロックを外す

KVMRUN

  • ※ これはLinux側の処理
virt/kvm/kvm_main.c:kvm_vcpu_ioctl() =>
  arch/x86/kvm/x86.c:kvm_arch_vcpu_ioctl_run() =>
    vcpu_run() =>
      loop {
         ret = {
            vcpu_enter_guest() =>
              kvm_x86_ops->run(vcpu) = arch/x86/kvm/vmx.c:vmx_vcpu_run() =>
                VMLAUNCH
            kvm_x86_ops->handle_exit(vcpu) = arch/x86/kvm/vmx.c:vmx_handle_exit() =>
              kvm_vmx_exit_handlers[exit_reason](vcpu)
         }
         if (ret >= 0) break
      }
  • VMCALLの処理
arch/x86/kvm/vmx.c:handle_vmcall() =>
  arch/x86/kvm/x86.c:kvm_emulate_hypercall() =>
    handle_vm_call or kvm_skip_emulated_instruction()
  • VMCALLはKVM内で処理され,ioctl側に制御が戻らない
  • VMCALLでioctl側に制御を渡すには,KVMの修正が必要

続く?