2012年12月19日水曜日

vSphere環境でリンクドクローンVMを大量生成

仮想マシンが大量にほしかったので ESXi 上でリンクドクローンを量産しようとおもったら UI 上からできなかった。 PowerCLI からごにょごにょ。


$SRC_VM で指定した仮想マシンが持っている既存スナップショットを親とするからっぽの Delta ディスクを生成。


$SRC_VM= "Source VM Name"

Add-PSSnapin VMware.VimAutomation.Core

$vi = Connect-VIServer -Server IP.add.re.ss -User root -Password vmware
$esx = Get-VMHost ESXi.Host.Name

$vm= Get-VM $SRC_VM | Get-View
$cloneFolder = $vm.parent

$cloneSpec = new-object Vmware.Vim.VirtualMachineCloneSpec
$cloneSpec.Location = new-object Vmware.Vim.VirtualMachineRelocateSpec
$cloneSpec.Location.Host = (get-vm $SRC_VM| get-vmhost | get-view).MoRef
$cloneSpec.Location.Datastore = (get-datastore -vm $SRC_VM | get-view).MoRef

$cloneSpec.Snapshot = $vm.Snapshot.CurrentSnapshot

$cloneSpec.Location.DiskMoveType = [Vmware.Vim.VirtualMachineRelocateDiskMoveOptions]::createNewChildDiskBacking

1..100 | foreach {
    $DEST_VM= ("clone-" + $_)
    $vm.CloneVM( $cloneFolder, $DEST_VM, $cloneSpec )
}
最初は SOAP 経由になるかと思ったけど PowerCLI のレベルで済んで良かった。 PowerCLI なら Exe ファイルひとつふたつでインストールがおわるから環境作るのも楽だしね。


2012年12月14日金曜日

割り込みはいかにしてゲストの割り込みハンドラに届くのか

こんばんわ。出遅れましたが12月13日は カーネル/VM Advent Calendar 2012 にて私の当番、といっても一昨年、去年ほど頑張ってネタを仕込めていないので、昨晩調べてたことを軽くまとめておきたいと思います。


ご存じのとおり、Linux KVMやFreeBSDのBHyVeなどはメインループをユーザモードプロセスに持たせる形で仮想マシンが実装されています。また、CPUにはIntel VTもしくはAMD-Vと呼ばれる仮想化支援機能が搭載されており、これらが仮想マシンの実行を行う命令群を提供します。
ただ、実際にはI/O操作など非同期で処理が行われるものがあります。例えば、わかりやすいのは下記の三点でしょう。


  • タイマ割り込み


  • ディスクI/Oの完了通知


  • ネットワークパケットの到着通知


割り込みは遅延させずに通知させる必要があります。たとえば、タイマ割り込みは遅延するとゲスト内で正確な時間をカウントできなくなります(もっとも従来の割り込みを使った時刻の管理が仮想マシンで適しているとは思わないのですが)。
時刻同期のほかにも、例えばディスクI/Oの割り込みも大事ですね。例えばデータベースの更新トランザクションを考えてみます。一般的にデータベースのトランザクション保護はログ領域やダブルライト領域にデータの書き込みが完了してから、次のトランザクションや実際のデータ領域の更新に着手します。このためディスクI/Oが完了したことがリアルタイムに通知されることは非常に大事です。
割り込みは、VMENTERするときにフィールドを指定することによりゲストに挿入できます。が、もしVMが実行されている状況で割り込みが発生したとしたらどういう風に通知されるんだろう、と思って、今回は、割り込みが通知されてからVMENTERするあたりのロジックをちょっとだけ追いかけてみました。


■ KVM編

KVMのソースコードを見てみたところ、kvm_lapic_find_highest_irr() に、まさにその答えがありました。割り込みを挿入したい場合には、 kvm_vcpu_kick() を介してVMEXIT を促します。

arch/x86/kvm/lapic.c

int kvm_lapic_find_highest_irr(struct kvm_vcpu *vcpu)
{
        struct kvm_lapic *apic = vcpu->arch.apic;
        int highest_irr;

        /* This may race with setting of irr in __apic_accept_irq() and
         * value returned may be wrong, but kvm_vcpu_kick() in __apic_accept_irq
         * will cause vmexit immediately and the value will be recalculated
         * on the next vmentry.
         */
        if (!apic)
                return 0;
        highest_irr = apic_find_highest_irr(apic);

        return highest_irr;
}


kvm_vcpu_kick() 自体は kvm_main.c にありました。

kvm_main.c

