Plan 9 on KVMでもPCIパススルーしてみる

KVMPlan 9が動くことは何回か紹介してみるが、今回はネットワークデバイスPCIパススルーを使ってアクセスできるか試してみた。結論から言うと、Debian/squeezeの標準カーネル(2.6.32-5-amd64)はNGだけど、2.6.36なら動いた。使用したデバイスIntelのdual port GbEで、コントローラチップとして82576を積んだNICだ。

PCIパススルーとは何かというと、仮想マシンモニタをバイパスして、ゲストOSから直接物理デバイスにアクセスする仕組みのことである。PCIパススルーを使わない場合は、ゲストOSのIO要求は、QEMUでエミュレーションされ、ホストOSのデバイスドライバが物理デバイスにアクセスすることになる。このオーバヘッドは結構馬鹿にならないので、KVMではvirtioとかvhostといった準仮想化方式を取り入れているが、それでも大変なことには変わりない。GbEぐらいならいいが、10 GbEになると、今時のサーバマシンでもちょっと前に調べたときは3 Gbps前後ぐらいしか出なかった*1

PCIパススルーを使うためには、CPUやチップセットIntel VT-dまたはAMD-Viに対応している必要がある。VT-dは何者かとと言うと、ゲストOSの仮想アドレスからホストOSの物理アドレスへの変換や、割り込み(MSI/MSI-X)の変換をハードウェア側でオフロードしてくれる機能だ。つまり、例えばゲストOSがDMA転送を要求したとき、仮想マシンモニタ(KVM)の力を借りることなく、実行できる(実際には仮想マシンモニタが仮想マシンに対して割込みインジェクションする必要があるので、まったく関与しないわけじゃない)。この機能が有効かされているかBIOSのチェックも必要だ。詳細は「How to assign devices with VT-d in KVM」あたりを参照のこと。カーネルオプションの仮想化対応やPCIスタブドライバなどは、普通に有効になっていると思う。あと、コマンドラインオプションに「intel_iommu=on」渡すこともたぶん必要だ。

と長い前説だったが、virt-managerを使えば、デバイスの追加でphysical host deviceを選択し、パススルーしたいデバイスをプルダウンメニューから選べばおしまいで、あまり説明の余地がない。ブリッジを作ってどうのこうのとかに比べると、拍子抜けするほど簡単だ。ただし、冒頭にも書いたがDebian標準の2.6.32-5-amd64カーネルだと、パススルーデバイスに割込みが入った瞬間に固まってしまった。KVMがソフトロックアップしているのだけど、どこかでロックの使い方が間違っている感じだ。2.6.36では問題なく動いているので、その間で修正されたのだろう。Linuxなら問題なく動いていたので、コーナケースを突いたのかもしれない。