#ifndef CONFIG_S390
/*
* Kick a sleeping VCPU, or a guest VCPU in guest mode, into host kernel mode.
*/
void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
{
        int me;
        int cpu = vcpu->cpu;
        wait_queue_head_t *wqp;


        wqp = kvm_arch_vcpu_wq(vcpu);
        if (waitqueue_active(wqp)) {
                wake_up_interruptible(wqp);
                ++vcpu->stat.halt_wakeup;
        }


        me = get_cpu();
        if (cpu != me && (unsigned)cpu < nr_cpu_ids && cpu_online(cpu))
                if (kvm_arch_vcpu_should_kick(vcpu))
                        smp_send_reschedule(cpu);
        put_cpu();
}
#endif /* !CONFIG_S390 */

vCPUが停止状態であればそれを活性化し、またvCPUが稼働中であればvCPUに対して割り込みを送ることで、仮想マシンから強制的にVMEXITさせます。このあとLinuxカーネルが再度vCPUをスケジュールする際に割り込みが挿入されるわけですね。

■ BHyVe 編

続いて BHyVe の場合どうなっているかを見てみます。BHyVeを調べるにあたっては、PCIデバイスのエミュレーション部分のMSI挿入用関数である pci_generate_msi() を起点に追いかけてみます。

src/usr.sbin/bhyve/pci_emul.c

void
pci_generate_msi(struct pci_devinst *pi, int msg)
{

        if (pci_msi_enabled(pi) && msg < pci_msi_msgnum(pi)) {
                vm_lapic_irq(pi->pi_vmctx,
                             pi->pi_msi.cpu,
                             pi->pi_msi.vector + msg);
        }
}


vm_lapic_irq() という関数が割り込みを挿入していますが、これは libvmmapi で提供されるライブラリ関数です。実際には仮想マシンのファイル識別子に ioctl() を投げるラッパです。

src/lib/libvmmapi/vmmapi.c

int
vm_lapic_irq(struct vmctx *ctx, int vcpu, int vector)
{
        struct vm_lapic_irq vmirq;

        bzero(&vmirq, sizeof(vmirq));
        vmirq.cpuid = vcpu;
        vmirq.vector = vector;

        return (ioctl(ctx->fd, VM_LAPIC_IRQ, &vmirq));
}



この ioctl() を受け取るコードは BHyVe の肝ともいえる vmm.ko 内にあります。 ioctl() に応じてVMENTERしたりするわけですが、割り込みのインジェクションも ioctl() なんですね。

src/sys/amd64/vmm/vmm_dev.c

static int
vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
             struct thread *td)
{
        省略        /*
         * Some VMM ioctls can operate only on vcpus that are not running.
         */
        switch (cmd) {
        case VM_RUN:
        case VM_SET_PINNING:
        case VM_GET_REGISTER:
        case VM_SET_REGISTER:
        case VM_GET_SEGMENT_DESCRIPTOR:
        case VM_SET_SEGMENT_DESCRIPTOR:
        case VM_INJECT_EVENT:
        case VM_GET_CAPABILITY:
        case VM_SET_CAPABILITY:
        case VM_PPTDEV_MSI:
        省略
        case VM_LAPIC_IRQ:
                vmirq = (struct vm_lapic_irq *)data;
                error = lapic_set_intr(sc->vm, vmirq->cpuid, vmirq->vector);
                break;
        省略}


VM_LAPIC_IRQの処理の実体は vmm_lapic.c にありました。ここでは、 vlapic_set_intr_ready() で「仮想マシンに割り込みが来ている」というフラグを立てて、さらにここから vmm_ipi.c の中の vm_interrupt_hostcpu() 関数を呼んでいます。IPIとはInter Processor Interruptですかね。

src/sys/amd64/vmm/vmm_lapic.c

lapic_set_intr(struct vm *vm, int cpu, int vector)
{
        struct vlapic *vlapic;

        if (cpu < 0 || cpu >= VM_MAXCPU)
                return (EINVAL);

        if (vector < 32 || vector > 255)
                return (EINVAL);

        vlapic = vm_lapic(vm, cpu);
        vlapic_set_intr_ready(vlapic, vector);

        vm_interrupt_hostcpu(vm, cpu);

        return (0);
}


src/sys/amd64/vmm/vmm_ipi.c

void
vm_interrupt_hostcpu(struct vm *vm, int vcpu)
{
        int hostcpu;

        if (vcpu_is_running(vm, vcpu, &hostcpu) && hostcpu != curcpu)
                ipi_cpu(hostcpu, ipinum);
}


ここで呼び出している ipi_cpu() という関数はカーネル内でCPUを強制的にコンテクストスイッチしたりNMI突っ込んだりする際に使われる関数のようです。なるほど、これで仮想マシンから強制的にVMEXITできるんですね。

src/sys/amd64/amd64/mp_machdep.c

void
ipi_cpu(int cpu, u_int ipi)
{

        /*
         * IPI_STOP_HARD maps to a NMI and the trap handler needs a bit
         * of help in order to understand what is the source.
         * Set the mask of receiving CPUs for this purpose.
         */
        if (ipi == IPI_STOP_HARD)
                CPU_SET_ATOMIC(cpu, &ipi_nmi_pending);

        CTR3(KTR_SMP, "%s: cpu: %d ipi: %x", __func__, cpu, ipi);
        ipi_send_cpu(cpu, ipi);
}


VMEXITされると、vmm.koから仮想マシンを実行するプロセスであるbhyveコマンドに制御が移ります。この時点で「次回はMSIを発生させるぞ」というステータスになっているわけなので、bhyveコマンドが次に仮想マシンを実行すると割り込みが挿入され、ゲストOSの割り込みハンドラがトリガされるわけですね。

qemu-kvmでもbhyveでも、強制的にVMEXITさせた後にプロセスがpreemptするかどうかはOSのスケジューラの動作にかかってくる感じでしょうか。たとえばVMENTER直後でタイムスライスを使い切っていない状態であれば、VMEXITした後にコンテクストスイッチをはさまずVMENTERすることもあるでしょう。VMENTERしてから暫く時間が経過していると、VMEXITと同時に他のプロセスにスイッチされちゃうのかな。

2012年7月31日火曜日

BHyVeハッカソンに参加してきました(3)

BHyVeハッカソンでの個人的な取り組みについて紹介してきます。
スライドを一応準備しました(二日目 4:00pm時点。その後いろいろと改良済み)





BHyVeはまだ誕生したばかりの仮想マシンモニタであり管理ツールなどもぜんぜん充実していません。正直なところ実用レベルに達するのは当分先の話でしょう(だからこそいじりがいがある、というものですが)。


実は BHyVe にはまだ仮想マシンをリスト表示するコマンドすら存在していないので、 vmmls というコマンドを実装してみました。


BHyVe では、仮想マシンを作成すると /dev/vmm/仮想マシン名 という名前で特殊な制御ファイルとして仮想マシンが見えるようになります。たとえば myguest という仮想マシンであれば /dev/vmm/myguest という名前です。

ls -l /dev/vmm/ とすれば仮想マシンの一覧はとれるのですが、ただそれだけなのはビミョーなので簡単なツールと作ってみました。 vmmls コマンドを実行すると仮想マシンの一覧と、現時点では highmem で指定されたメモリ量が表示されます。

また、仮想マシンに割り当て可能なメモリ量を表示するようにしてみました。

実は、「この仮想マシンに割り当て可能なメモリ量」を知ることは、現時点のBHyVeでは普通にはできません。なぜならば、BHyVeのメモリ管理機能は、以下の機能を提供しますが、本当にこれだけのことをできるだけの最低限の実装しかありませんでした。

  • FreeBSDが使っていないメモリを自分のものとしてプールの管理下におく
  • 必要に応じて仮想マシンにメモリをプールから配る
  • 仮想マシンからメモリが返却されたらプールに戻す

管理者などが仮想マシンモニタのメモリ利用状況を知る方法はなく、あえていえば仮想マシンモニタのメモリ管理機能がもっているデータを計算すれば求められる、という状態でした。カーネルモードに手を出すかなやんだのですが、結局以下のとおり実装しました。

int
vmm_mem_get_mem_free(void)
{
    int length = 0;
    int i;

    mtx_lock(&vmm_mem_mtx);
    for (i = 0; i < vmm_mem_nsegs; i++) {
        length += vmm_mem_avail[i].length;
    }
    mtx_unlock(&vmm_mem_mtx);

    return(length);
}

この関数の値は(最初はioctlで取得できたのですが)最新のコードではsysctlにて取得できます。

[root@vm160 ~/bhyve/sys/amd64/vmm]# sysctl hw.vmm
hw.vmm.mem_free: 0 ← BHyVeがメモリをもっていたらここにバイト単位で表示される
hw.vmm.mem_total: 0
hw.vmm.create: beavis
hw.vmm.destroy: beavis

また、最新版ではメモリのフリーエリアだけでなくBHyVeがコントロール下においているメモリ総量もわかるようにしました(mem_totalがそれです)。

これらの値は sysctl にて hw.vmm.* を参照すればわかりますが、一応 libvmmapi 経由でこれらの値がとれるようにライブラリも拡張しました。

というわけで、