[1522830.477435] ------------[ cut here ]------------
[1522830.477451] WARNING: at /build/buildd-linux-2.6_2.6.32-29-amd64-xcs37n/linux-2.6-2.6.32/debian/build/source_amd64_none/arch/x86/kvm/../../../virt/kvm/irq_comm.c:133 kvm_set_irq+0x5e/0x100 [kvm]()
[1522830.477511] Pid: 24780, comm: kvm Not tainted 2.6.32-5-amd64 #1
[1522830.477513] Call Trace:
[1522830.477524]  [<ffffffffa052466d>] ? kvm_set_irq+0x5e/0x100 [kvm]
[1522830.477533]  [<ffffffffa052466d>] ? kvm_set_irq+0x5e/0x100 [kvm]
[1522830.477539]  [<ffffffff8104dd7c>] ? warn_slowpath_common+0x77/0xa3
[1522830.477545]  [<ffffffff81036225>] ? gup_pud_range+0x15e/0x19b
[1522830.477554]  [<ffffffffa052466d>] ? kvm_set_irq+0x5e/0x100 [kvm]
[1522830.477563]  [<ffffffffa0520ea3>] ? kvm_assigned_dev_ack_irq+0x2b/0x5c [kvm]
[1522830.477572]  [<ffffffffa05245f1>] ? kvm_notify_acked_irq+0x87/0xa5 [kvm]
[1522830.477583]  [<ffffffffa053d0c6>] ? picdev_write+0x204/0x31e [kvm]
[1522830.477591]  [<ffffffffa051f1f5>] ? kvm_io_bus_write+0x37/0x50 [kvm]
[1522830.477602]  [<ffffffffa052b5b0>] ? kvm_emulate_pio+0x131/0x155 [kvm]
[1522830.477612]  [<ffffffffa052e132>] ? kvm_arch_vcpu_ioctl_run+0x7ed/0xa44 [kvm]
[1522830.477620]  [<ffffffffa014e3ea>] ? ext3_sync_file+0x86/0xc8 [ext3]
[1522830.477627]  [<ffffffff810ee69a>] ? do_sync_write+0xce/0x113
[1522830.477635]  [<ffffffffa05209d1>] ? kvm_vcpu_ioctl+0xf1/0x4e6 [kvm]
[1522830.477640]  [<ffffffff81064cea>] ? autoremove_wake_function+0x0/0x2e
[1522830.477645]  [<ffffffff810fa5a6>] ? vfs_ioctl+0x21/0x6c
[1522830.477649]  [<ffffffff810faaf4>] ? do_vfs_ioctl+0x48d/0x4cb
[1522830.477653]  [<ffffffff810ef036>] ? vfs_write+0xcd/0x102
[1522830.477657]  [<ffffffff810fab83>] ? sys_ioctl+0x51/0x70
[1522830.477661]  [<ffffffff81010b42>] ? system_call_fastpath+0x16/0x1b
[1522830.477664] ---[ end trace 648d57ba48c33e30 ]---

virt-managerを使わずに、直接qemu-kvmを実行したい場合は、まずlspciコマンドでパススルーしたいデバイスのIDを調べ、次のようにスタブドライバを設定する。今回パススルーするのは05:00.0でベンダIDが8086、デバイスIDが10c9になる。スタブドライバはそのデバイスを間違ってホストOSが使わないようにするダミーのドライバという理解でOK?

# echo "8086 10c9" > /sys/bus/pci/drivers/pci-stub/new_id
# echo "0000:05:00.0" > /sys/bus/pci/devices/0000\:05\:00.0/driver/unbind
# echo "0000:05:00.0" > /sys/bus/pci/drivers/pci-stub/bind
# echo "8086 10c9" > /sys/bus/pci/drivers/pci-stub/remove_id

これは面倒なので、普段はvirshを使っている。

$ sudo virsh nodedev-dettach pci_0000_05_00_0

で、qemuのオプションには、「-device pci-assign,host=05:00.0」を指定する。

こんな感じでPCIパススルーを使えばIO性能を物理マシンと同程度まで改善することができるのだが(といいつつ性能は示していないが)、問題がないわけではない。ゲストOSが排他的に物理デバイスを触ることになるので、他のゲストOSとそのデバイスを共有できない。一台の物理サーバに何台も仮想マシンを集約させるという場合に使うのは難しいだろう。この問題に関してはSR-IOV (Single Root-IO Virtualization)が使えれば改善できる。SR-IOV対応デバイスが必要になるが、複数ゲストOSからの共有が可能になる。まぁ、対応した製品はまだまだ少ないし、Linuxでも対応しているのはigbとixgbぐらいだけど、これから増えてくると期待したい。SR-IOV対応デバイスはVF (Virtual Function)という物理デバイスの機能のサブセットをいくつか持っていて、これを仮想マシンに紐付けてパススルーして使うことになる。VFの数はデバイス依存だが、82576であれば8個、10 GbE版の82599なら64個である。もう一つの問題は、マイグレーションである。ゲストOSがVMMをバイパスしてデバイスを握っているので、物理マシンから引きはがすのが面倒くさい。準仮想化ドライバやPCI hotplugを併用してどうにかしようという提案はあるようだ。

次はSR-IOVが動くかチェックかな。VF用のドライバが別途必要になると思うので*2、そのままじゃ動かないと思うけど。

*1:これは1 VMでの話なので、複数VMで使えば合計バンド幅としてはもう少し出るのかもしれない。

*2:少なくともLinuxでは、igbとは別にVF用にigbvfがある。