  • 仮想マシンの一覧表示ができるようになった

  • 仮想マシンモニタのメモリ(合計および空き)がバイト単位でわかるようになった

  • vmmapi に仮想マシンモニタのメモリ情報を取得する関数を追加した

というところで二日間終了しました。成果物についてははやめに BHyVe 開発者に送付できるように整理するつもりです。

関連記事



BHyVeハッカソンに参加してきました(2)

BHyVeハッカソンは二日間にわたって開催されましたが、最初の半日は仮想化技術にまつわるプレゼンテーションがなされました。
  • Linux KVMとBHyVeの実装を考慮しつつの「仮想マシンの仕組み」 (私がやりました)
  • ハードウエアやら仮想化やらの深いお話
  • BHyVeってなんぞや
プレゼンテーションの後、必要な人には作業用マシンが割り当てられたりして、各々のトピックで作業を開始。気づけば皆、時がたつのを忘れてカーネルモードのコードをいじったりBHyVeをビルドしまくったりとかしていたようです。



私は会場近くのホテルにとまったのですが、最初の一時間はローカルアレンジメントの担当者と私の二人だけでした。。。。二日目はスロースタートでし
たが、ハッカソンはもりあがり、結局撤収は日曜日の20時頃にはじまりました(翌日は仕事とかが普通なのによくがんばるわ・・・)。

なお各自で取り組んだトピックは様々でしたが、私が把握している限りでは、ほかの方々は

  • BIOSコールの実装。未改造のブートローダなどを動かすことを目的としている
  • BHyVeのベンチマーク
  • コンソールの性能改善
  • クラッシュの原因となるパスの解消
  • 仮想マシンの状態保存/復帰
  • NetBSDへのポート

と様々な取り組みをしていました。驚くべきことに参加者のほとんどがカーネルモードで戦いをしていたのが衝撃的でした(私はユーザモードをいじっていたw)。

イベントがおわってからは近くのホルモンやさんで軽く焼き肉をしてから解散。久々にコードをいじったのでいい気分転換になりました。

関連記事




BHyVeハッカソンに参加してきました(1)

7月28日〜29日に埼玉県内某所にてBHyVeハッカソンが開催されました。BHyVeとはFreeBSD向けの全く新しい仮想マシンモニタです。


BHyVeについて簡単に紹介しておきましょう。2006年にXen 3.0がVT-xを使って完全仮想化を実現し、その後にLinux KVMが続きました。これらの仮想マシンモニタの設計上の大きな相違点は、ハイパーバイザーとして必要となるスケジューラなどの機能を新たに実装したか、
それともホストOSにある機能をうまく再利用したか、という点が第一に挙げられるでしょう。

もうすこし細かく解説しておくと、Xenの場合は何もなくてもXen自体が複数のOSを実行できます(管理のためにdomain-0が必要なのはここでは考慮しません)。それに対してLinux
KVMはユーザプロセスのひとつという位置づけで仮想マシンを実行するためのカーネルモジュールで、それ自体はスケジューラの機能をもちません。共通していえるのは、XenもLinux KVMもVT-xを使ったオープンソースの仮想マシンモニタであり、未改造のLinuxやWindowsなどのOSを実行できる、ということです。

Linux
KVMは、Xenでは実装しているようなスケジューラなどの機能を実装していないためコードが短くすんでいますが、BHyVeはさらに短いコードで実装されています。BHyVeは、以下の制約を受け入れることにより本体自体に非常にシンプルに実装しているのです。

  • VT-xなどのCPU仮想化機能を必須とする
  • EPTなどのメモリ仮想化機能を必須とする
  • I/OはBHyVe専用コンソールとvirtio、もしくはVT-dによるパススルーに限定
  • I/Oの実装は最小限
これらの制約はおそらく開発者の人的リソースによる事情もあるかと思いますが、Xenからに影響をうけつつ、不要な部分を取り除いてコンパクトに実装したものがLinux KVMであるとしたら、さらに KVM のうち一部の機能を割り切って実装したものが BHyVe であるといえます。なのでKVM が理解できれば BHyVe は理解できますし、逆に BHyVe を理解できれば KVM も理解できるでしょう。