QEMU (简体中文)

From ArchWiki
翻译状态:本文是 QEMU翻译。上次翻译日期:2021-1-14。如果英文版本有所更改,则您可以帮助同步翻译。

来自 QEMU 关于页面:“Qemu”是一个广泛使用的开源计算机模拟器和虚拟机。"

当作为模拟器时,可以在一种架构(如x86 PC)下运行另一种架构(如ARM)下的操作系统和程序。通过使用动态转换,它可以获得非常好的性能。

作为虚拟机时,QEMU可以使用其他虚拟机管理程序(如 XenKVM)来使用CPU扩展(HVM)进行虚拟化,通过在主机CPU上直接执行客户机代码来获得接近于宿主机的性能。

安装

安装 qemu[损坏的链接:package not found],(或 qemu-headless[损坏的链接:replaced by qemu-base],一个没有GUI的版本)并根据需要安装下面的可选软件包:

除了这些之外, qemu-user-staticAUR 提供了qemu在user-mode下运行静态编译程序的支持

QEMU 变种

QEMU提供了多个变种以供用户在不同场景中使用。

粗略地说,QEMU有两种运行模式:

全系统模拟模式 (full-system emulation)
在该模式下, QEMU将会模拟一个完整的系统,包含一个或多个处理器以及各种外围设备。这种模式更加贴近真实的系统,且这种模式不要求被模拟的客户机系统是Linux,但它的速度较慢。
QEMU中启用full-system模式的命令依照如下规则进行命名 qemu-system-目标机器架构, 例如 qemu-system-x86_64 用于模拟64位intel架构CPU, qemu-system-i386 模拟32位intel架构CPU, qemu-system-arm 模拟ARM架构(32 位), qemu-system-aarch64 模拟ARM架构(64位), 等等。
如果模拟的CPU架构与宿主机的CPU架构相同, 那么即使在此模式下,QEMU仍有可能使用hypervisor(例如KVM or Xen)的技术对模拟机进行加速。
用户模式(Usermode emulation)
在此模式下, QEMU能够利用宿主机的系统资源来调用为其他架构编译的Linux可执行文件。当然,里面依旧存在一些小问题, 比如说一些功能特性没有被实现, 采用动态链接的可执行文件无法直接在上面使用 (参阅#从x86_64环境中Chroot至arm/arm64环境解决该问题) 并且只支持Linux程序 (尽管我们可以使用wine在Linux上运行windows程序)。
QEMU中启用user模式的命令依照如下规则进行命名 qemu-目标机器架构, 例如 qemu-x86_64 用于模拟intel 64位的CPU。

QEMU 拥有动态链接和静态链接两个变种:

动态链接 (默认)
qemu-* 命令依赖于宿主机上的库文件, 因此整个程序体积较小。
静态链接
qemu-* 命令则可以在任何架构相同的Linux系统上使用。

在Arch Linux中,全系统模拟有两个变种:

有界面 (Non-Headless) (默认)
这个变种启用了一些GUI相关的特性,需要额外的依赖(例如SDL或GTK)。
无界面 (Headless)
这个变种不需要GUI相关的依赖 (适用于服务器场景)。

需要注意这两个版本的安装名是一样的 (例如 qemu-system-x86_64) 因此系统上不能同时安装这两个版本。

Arch Linux中相关安装包的详细信息

注意: 目前为止,Arch Linux 无论是在官方仓库还是AUR仓库中都不提供full-system模式和静态链接的QEMU变种,原因是很少有用户需要它们

QEMU 的图形前端

与其他的虚拟化程序如 VirtualBoxVMware 不同, QEMU不提供管理虚拟机的GUI(运行虚拟机时出现的窗口除外),也不提供创建具有已保存设置的持久虚拟机的方法。除非您已创建自定义脚本以启动虚拟机,否则必须在每次启动时在命令行上指定运行虚拟机的所有参数。

Libvirt提供了一种管理 QEMU 虚拟机的便捷方式。有关可用的前端,请参阅 libvirt 客户端列表

QEMU的其他图形化前端:

  • AQEMU — QEMU GUI Qt5编写.
https://github.com/tobimensch/aqemu || aqemuAUR

创建新虚拟系统

创建硬盘镜像

警告: 本页面的英文wiki中,对该节的准确性提出了怀疑,暂时停止该部分的翻译,原页面告警如下: If I get the man page right the raw format only allocates the full size if the filesystem does not support "holes" or it is explicitly told to preallocate. See man qemu-img in section Notes. (Discuss in Talk:QEMU#)
提示: 有关QEMU镜像的更多信息,请参阅 QEMU Wikibook

除非直接从 CD-ROM 或网络引导(并且不安装系统到本地),运行 QEMU 时都需要硬盘镜像。硬盘镜像是一个文件,存储虚拟机硬盘上的内容。

一个硬盘镜像可能是 raw镜像, 和客户机器上看到的内容一模一样,并且将始终使用主机上的来宾硬盘驱动器的全部容量。此方法提供的I / O开销最小,但可能会浪费大量空间,因为guest虚拟机上未使用的空间无法在主机上使用。

另外一种方式是qcow2 格式,仅当客户系统实际写入内容的时候,才会分配镜像空间。对客户机器来说,硬盘大小表现为完整大小,即使它可能仅占用主机系统上的非常小的空间。此映像格式还支持QEMU快照功能(有关详细信息,请参阅 #通过 monitor console 创建快照和管理快照)。但是,使用此格式而不是 raw 可能会影响性能。

QEMU 提供 qemu-img命令创建硬盘镜像.例如创建一个 4 GB raw 格式的镜像:

$ qemu-img create -f raw image_file 4G

您也可以用 -f qcow2 创建一个 qcow2 镜像。

ddfallocate 也可以创建一个 raw 镜像。

注意: 您也可以通过 ddfallocate 创建一个所需大小的 raw 镜像。
警告: 如果硬盘镜像存储在 Btrfs 系统上,则应在创建任何映像之前考虑禁用该目录的 写时复制

上层存储镜像

可以创建一个基础镜像,and have QEMU keep mutations to this image in an overlay image. This allows you to revert to a previous state of this storage image. You could revert by creating a new overlay image at the time you wish to revert, based on the original backing image.

To create an overlay image, issue a command like:

$ qemu-img create -o backing_file=img1.raw,backing_fmt=raw -f qcow2 img1.cow

After that you can run your QEMU VM as usual (see #运行虚拟化的系统):

$ qemu-system-i386 img1.cow

The backing image will then be left intact and mutations to this storage will be recorded in the overlay image file.

When the path to the backing image changes, repair is required.

警告: The backing image's absolute filesystem path is stored in the (binary) overlay image file. Changing the backing image's path requires some effort.

Make sure that the original backing image's path still leads to this image. If necessary, make a symbolic link at the original path to the new path. Then issue a command like:

$ qemu-img rebase -b /new/img1.raw /new/img1.cow

At your discretion, you may alternatively perform an 'unsafe' rebase where the old path to the backing image is not checked:

$ qemu-img rebase -u -b /new/img1.raw /new/img1.cow

调整镜像大小

警告: 调整包含NTFS引导文件系统的镜像将无法启动已安装的操作系统,推荐在操作之前进行备份

执行 qemu-imgresize 选项调整硬盘驱动镜像的大小.它适用于 rawqcow2. 例如, 增加镜像 10 GB 大小, 运行:

$ qemu-img resize disk_image +10G

在磁盘映像扩容后,必须使用虚拟机内部系统的分区工具对该镜像进行分区并格式化后才能真正开始使用新空间。 在收缩磁盘映像时,必须首先使用虚拟机内部系统的分区工具减少分该分区的大小,然后相应地收缩磁盘映像,否则收缩磁盘映像将导致数据丢失!

准备安装介质

要将操作系统安装到您的磁盘镜像, 你需要操作系统的安装介质 (例如 光盘, USB设备, 或 ISO 镜像). 不要挂载安装介质,因为 QEMU 要直接访问媒体。

提示: 如果使用光盘,最好先将媒体转储到文件中,因为这既可以提高性能,又不需要您直接访问设备(也就是说,您可以将QEMU作为普通用户,而无需更改对媒体设备文件的访问权限)。例如,如果CD-ROM设备节点名为/dev/cdrom,则可以使用以下命令将其转储到文件中:
$ dd if=/dev/cdrom of=cd_image.iso

安装操作系统

这是你第一次需要去启动模拟器的步骤,为了在磁盘镜像上安装操作系统,你必须同时将磁盘镜像与安装介质装载到虚拟机上,从安装介质中启动操作系统。

以i386的客户机为例,为了从CD-ROM内的把可用于启动的ISO文件安装到磁盘镜像上,你需要:

$ qemu-system-x86_64 -cdrom iso_image -boot order=d -drive file=disk_image,format=raw

参阅 qemu(1) 获得更多关于不同类型安装介质的信息 (例如floppy,磁盘镜像和物理驱动盘),参阅 #运行虚拟化的系统 了解更多有用的选项。

在安装完操作系统后,就可以直接从QEMU镜像内启动了。(参阅 #运行虚拟化的系统

注意: 默认情况下仅分配给虚拟机128MB的内存, 分配的内存大小可以通过 -m 调整, 比如 -m 512M-m 2G
提示:
  • 相较于指定 -boot order=x ,一部分用户感觉使用 -boot menu=on 启用boot菜单的体验更舒服些,至少在配置和实验时是这样的。
  • 当使用无界面(headless)模式时, 将会默认在本地5900端口启动一个VNC服务器, 可以用 TigerVNC 连接到客户机的系统上: vncviewer :5900
  • 若你在安装过程中需要替换软盘或CD,可以使用QEMU机器监视器(在虚拟机窗口中按Ctrl + Alt + 2)来删除存储设备并将其连接到虚拟机。使用 info block查看块设备,然后使用change命令换出设备。按下 Ctrl + Alt + 1返回虚拟机。

运行虚拟化的系统

qemu-system-* 程序 (例如 qemu-system-i386qemu-system-x86_64, 取决于客户机架构)用来运行虚拟化的客户机. 用法是:

$ qemu-system-i386 options disk_image

所有 qemu-system-*的选项是相同的,参见 qemu(1) 查看文档和所有选项

默认 QEMU会在窗口中显示虚拟机的视频输出.有一点要记住:当您单击QEMU窗口,鼠标指针被捕获。要放开,按 Ctrl+Alt+g.

警告: QEMU 不应以 root 身份运行. 如果必须以root身份在某个脚本中运行QEMU,那么你需要使用 -runas 选项让QEMU放弃root权限

启用 KVM

KVM 必须要您处理器和内核支持, 和必要的 kernel modules加载. 更多信息参见 KVM.

要在KVM模式中启动QEMU, 追加 -enable-kvm到启动选项. To check if KVM is enabled for a running VM, enter the QEMU Monitor using Ctrl+Alt+Shift+2, and type info kvm.

注意:
  • -machine 选项中的 accel=kvm 参数与-enable-kvm-accel kvm 选项是等价的。
  • CPU模型 host 需要 KVM。
  • 如果你使用GUI工具去启动QEMU,但是性能体验极差,那么最好检查一下是否真的开启了KVM支持,因为QEMU可能选择了备用的模拟模式,即软件级模拟。
  • 需要启用KVM才能正常启动windows7和windows8,否则会出现“蓝屏”.

启用 IOMMU (Intel VT-d/AMD-Vi) 的支持

首先启用IOMMU, 参阅 PCI passthrough via OVMF#Setting up IOMMU.

添加 -device intel-iommu 选项创建IOMMU设备:

$ qemu-system-x86_64 -enable-kvm -machine q35 -device intel-iommu -cpu host ..
注意: 在基于Intel CPU的系统上用 -device intel-iommu 创建QEMU内的IOMMU设备将会禁用PCI直通, 并返回一个像这样的错误报告:
Device at bus pcie.0 addr 09.0 requires iommu notifier which is currently not supported by intel-iommu emulation
, 虽然仍然需要添加内核参数 intel_iommu=on 来重新映射IO(例如,通过vfio-pci的PCI直通),但如果需要PCI直通,则不应设置-device intel-iommu

宿主机和虚拟机数据交互

网络

我们可以利用任何支持文件传输的网络协议实现客户机和宿主机之间的数据交互, 例如 NFS, SMB, NBD, HTTP, FTP, 或 SSH, 当然这么做的前提是你已经配置好二者之间的网络,且在系统上启动了相应的服务程序。

在默认情况下,用户模式的客户机能够通过10.0.2.2这个IP访问到宿主机。任何运行于宿主机上的服务端程序都可以通过这个地址被访问到,比如说我们可以通过这个IP访问到宿主机上的SSH服务器或SMB服务器。因此在这种情况下,客户机能够挂载宿主机通过SMB or NFS暴露出来的目录,也可以访问宿主机上的HTTP服务器等。 通常情况下宿主机无法访问客户机上的服务,不过你也可以通过一些特殊的网络配置达到这个目的 (参阅#Tap 网络)

QEMU 端口转发

QEMU能够将宿主机的端口转发到客户机上以实现一些功能,例如从宿主机上访问客户机的SSH端口。

举个例子,将宿主机上的10022端口与客户机上的22 (SSH) 端口进行绑定, 对应的QEMU命令如下:

$ qemu-system-x86_64 disk_image -nic user,hostfwd=tcp::10022-:22

确认你客户机上的sshd程序正在运行,然后可以通过如下命令连接到客户机的SSH端口

$ ssh guest-user@localhost -p 10022

你可以用 SSHFS 把客户机的整个文件系统都挂到宿主机上,这样就可以在宿主机上对客户机的文件系统进行读写了。

想进行多端口转发的话, 只需要在-nic参数中指定多个hostfwd, 以VNC端口为例:

$ qemu-system-x86_64 disk_image -nic user,hostfwd=tcp::10022-:22,hostfwd=tcp::5900-:5900

QEMU 的内置SMB服务器

QEMU的文档中指出它有一个内置的SMB服务器,但实际上,它只是在宿主机上加载一个自动生成的smb.conf配置文件 (位于/tmp/qemu-smb.random_string),然后启动宿主机上的Samba,使得客户机能够通过一个IP地址进行访问 (默认的IP地址是10.0.2.4)。这个方法只适用于用户网络,在你不想在宿主机开启通常的Samba服务 (客户机同样能访问这类Samba服务) 时这个方法还挺好用的。

选项smb=可以设置仅共享一个目录,如果QEMU的SMB配置允许用户使用符号链接,那么即使在虚拟机运行时新加入更多的目录也很容易,只需要通过在共享目录里创建相应的软链接就行。然而他并没有这么配置,我们可以依照如下进行配置SMB服务器

宿主机上必须安装 Samba。通过如下QEMU命令启用这项特性:

$ qemu-system-x86_64 disk_image -net nic -net user,smb=shared_dir_path

shared_dir_path 就是你想要在宿主机和客户机之间共享的目录。

接着,在客户机内,你应该能够通过10.0.2.4访问到名为qemu的共享文件夹。例如在Windows Explorer中前往 \\10.0.2.4\qemu 这个地址。

注意:
  • 如果你像这样多次指定共享选项 -net user,smb=shared_dir_path1 -net user,smb=shared_dir_path2 or -net user,smb=shared_dir_path1,smb=shared_dir_path2 qemu只会共享参数中最后的一个目录。
  • 如果你不能访问共享文件夹且客户机系统为 Windows, 请检查 NetBIOS 协议是否被启用 并确认防火墙没有屏蔽NetBIOS协议的 端口
  • 如果你不能访问共享文件夹且客户机系统为 Windows 10 Enterprise 或 Education 或 Windows Server 2016, 请启用游客访问.

共享多个文件夹并在运行时增删文件夹的一个方法是:共享一个空目录,然后在其中创建指向其余共享目录的符号链接。可以用下面的脚本修改SMB服务器的配置,这个脚本还能使宿主机上不允许执行的文件在客户机内拥有执行权限。

#!/bin/sh
eval $(ps h -C smbd -o pid,args | grep /tmp/qemu-smb | gawk '{print "pid="$1";conf="$6}')
echo "[global]
allow insecure wide links = yes
[qemu]
follow symlinks = yes
wide links = yes
acl allow execute always = yes" >> "$conf"
# in case the change is not detected automatically:
smbcontrol --configfile="$conf" "$pid" reload-config

仅当客户机第一次访问到网络驱动后,才能将该脚本启用,并作用于qemu启动的SMB服务器。共享多文件的另一个方法是在配置文件里加入额外的共享路径,就像下面这样

echo "[myshare]
path=another_path
read only=no
guest ok=yes
force user=username" >> $conf

这个共享文件夹可以在客户机内通过\\10.0.2.4\myshare访问。

使用直通式文件系统和VirtFS

参阅 QEMU 文档.

在宿主机上挂载客户机的分区

在虚拟机运行时,我们仍可以将磁盘镜像内的分区作为loop设备挂载到宿主机,不过一些特殊的磁盘镜像文件并不支持这样的操作,比如说qcow2格式的文件,尽管说在平时能够使用qemu-nbd将qcow2挂载到文件系统上。

警告: 请确保重新运行虚拟机之前,所有挂载的分区都被卸载,否则很可能造成磁盘数据的损坏。

手动指出偏移量

挂载磁盘镜像中分区的一个方法是手动指定挂载的偏移量,你可以使用类似下面的命令完成这个操作:

# mount -o loop,offset=32256 disk_image mountpoint

offset=32256 选项实际上会被传递给 losetup 程序,用于设置一个起始地址为32256字节处的loop设备。接着这个loop设备将会被挂载。 你也可以使用 sizelimit 选项指定这个分区的具体大小,通常不需要指定该选项。 具体的偏移量取决于你的磁盘镜像,你所需要的分区可能并不以32256字节处作为起始地址。运行 fdisk -l disk_image 查看磁盘镜像中的分区,fdisk会显示分区的起始地址和结束地址,地址以512字节的扇区为单位,因此需要将该地址乘以512获得可用于 mount 的字节偏移量。

loop模块自动检测分区

Linux的loop驱动支持loop设备的分区,不过默认情况下它是关闭的,可以通过下面的方法启用:

  • 卸载所有loop设备 (比如说卸载所有挂载的镜像)。
  • 卸下 the loop 内核模块, 以 max_part=15 参数重新加载该模块。 此外,你可以用 max_loop 参数设置loop设备的最大数量。
提示: 你可以在 /etc/modprobe.d 增加一个条目使得每次加载loop模块时带上 max_part=15 参数, 或者把 loop.max_part=15 加入内核命令行, 这得看你的内核是否有编译 loop.ko 模块

将镜像文件设置为loop设备:

# losetup -f -P disk_image

接着,假设创建的loop设备名称为 /dev/loop0 ,相应的 /dev/loop0pX 也会被自动创建, X代表分区的号码,这些分区可以被直接挂载,例如:

# mount /dev/loop0p1 mountpoint

如要使用 udisksctl 挂载磁盘镜像, 参阅 Udisks#Mount loop devices.

使用 kpartx

multipath-tools 包内的 kpartx 可以读取设备的分区表,然后为每个分区创建一个新设备,举个例子:

# kpartx -a disk_image

这条命令将会为你设置loop设备,并在 /dev/mapper/ 下创建必要的分区设备。

挂载qcow2镜像内的分区

我们将使用 qemu-nbd 完成这一功能, 同时它也能让我们使用 NBD (network block device) 协议共享该磁盘镜像。

首先,我们需要加载nbd模块:

# modprobe nbd max_part=16

接着,共享该磁盘并创建设备条目:

# qemu-nbd -c /dev/nbd0 /path/to/image.qcow2

进行分区发现检测:

# partprobe /dev/nbd0

fdisk 可以获取 nbd0 内各分区的相关信息 :

# fdisk -l /dev/nbd0
Disk /dev/nbd0: 25.2 GiB, 27074281472 bytes, 52879456 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa6a4d542

Device      Boot   Start      End  Sectors  Size Id Type
/dev/nbd0p1 *       2048  1026047  1024000  500M  7 HPFS/NTFS/exFAT
/dev/nbd0p2      1026048 52877311 51851264 24.7G  7 HPFS/NTFS/exFAT

接下来可以挂载镜像的任意分区了,比如说我们要挂载分区2:

# mount /dev/nbd0p2 mountpoint

完成任务后,切记卸载镜像文件,然后根据之前的操作一步步还原,即分区并断开与nbd设备的连接:

# umount mountpoint
# qemu-nbd -d /dev/nbd0

将任意分区作为磁盘镜像唯一主分区

有时,你可能想在QEMU中使用物理机的一个系统分区。在虚拟机里使用原始分区可以改善读写性能,因为此时QEMU不需要经过宿主机的文件系统层完成读写操作,该分区同样可以用于在宿主机和客户机之间进行数据共享。

在Arch Linux的默认设置中,代表原始分区的设备文件的所有者为 root , 隶属于 disk组, 如果你希望使用一个非root用户能够对原始分区进行读写,那么请更改对应设备文件的所有者为该用户,将这个用户加入 disk 组中,或者使用 ACL 完成更精细的权限管理。

警告:
  • 不推荐给予虚拟机修改宿主机系统上重要数据的权限,比如说对root分区的修改权限,尽管你拥有这么做的能力。
  • 不要同时挂载一块在宿主机和客户机内都可读写的分区,否则会造成数据的损坏。

在完成上述设置后,你可以将该分区作为一块虚拟磁盘添加到QEMU虚拟机内了。

然而,如果你想使得整个虚拟机都包含在单个分区中,事情就开始变得有些复杂了。在这个场景下,由于系统分区本身是作为文件系统完成格式化的,而非一个带有MBR的分区设备,我们便无法在这个分区上安装bootloader程序,进而也就没有办法启动虚拟机了。要启动这类虚拟机你需要手动指定 kernelinitrd, 或者用 Device-mapper/线性 RAID/Linux网络块设备模拟一块带MBR的磁盘。

手动指定 kernel 和 initrd

QEMU支持直接加载 Linux kernelsinit ramdisks, 从而绕过了类似 GRUB 这类的bootloader程序。它会把包含根文件系统的物理分区作为虚拟机的虚拟磁盘,然后启动虚拟机。通过类似下面的命令可以完成这些操作:

注意: 在示例中, 我们使用的是 宿主机的 镜像,而非客户机的。如果你希望使用客户机的镜像,请将 /dev/sda3 设置为只读(为了保护宿主机上的文件系统), 并且指定 /full/path/to/images , 或者在客户机内使用一些 kexec的技巧, 重新加载客户机的内核(延长启动时)
$ qemu-system-x86_64 -kernel /boot/vmlinuz-linux -initrd /boot/initramfs-linux.img -append root=/dev/sda /dev/sda3

在上面这个例子中, 宿主机上的 /dev/sda3 物理分区被用于客户机的根文件系统, 但是在虚拟机内部它的名字则是 /dev/sda

当然你可以指定任意一个kernel和initrd,而不局限于Arch Linux提供的。

-append 中可以传递许多 kernel parameter, 需要用单引号或双引号将他们包起来,比如:

... -append 'root=/dev/sda1 console=ttyS0'

模拟一块带MBR的磁盘

当我们想要既保持物理分区遵循文件系统的格式,又作为客户机的虚拟磁盘包含客户机内的分区时,我们有一个比较复杂的方法,那就是模拟一块带MBR的磁盘,这样就使得它可以被类似GRUB的引导程序正常启动。

假设你有一块普通的、未挂载的硬盘分区 /dev/hdaN ,分区内有些文件系统, 若想将其制作为QEMU硬盘镜像,需要在该物理分区内动态地预置一个主引导记录(MBR)。更宽泛地说,该分区可以是更大的模拟磁盘中的一部分,尤其是那种模拟了物理磁盘,但仅向虚拟机暴露 /dev/hdaN 名称的块设备。

这类虚拟磁盘可以用带引用的VMDK文件形容,VMDK内的引用指向MBR的副本以及实际的分区。 但QEMU并不支持这类VMDK格式,例如通过如下命令创建的虚拟机:

$ VBoxManage internalcommands createrawvmdk -filename /path/to/file.vmdk -rawdisk /dev/hda

将会被QEMU拒绝,并返回一个错误信息

Unsupported image type 'partitionedDevice'

注意 VBoxManage 会创建两个文件, file.vmdkfile-pt.vmdk, 后一个是MBR的副本, file.vmdk 。 对目标分区外的数据或MBR本身的读操作将会返回0, 而写操作则会被丢弃。

设备映射器

与使用VMDK描述文件类似的方法就是使用设备映射器,设备映射器会在目标分区内添加一份带有有loop设备的MBR文件。在此种情况下,不要求虚拟磁盘与原始分区拥有同样的大小。首先创建一个包含MBR的文件:

$ dd if=/dev/zero of=/path/to/mbr count=2048

在这里,我们通过这个命令,使用现代化的磁盘分区程序创建了一个符合分区对齐策略的文件,总共大小为1MB (2048 * 512 字节)。为了兼容老式的磁盘分区程序(老式的分区程序可能要求其为63个扇区而非2048个扇区), MBR表仅占一块512字节的块,剩余的空间可以用于创建一个BIOS启动分区,或者在创建一个在混合分区方案中需要用到的GUID分区表。接着我们将loop设备附加到MBR文件上:

# losetup --show -f /path/to/mbr
/dev/loop0

在这个例子里,最终生成的设备为 /dev/loop0 ,设备映射器将会把MBR文件与分区结合起来。

# echo "0 2048 linear /dev/loop0 0
2048 `blockdev --getsz /dev/hdaN` linear /dev/hdaN 0" | dmsetup create qemu

生成的 /dev/mapper/qemu 可以被作为QEMU原生镜像使用了,额外的步骤需要你去在虚拟磁盘上创建一个分区表(参阅关于 linear RAID 的部分, 它可以作为创建分区表的一个范例), 以及相应的bootloader代码 (代码会存放在

/path/to/mbr )

下面的步骤中, /dev/hdaN 在虚拟磁盘和物理磁盘上的位置都相同,并且除了MBR(副本)外磁盘的其余部分都是隐藏的:

# dd if=/dev/hda count=1 of=/path/to/mbr
# loop=`losetup --show -f /path/to/mbr`
# start=`blockdev --report /dev/hdaN | tail -1 | awk '{print $5}'`
# size=`blockdev --getsz /dev/hdaN`
# disksize=`blockdev --getsz /dev/hda`
# echo "0 1 linear $loop 0
1 $((start-1)) zero
$start $size linear /dev/hdaN 0
$((start+size)) $((disksize-start-size)) zero" | dmsetup create qemu

通过管道传入作为 dmsetup 标准输入的是一张表,该表的格式与 VBoxManage 创建的VMDK描述文件中的表格式相同, 即我们同样能通过 dmsetup create qemu --table table_file 加载VMDK描述文件中的表。 对于虚拟机来说, 仅 /dev/hdaN 能被访问, 而对其余部分, 除了第一个扇区外,对其他扇区的读操作都只会返回0, 写操作都会被丢弃。 可以用 dmsetup table qemu 显示 /dev/mapper/qemu 的表(用 udevadm info -rq name /sys/dev/block/major:minormajor:minor 转化为类似 /dev/blockdevice 的名字)。若要删除创建的设备,请使用 dmsetup remove qemulosetup -d $loop

设备映射器方法一个可能的应用场景与Windows XP有关,比如说要在Windows XP安装中进行多引导配置,并且有可能要采用混合分区方案(从物理硬件上说,Windows XP可能是唯一使用MBR分区表的系统,更现代化的操作系统能使用GUID分区表)。Windows XP支持硬件配置文件,因此同样的安装可以选择不同的硬件配置完成(在本例中是裸机与虚拟机),而Windows仅需为每个配置文件安装一次新检测到的硬件的驱动即可。注意在这个例子里, 要更新MBR副本中bootloader代码的部分,使得它直接从 /dev/hdaN 加载Windows XP而不是系统内自带的多引导bootloader(比如GRUB)。或者,将包含bootloader安装的启动分区之副本包含于虚拟磁盘中,同样也可起到类似MBR的作用。

线性 RAID

你同样能用 线性模式(需要 linear.ko 内核驱动)的RAID以及一块loop设备完成这件事。

首先创建一个用于容纳MBR的小文件:

$ dd if=/dev/zero of=/path/to/mbr count=32

这样就创建了一个 16 KB(32 * 512字节)的小文件。该文件最好不要太小(尽管MBR只需要一个512字节大小的块),过小的话将会限制软RAID设备的chunk大小,对于性能是有影响的。将loop设备设置到MBR文件上。

# losetup -f /path/to/mbr

因为我们尚无法使用其他loop设备, 因此就先假设产生的设备名为 /dev/loop0。下一步将创建合并式MBR + 使用软件RAID的磁盘镜像 /dev/hdaN

# modprobe linear
# mdadm --build --verbose /dev/md0 --chunk=16 --level=linear --raid-devices=2 /dev/loop0 /dev/hdaN

这一步产生的 /dev/md0 设备将在之后作为一个QEMU原始镜像(别忘了给模拟器相应的访问权限)。最后一步(也是比较取巧的一步)是设置磁盘配置,使得MBR内主分区的起始地址与 /dev/md0 中某一块 /dev/hdaN 的起始地址(在这个例子中是 16 * 512 = 16384字节的偏移处)。请在宿主机上用 fdisk 完成该操作,别在虚拟机里面这么做,因为默认的QEMU内的磁盘检测程序常给出无法进行千位舍入的偏移量(比如31.5 KB),软件RAID无法处理这样的情况。因此请在主机上进行下面的操作:

# fdisk /dev/md0

按下 X 进入专家选单, 设置每个磁道上 's'ectors 的数目,使得一个柱面的大小与MBR文件中相符。对于双磁头柱面,且每个扇区为512字节的情况, 每个磁道上的扇区数当为16, 因此我们通过计算 2x16x512=16k 得到柱面的大小。

按下 R 返回主界面。

按下 P 并检查柱面大小为16K。

现在,来创建与 /dev/hdaN 对应的主分区。 设置它的起始地址为柱面2, 结束地址在磁盘的末端(注意现在的柱面编号已经与进入fdisk时的编号不同了)。

最后,把结果写入:完成~ 现在你拥有了一块可以直接从宿主机进行挂载的分区,该分区同样也是QEMU磁盘镜像的一部分。

$ qemu-system-x86_64 -hdc /dev/md0 [...]

现在,若原始的 /dev/hdaN 分区中包含必要的一些工具,你就可以安全地用QEMU在磁盘镜像上设置任何一个bootloader。

网络块设备

除了上面描述的各种方法外,你也可以用 nbd-server (在 nbd 包中)为QEMU创建一个MBR封装器。

假设你已经用上面所说的方法创建了一个方法创建了一个MBR封装文件,将其重命名为 wrapper.img.0。然后在同一个目录下创建一个名为 wrapper.img.1的符号链接,指向你选择的分区。然后,还是在这个文件夹下,创建如下的脚本:

#!/bin/sh
dir="$(realpath "$(dirname "$0")")"
cat >wrapper.conf <<EOF
[generic]
allowlist = true
listenaddr = 127.713705
port = 10809

[wrap]
exportname = $dir/wrapper.img
multifile = true
EOF

nbd-server \
    -C wrapper.conf \
    -p wrapper.pid \
    "$@"

.0 and .1 这两个后缀名是关键,名字的其他其他部分都可以更改。在运行上面的脚本(有可能需要以root身份运行,保证nbd-server能够去访问该分区)后,你可以用如下命令启动QEMU:

qemu-system-x86_64 -drive file=nbd:127.713705:10809:exportname=wrap [...]

网络

警告: 本部分在翻译过程中,英文wiki产生style告警,这代表日后本节的内容将会有相应调整,暂停本节的翻译: 网络拓扑 (#仅主机 网络, #内部网络 节中以及其他节中引用的部分) 不应当根据不同的虚拟网络接口实现进行划分和描述, 例如#用户模式, #Tap 网络, #通过 VDE2 配置网络

采用TAP设备和网桥的虚拟网络的性能应该会比使用用户模式网络或VDE要好,原因在于TAP设备和网桥是在内核中实现的。

此外,虚拟网络的性能可以通过将网络设备直接注册到虚拟机中改善,这比默认情况下模拟e1000 NIC的性能表现要更好,参阅 #安装 virtio 驱动 获得更多相关信息。

关于链路层地址的限制

若在QEMU启动中指定了 -net nic 参数,QEMU将会为虚拟机注册一块虚拟网卡,其链路层地址为 52:54:00:12:34:56 。然而,当在多台虚拟机之间搭建桥接网络时,每台虚拟机在tap设备的虚拟机端都需要拥有一个独一无二的链路层地址 (MAC),否则网桥会因为收到多个不同源却拥有相同MAC地址的数据包而无法正常工作。即使你为多个tap设备配置了不同的MAC地址也依旧会出现这个问题,因为当数据包通过tap设备时,tap设备并不会改写包内的链路层地址。

因此请确保每个虚拟机拥有自己独一无二的网卡地址, 并且它们都以 52:54: 开头。 可以通过如下命令手动设置虚拟机的MAC地址, 下面的'X'可以替换成任何16进制字符:

$ qemu-system-x86_64 -net nic,macaddr=52:54:XX:XX:XX:XX -net vde disk_image

生成不同的链路层地址有很多方法:

  • 手动为每个NIC设置独一无二的链路层地址,这么做的优点在于每次启动虚拟机时,DHCP服务器都会将相同的IP地址分配给对应的MAC地址,但是这个方法在需要大量虚拟机的情况下就不适用了。
  • 在每次启动虚拟机时随机生成链路层地址,实际情况下地址冲突的概率可视为0, 不过此方法的缺点在于DHCP服务器每次都会分配一个不同的IP地址。你可以用如下的这些脚本生成随机的链路层地址,并用于 macaddr 参数中。
printf -v macaddr "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
qemu-system-x86_64 -net nic,macaddr="$macaddr" -net vde disk_image
  • 使用这个脚本 qemu-mac-hasher.py 可以根据虚拟机的名字进行Hash得到一个链路层地址。 只要每台虚拟机的名字是独一无二的, 这个方法就结合了上述两种方法的优点: 每次运行脚本生成的链路层地址都是相同的, 且冲突概率在实际应用中仍可视为0.
qemu-mac-hasher.py
#!/usr/bin/env python

import sys
import zlib

if len(sys.argv) != 2:
    print("usage: %s <VM Name>" % sys.argv[0])
    sys.exit(1)

crc = zlib.crc32(sys.argv[1].encode("utf-8")) & 0xffffffff
crc = str(hex(crc))[2:]
print("52:54:%s%s:%s%s:%s%s:%s%s" % tuple(crc))

如果要在脚本调用这个方法,你可以参照下面的例子:

vm_name="VM Name"
qemu-system-x86_64 -name "$vm_name" -net nic,macaddr=$(qemu-mac-hasher.py "$vm_name") -net vde disk_image

用户模式

默认情况下,没有任何-netdev参数,QEMU将使用带有内置DHCP服务器的用户模式网络。当您的虚拟机运行其DHCP客户端时,将为其分配IP地址,它们将能够通过QEMU伪装的IP来访问物理主机的网络。

警告: 仅适用于TCP和UDP协议,因此ICMP协议(包括ping)将不起作用。 请勿使用ping测试网络连接。 要在来宾中执行ping操作,请参阅Sysctl#Allow unprivileged users to create IPPROTO_ICMP sockets

如果主机已连接Internet,则此默认配置可以使您的虚拟机轻松访问Internet。但是如果您同时启动多个虚拟机,则虚拟机将无法在外部网络上直接看到,虚拟机也将无法相互通信。

QEMU的用户模式网络可以提供更多功能,例如内置TFTP或SMB服务器,将主机端口重定向到虚拟机(例如,允许SSH连接到虚拟机)或将虚拟机连接到VLAN,以便它们可以彼此通信。 有关更多详细信息,请参见-net user标志上的QEMU文档。

但是,用户模式网络在效用和性能上都有局限性。更高级的网络配置需要使用TAP设备或其他方法。

注意: 如果主机系统使用systemd-networkd,请确保按照systemd-networkd#Required services and setup中的描述对/etc/resolv.conf文件进行符号链接,否则虚拟机系统中的DNS查找将无法进行。

Tap 网络

Tap devices是一个Linux内核特性,允许您创建作为真实网络接口的虚拟网络接口。发送到tap接口的包将被传递到一个用户空间程序(如QEMU),该程序将自己绑定到该接口。

QEMU可以为虚拟机使用tap网络,因此发送到tap接口的包将被发送到虚拟机,并显示为来自虚拟机中的网络接口(通常是以太网接口)。相反,虚拟机通过其网络接口发送的所有内容都将出现在tap接口上。

Linux桥接驱动程序支持Tap设备,因此可以将Tap设备彼此桥接在一起,也可以连接其他主机接口,如eth0。如果您希望您的虚拟机能够相互通信,或者希望LAN上的其他机器能够与虚拟机通信,那么这是非常理想的方案。

警告: 如果您将tap设备和一些主机接口桥接在一起,例如eth0,您的虚拟机将直接出现在外部网络上,这将使它们遭受攻击的可能。根据您的虚拟机可以访问的资源,您可能需要采取所有precautions来保护您的虚拟机。如果风险太大,虚拟机没有资源或您设置多个虚拟机,一个更好的解决方案可能是使用host-only networking建立NAT。在这种情况下,您只需要在主机上安装一个防火墙,而不是为每个虚拟机安装多个防火墙。

正如在用户模式网络部分中指出的,tap设备提供比用户模式具有更高的网络性能。如果虚拟机中的操作系统支持virtio网络驱动程序,那么网络性能也会显著提高。假设使用tap0设备,virtio驱动程序在客户端上使用,并且没有使用脚本来帮助启动/停止网络,使用下面的qemu命令:

-net nic,model=virtio -net tap,ifname=tap0,script=no,downscript=no

但是,如果已经使用带有virtio网络驱动程序的Tap设备,则甚至可以通过启用vhost来提高网络性能,例如:

-net nic,model=virtio -net tap,ifname=tap0,script=no,downscript=no,vhost=on

详情请参考:http://www.linux-kvm.com/content/how-maximize-virtio-net-performance-vhost-net

仅主机 网络

如果为网桥提供了IP地址,并且使能发往该网桥的流量允许,但没有实际接口(例如eth0)连接到网桥,则虚拟机与虚拟机间,虚拟机与主机间能够相互通信。但是,如果您没有在物理主机上设置IP掩蔽,则他们将无法与外部网络进行通信。 此配置被其他虚拟化软件(例如VirtualBox)称为“仅主机网络模式”。

提示:
  • 如果你想设置IP掩蔽,例如虚拟机的NAT,请查看Internet sharing#Enable NAT页面。
  • 您也许想在网桥接口上运行一个DHCP服务器来服务虚拟网络。例如,使用172.20.0.1/16子网,dnsmasq作为DHCP服务器:
# ip addr add 172.20.0.1/16 dev br0
# ip link set br0 up
# dnsmasq --interface=br0 --bind-interfaces --dhcp-range=172.20.0.2,172.20.255.254

内部网络

如果您不为网桥提供IP地址并在iptables添加INPUT规则链,将所有流向网桥中的数据丢弃,则虚拟机将能够彼此通信,但无法与物理主机或外部网络通信。此配置被其他虚拟化软件(例如VirtualBox)称为“内部网络”。您将需要为虚拟机分配静态IP地址,或在其中一个虚拟机上运行DHCP服务器。

在默认情况下,iptables将丢弃桥接网络中的数据包。您可能需要使用这样的iptables规则来允许桥接网络中的数据包:

# iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT

使用 qemu-bridge-helper 桥接网络

注意: 这个方法从QEMU 1.1开始就可用了,参见http://wiki.qemu.org/Features/HelperNetworking。

这种方法不需要启动脚本,并且很容易适应多个tap和多个桥。它使用/usr/lib/qemu/qemu-bridge-helper,允许在现有桥上创建tap设备。

提示: 参见 Network bridge 获取创建网桥的信息.

首先,创建一个配置文件,包含QEMU使用的所有网桥的名称:

/etc/qemu/bridge.conf
allow bridge0
allow bridge1
...

现在启动虚拟机:

$ qemu-system-i386 -net nic -net bridge,br=bridge0 [...]

在多个TAP设备的情况下,最基本的用法是要为所有NIC指定VLAN:

$ qemu-system-i386 -net nic -net bridge,br=bridge0 -net nic,vlan=1 -net bridge,vlan=1,br=bridge1 [...]

手工创建网桥

提示: 自QEMU 1.1起,network bridge helper可以为您设置tun/tap,而无需其他脚本。 请参阅#使用 qemu-bridge-helper 桥接网络

下面介绍如何将虚拟机连接到主机接口,如eth0,这可能是最常见的配置。这种配置使虚拟机看起来直接位于外部网络,与物理主机位于同一以太网段。

我们将用桥适配器替换普通的以太网适配器,然后将普通的以太网适配器绑定到它。

  • 启用IPv4转发:
# sysctl net.ipv4.ip_forward=1

要使更改永久生效,请将/etc/sysctl.d/99-sysctl.conf中的net.ipv4.ip_forward = 0更改为net.ipv4.ip_forward = 1

  • 加载tun模块,并将其配置为在引导时加载。详见Kernel modules
  • 现在创建桥。有关详细信息,请参见Bridge with netctl。请记住网桥的命名,如 br0,或将以下脚本更改为网桥的名称。
  • 创建QEMU用于打开tap适配器的脚本,该脚本具有root:kvm 750权限:
/etc/qemu-ifup
#!/bin/sh

echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridged mode..."
sudo /usr/bin/ip link set $1 up promisc on
echo "Adding $1 to br0..."
sudo /usr/bin/brctl addif br0 $1
sleep 2
  • 创建QEMU用于在/etc/qemu-ifdown中关闭tap适配器的脚本,该脚本具有root:kvm 750权限:
/etc/qemu-ifdown
#!/bin/sh

echo "Executing /etc/qemu-ifdown"
sudo /usr/bin/ip link set $1 down
sudo /usr/bin/brctl delif br0 $1
sudo /usr/bin/ip link delete dev $1
  • 使用visudo将以下内容添加到sudoers文件中:
Cmnd_Alias      QEMU=/usr/bin/ip,/usr/bin/modprobe,/usr/bin/brctl
%kvm     ALL=NOPASSWD: QEMU
  • 您可以使用以下run-qemu脚本启动QEMU:
run-qemu
#!/bin/bash
USERID=$(whoami)

# Get name of newly created TAP device; see https://bbs.archlinux.org/viewtopic.php?pid=1285079#p1285079
precreationg=$(/usr/bin/ip tuntap list | /usr/bin/cut -d: -f1 | /usr/bin/sort)
sudo /usr/bin/ip tuntap add user $USERID mode tap
postcreation=$(/usr/bin/ip tuntap list | /usr/bin/cut -d: -f1 | /usr/bin/sort)
IFACE=$(comm -13 <(echo "$precreationg") <(echo "$postcreation"))

# This line creates a random MAC address. The downside is the DHCP server will assign a different IP address each time
printf -v macaddr "52:54:%02x:%02x:%02x:%02x" $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff )) $(( $RANDOM & 0xff)) $(( $RANDOM & 0xff ))
# Instead, uncomment and edit this line to set a static MAC address. The benefit is that the DHCP server will assign the same IP address.
# macaddr='52:54:be:36:42:a9'

qemu-system-i386 -net nic,macaddr=$macaddr -net tap,ifname="$IFACE" $*

sudo ip link set dev $IFACE down &> /dev/null
sudo ip tuntap del $IFACE mode tap &> /dev/null

然后,要启动VM,可以这样做:

$ run-qemu -hda myvm.img -m 512 -vga std
  • 出于性能和安全原因,建议禁用网桥上的防火墙[1]
/etc/sysctl.d/10-disable-firewall-on-bridge.conf
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

运行sysctl -p /etc/sysctl.d/10-disable-firewall-on-bridge.conf立即应用更改。

参见libvirt wikiFedora bug 512206。如果在引导过程中sysctl发现关于不存在文件的错误,请在引导时加载bridge模块。参见Kernel module#Automatic module loading with systemd

或者,您可以配置iptables,通过添加类似这样的规则,允许所有流量通过桥进行转发:

-I FORWARD -m physdev --physdev-is-bridged -j ACCEPT

物理设备和Tap设备之间通过iptables进行网络共享

Merge-arrows-2.pngThis article or section is a candidate for merging with Internet_sharing.Merge-arrows-2.png

Notes: Duplication, not specific to QEMU. (Discuss in Talk:QEMU (简体中文))

桥接网络能在有线接口(例如eth0)之间工作,并且很容易设置。但是,如果主机通过无线设备连接到网络,则无法进行桥接。

参见 Network bridge#Wireless interface on a bridge.

解决这个问题的一种方法是,给tap设备设置一个静态IP,使linux自动处理它的路由,然后通过iptables规则转发tap接口和连接到网络的设备之间的通信。

参见 Internet sharing.

在那里你可以找到在设备之间共享网络所需要的东西,包括tap和tun。下面将进一步介绍所需的一些主机配置。如上所述,需要为静态IP配置客户机,使用分配给tap接口的IP作为网关。需要注意的是,如果客户机上的DNS服务器在从一个连接到网络的主机设备切换到另一个时发生了更改,那么它们可能需要手动编辑。

要在每次启动时允许IP转发,需要在/etc/sysctl.d中,向sysctl配置文件添加以下信息:

net.ipv4.ip_forward = 1
net.ipv6.conf.default.forwarding = 1
net.ipv6.conf.all.forwarding = 1

iptables规则如下:

# Forwarding from/to outside
iptables -A FORWARD -i ${INT} -o ${EXT_0} -j ACCEPT
iptables -A FORWARD -i ${INT} -o ${EXT_1} -j ACCEPT
iptables -A FORWARD -i ${INT} -o ${EXT_2} -j ACCEPT
iptables -A FORWARD -i ${EXT_0} -o ${INT} -j ACCEPT
iptables -A FORWARD -i ${EXT_1} -o ${INT} -j ACCEPT
iptables -A FORWARD -i ${EXT_2} -o ${INT} -j ACCEPT
# NAT/Masquerade (network address translation)
iptables -t nat -A POSTROUTING -o ${EXT_0} -j MASQUERADE
iptables -t nat -A POSTROUTING -o ${EXT_1} -j MASQUERADE
iptables -t nat -A POSTROUTING -o ${EXT_2} -j MASQUERADE

假设有3个设备连接到一个内部设备的网络共享流量,例如:

INT=tap0
EXT_0=eth0
EXT_1=wlan0
EXT_2=tun0

前面显示了一个转发,允许与tap设备共享有线和无线连接。

所示的转发规则是无状态的,用于纯转发。可以考虑限制特定的流量,设置防火墙来保护来宾和其他人。然而,这些会降低网络性能,而简单的网桥不包括这些。

好处:不管连接是有线还是无线,如果使用tun设备通过VPN连接到远程站点,假设为该连接打开的tun设备是tun0,并且应用了先前的iptables规则,那么远程连接也将与客户机共享。这避免了客户也需要打开VPN连接。同样,由于来宾网络需要是静态的,因此如果以这种方式远程连接主机,很可能需要编辑来宾网络上的DNS服务器。

通过 VDE2 配置网络

何为VDE?

VDE全称为Virtual Distributed Ethernet,作为uml_switch的一个扩展,是一个用于管理虚拟网络的工具包

其基本的思想是创建一个虚拟的开关,就如插座那样,允许虚拟机和物理机通过"插入"连接彼此。下面的配置非常简单,然而,VDE的功能远比展示的更强大,其能够接入虚拟开关,在不同的主机上运行它们并监听开关上的通信。恳请您阅读该项目的文档获取更多信息。

本方法的优点在于无需sudo特权,普通用户一般没有运行modprobe的权限。

基础操作

可以通过安装official repositories中的vde2包获得VDE支持。

在此处配置中,我们使用tun/tap在主机上创建一块虚拟网卡,用如下命令加载tun模块(参阅Kernel modules获取更多信息)

# modprobe tun

现在来创建一个虚拟开关:

# vde_switch -tap tap0 -daemon -mod 660 -group users

这条命令完成了开关创建,tap0创建并将其"插入",然后允许users组内的用户使用之。

该网卡已插入,然而还没有进行配置。需要用下面的命令进行配置:

# ip addr add 192.168.100.254/24 dev tap0

现在,只需要以普通用户的身份,指定-net参数启动KVM即可:

$ qemu-system-i386 -net nic -net vde -hda [...]

接下来只需同配置物理机网络一般,对客户机网络进行配置就行。

提示: 你可能想在tap设备上设置NAT,实现在虚拟机内访问互联网,那就参考Internet sharing#Enable NAT获取更多的帮助吧

启动脚本

启动VDE的一个示例脚本:

/etc/systemd/scripts/qemu-network-env
#!/bin/sh
# QEMU/VDE network environment preparation script

# The IP configuration for the tap device that will be used for
# the virtual machine network:

TAP_DEV=tap0
TAP_IP=192.168.100.254
TAP_MASK=24
TAP_NETWORK=192.168.100.0

# Host interface
NIC=eth0

case "$1" in
  start)
        echo -n "Starting VDE network for QEMU: "

        # If you want tun kernel module to be loaded by script uncomment here
	#modprobe tun 2>/dev/null
	## Wait for the module to be loaded
 	#while ! lsmod | grep -q "^tun"; do echo "Waiting for tun device"; sleep 1; done

        # Start tap switch
        vde_switch -tap "$TAP_DEV" -daemon -mod 660 -group users

        # Bring tap interface up
        ip address add "$TAP_IP"/"$TAP_MASK" dev "$TAP_DEV"
        ip link set "$TAP_DEV" up

        # Start IP Forwarding
        echo "1" > /proc/sys/net/ipv4/ip_forward
        iptables -t nat -A POSTROUTING -s "$TAP_NETWORK"/"$TAP_MASK" -o "$NIC" -j MASQUERADE
        ;;
  stop)
        echo -n "Stopping VDE network for QEMU: "
        # Delete the NAT rules
        iptables -t nat -D POSTROUTING "$TAP_NETWORK"/"$TAP_MASK" -o "$NIC" -j MASQUERADE

        # Bring tap interface down
        ip link set "$TAP_DEV" down

        # Kill VDE switch
        pgrep -f vde_switch | xargs kill -TERM
        ;;
  restart|reload)
        $0 stop
        sleep 1
        $0 start
        ;;
  *)
        echo "Usage: $0 {start|stop|restart|reload}"
        exit 1
esac
exit 0

使用上面的脚本作为systemd服务:

/etc/systemd/system/qemu-network-env.service
[Unit]
Description=Manage VDE Switch

[Service]
Type=oneshot
ExecStart=/etc/systemd/scripts/qemu-network-env start
ExecStop=/etc/systemd/scripts/qemu-network-env stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

修改qemu-network-env权限至可执行

# chmod u+x /etc/systemd/scripts/qemu-network-env

可以像其他服务一样,start qemu-network-env.service服务。

备用方法

如果上面的方法不起作用,或者说不想折腾内核配置、TUN、dnsmasq和iptables,可以采用下面的方法,也可以获得同样的效果,

# vde_switch -daemon -mod 660 -group users
# slirpvde --dhcp --daemon

然后,启动VM并连接至主机网络中:

$ qemu-system-i386 -net nic,macaddr=52:54:00:00:EE:03 -net vde disk_image

VDE2 网桥

根据quickhowto: qemu networking using vde, tun/tap, and bridge所描述的. 任何连接到vde上的虚拟机都会暴露给外部。举个例子,每台虚拟机都能直接从ADSL路由器那收到DHCP的配置信息。

基础操作

要记得,你需要有tun模块并安装了bridge-utils包。

创建一个vde2/tap设备:

# vde_switch -tap tap0 -daemon -mod 660 -group users
# ip link set tap0 up

创建一个网桥:

# brctl addbr br0

添加设备:

# brctl addif br0 eth0
# brctl addif br0 tap0

配置网桥接口:

# dhcpcd br0

启动脚本

所有设备应该都设置好了,且只有网桥拥有IP地址。对于网桥上的物理设备(比如eth0),可以通过netctl使用一个自定义的配置文件完成上面所述设置:

/etc/netctl/ethernet-noip
Description='A more versatile static Ethernet connection'
Interface=eth0
Connection=ethernet
IP=no

下面的systemd服务用于为users用户组内的成员创建和启动VDE2 tap网卡。

/etc/systemd/system/[email protected]
[Unit]
Description=Network Connectivity for %i
Wants=network.target
Before=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/vde_switch -tap %i -daemon -mod 660 -group users
ExecStart=/usr/bin/ip link set dev %i up
ExecStop=/usr/bin/ip addr flush dev %i
ExecStop=/usr/bin/ip link set dev %i down

[Install]
WantedBy=multi-user.target

最后,通过netctl创建网桥

简化配置参数

如果你经常需要以不同的网络配置选项运行QEMU,就会发现时常得输入大量的-netdev-device选项组合,这些是大量重复性的劳动。可以用-nic选项将二者结合,就如下面这样,底下这些参数:

-netdev tap,id=network0,ifname=tap0,script=no,downscript=no,vhost=on -device virtio-net-pci,netdev=network0

可简化为:

-nic tap,script=no,downscript=no,vhost=on,model=virtio-net-pci

要注意的是缺失了网络ID,因此将会以model=创建这些设备。{ic|-nic}}命令的前半部分参数正是-netdev的参数,而后半部分参数(model=之后的部分)则与设备有关,原本设备所提供的参数同样可以在此使用(例如,可以指定smb=)。若要完全禁用网络,可以用-nic none

参阅 QEMU networking文档了解更多的相关参数。

图形

QEMU 可以使用一下几个图形输出:std, cirrus, vmware, qxl, xenfs 和 vnc。

使用 vnc 选项,你可以单独运行客户机,并且通过 VNC 连接。其他选项是使用std, vmware, cirrus:

std

使用 -vga std 你可以得到最高 2560 x 1600 像素的分辨率。从 QEMU 2.2 开始是默认选项。

qxl

QXL是一个支持2D的并行虚拟化图形驱动。需要在客户机中安装驱动并在启动QEMU时设置-vga qxl选项。你可能也会想使用#SPICE优化QXL的图形表现。

在Linux客户机中,需要加载qxlbochs_drm这两个内核模块,以获得一个比较好的效果。

QXL设备的默认VGA内存大小为16M,这样的内存大小最高支持QHD (2560x1440)的分辨率,如果想要一个更高的分辨率,请增加vga_memmb

vmware

尽管Bug有点多,但相比于std和cirrus它的表现会更好。对于Arch Linux客户机来说可以安装xf86-video-vmwarexf86-input-vmmouse获取VMware驱动。

virtio

virtio-vga / virtio-gpu 是一个基于virgl的3D并行虚拟化图形驱动。目前依旧处于开发中,仅支持最近的(>= 4.4)的Linux客户机,且需要以gallium-drivers=virgl选项编译mesa (>=11.2)。

若要在客户机上启用3D加速,那么需要用-vga virtio选项选择此vga,并用-display sdl,gl=on-display gtk,gl=on在显示设备上启用opengl上下文,这两个选项分别适用于sdl输出和gtk输出。如果配置成功了,那么在客户机的kernel log里可以看到:

# dmesg | grep drm 
[drm] pci: virtio-vga detected
[drm] virgl 3d acceleration enabled

cirrus

cirrus是2.2之前默认的图形选项,不应当在现代操作系统中使用它。

none

这就像一台完全没有VGA卡的PC,无法通过-vnc访问它。另外,这种情况与使用-nographic选项不同,-nographic会让QEMU模拟VGA卡,只是关闭了SDL输出。

SPICE

SPICE project旨在为用户提供一种完全开源的方式,无缝地对虚拟机进行远程访问。

Enabling SPICE support on the host

下面是一个启用SPICE作为远程桌面协议的例子,并支持复制和粘贴操作:

$ qemu-system-x86_64 -vga qxl -device virtio-serial-pci -spice port=5930,disable-ticketing -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 -chardev spicevmc,id=spicechannel0,name=vdagent

这些参数的含义如下:

  1. -device virtio-serial-pci 添加一块virtio-serial设备
  2. -spice port=5930,disable-ticketing 在TCP 5930端口上进行spice channel的监听,允许客户端不经验证即可连接。
提示: 使用Unix sockets而非TCP端口不会涉及宿主机系统的网络栈,也不意味着可以对数据包进行封装和解封以使用网络和其它相关的协议。这些socket仅通过硬盘上的inode进行表示,这么做是出于性能上的考虑。使用Unix sockets可以使用-spice unix,addr=/tmp/vm_spice.socket,disable-ticketing代替上面的参数。
  1. -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 在virtio-serial设备上为spice vdagent打开一个端口。
  2. -chardev spicevmc,id=spicechannel0,name=vdagent 为该端口添加一块spicevmc字符设备。virtserialportchardev=选项需要与chardevid=相符,在本例中是spicechannel0。将端口名字设置为com.redhat.spice.0也很重要,因为它就是vdagent在客户机上所搜索的命名空间。最后,指定name=vdagent选项使得spice知道该chennel的服务对象。

通过 SPICE 客户端连接到客户机

若要连接到客户机上必须要有一个SPICE客户端。在Arch中,有如下可用的客户端:

virt-viewer — 协议开发者所推荐的SPICE客户端,其是virt-manager project的子集。

https://virt-manager.org/ || virt-viewer

spice-gtk — SPICE GTK客户端,SPICE project的一个子集,作为小部件嵌入其它应用中。

https://www.spice-space.org/ || spice-gtk

若需要能在智能手机上运行的客户端,或者其他平台的客户端,参照spice-space download中的Other clients章节。

手动开启SPICE客户端

连接到一个监听在Unix socket /tmp/vm_spice.socket上的客户机的方法是用$ remote-viewer spice+unix:///tmp/vm_spice.socket or $ spicy --uri="spice+unix:///tmp/vm_spice.socket"命令手动运行SPICE客户端,使用的客户端取决于你的喜好。SPICE模式下的QEMU就如一个远程桌面服务器,可能使用-daemonize参数以daemon模式运行QEMU会更方便一点。

提示: 可以用下面的命令,通过SSH隧道连接到客户机:
$ ssh -fL 5999:localhost:5930 my.domain.org sleep 10; spicy -h 127.0.0.1 -p 5999
。这个例子中spicy连接到了本地的5999端口,该端口通过SSH转发至my.domain.org上的SPICE server端口5930。要注意-f选项让ssh在后台执行了sleep 10命令。这种情况下,在客户端存活时ssh session保持运行,在客户端结束运行时将自动关闭ssh session。

QEMU运行时启动SPICE

QEMU可以自动地开启一个SPICE客户端并创建一个合适的socket。如果通过-display spice-app参数将显示设置为SPICE,那么将会使用系统默认的SPICE客户端作为viewer,默认的客户端取决于你的mimeapps.list文件。

在客户机上开启SPICE的支持

对于Arch Linux客户机,如要支持多屏和共享剪贴板,需要安装以下的包:

  • spice-vdagent: Spice的xorg客户端代理,使得用户能够在客户端和X-session之间进行复制和粘贴等。
  • xf86-video-qxl: Xorg X11 qxl视频驱动

对于其它的操作系统客户机, 参照spice-space downloadGuest章节。

开启SPICE口令验证

若要启用SPICE的口令验证,需要从-spice的参数中移除disable-ticketing,改为password=yourpassword,例如:

$ qemu-system-x86_64 -vga qxl -spice port=5900,password=yourpassword -device virtio-serial-pci -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 -chardev spicevmc,id=spicechannel0,name=vdagent

现在SPICE客户端在连接SPICE服务器的时候应该就会进行口令询问了。

用TLS对与SPICE的通信进行加密

同样可以为客户端与SPICE服务器的通信配置TLS加密。首先,需要有一个包含如下文件的目录(文件名必须与下面保持一致):

  • ca-cert.pem: CA主证书。
  • server-cert.pem: ca-cert.pem签名后的服务器证书。
  • server-key.pem: 服务器的私钥。

Spice User Manual还展示了使用服务器自己生成的CA生成自签名证书的例子。

这些完成后,你可以用前文描述的-spice的参数在QEMU启动时自动开启SPICE:-spice tls-port=5901,password=yourpassword,x509-dir=/path/to/pki_certs/path/to/pki_certs代表包含那三个文件的目录。

现在可以用virt-viewer连接到服务器了:

$ remote-viewer spice://hostname?tls-port=5901 --spice-ca-file=/path/to/ca-cert.pem --spice-host-subject="C=XX,L=city,O=organization,CN=hostname" --spice-secure-channels=all

要记住的是,--spice-host-subject参数需要根据你的server-cert.pem中的子条目进行设置。此外还需要将ca-cert.pem复制到每个客户端上用于验证服务器证书。

提示: 使用下面的命令,就可以获得服务器证书中的子条目格式,可用于--spice-host-subject参数中(以逗号进行分隔):
$ openssl x509 -noout -subject -in server-cert.pem | cut -d' ' -f2- | sed 's/\///' | sed 's/\//,/g'

等效的 spice-gtk 命令为:

$ spicy -h hostname -s 5901 --spice-ca-file=ca-cert.pem --spice-host-subject="C=XX,L=city,O=organization,CN=hostname" --spice-secure-channels=all

VNC

可以用-vnc :X选项将QEMU的VGA输出重定向至VNC会话中。将X替换为输出目标的编号(0代表之后监听在5900,1代表监听在5901...)。

$ qemu-system-x86_64 -vnc :0

#开机时启动QEMU虚拟机这一小节中同样提供了一个VNC的示例。

警告: 默认的VNC服务器没有使用任何验证手段,用户可以从任何主机上连接到VNC。

基本的口令验证

可以通过使用password选项很容易地设置访问口令。必须在QEMU Monitor中指定口令,仅当用户提供口令时才有可能连接到VNC。

$ qemu-system-x86_64 -vnc :0,password -monitor stdio

在QEMU Monitor中设置口令需使用change vnc password命令,然后指定一个口令。

底下的命令将在启动VNC时直接为其设置口令:

$ printf "change vnc password\n%s\n" MYPASSWORD | qemu-system-x86_64 -vnc :0,password -monitor stdio
注意: 口令被限制在8个字符内,可以用暴力破解的方式猜到口令。因此在公网上推荐使用更细致的保护措施。

音频

宿主机

-audiodev标识用于设定后端音频驱动及其相关选项。在qemu(1)man页面中详细列出了可用的后端音频驱动以及可选的设置项。

最简单的情况下,你需要选择一个驱动并设置一个id。

-audiodev pa,id=snd0

客户机

使用音频设备

Intel HD Audio

模拟Intel HD Audio需要添加控制器和编解码器设备。可以用如下命令列出可用的Intel HDA Audio设备:

$ qemu-system-x86_64 -device help | grep hda

添加音频控制器:

-device ich9-intel-hda

添加音频编解码器并将其映射到宿主机的音频后端id上。

-device hda-output,audiodev=snd0
Intel 82801AA AC97

模拟AC97需要添加声卡设备并将其映射到宿主机的一个音频后端id上。

-device AC97,audiodev=snd0

无音频设备

通过如下命令获取支持模拟的音频驱动列表:

$ qemu-system-x86_64 -soundhw help

比如,要在客户机上模拟hda驱动,需要使用-device intel-hda -device hda-duplex选项启动QEMU。

注意: 客户机的显卡模拟驱动可能也会导致客户机中的音频质量出现问题,需要一个个进行排查。使用qemu-system-x86_64 -h | grep vga列出可用的选项

安装 virtio 驱动

QEMU为用户提供并行虚拟化块设备和网络设备的能力,其是借助virtio驱动实现的,拥有更好的性能表现以及更低的开销。

  • virtio块设备需要使用-drive指定一个disk image的参数,且需要带上if=virtio参数:
$ qemu-system-x86_64 -boot order=c -drive file=disk_image,if=virtio
  • 网络配置也是类似的:
$ qemu-system-x86_64 -nic user,model=virtio-net-pci
注意: 仅有当客户机有virtio设备对应的驱动时该方法才能起效,Linux是有这方面支持的,而在Arch Linux中已经包含所需的驱动了,不过无法保证这些驱动能够兼容其他操作系统。

(Arch) Linux 客户机

要在Arch Linux客户机中使用virtio设备,必须在客户端加载以下模块:virtio_pcivirtio_pcivirtio_blkvirtio_netvirtio_ring。对于32位系统来说,不需要特定的“virtio”模块。

如果希望从virtio磁盘引导,ramdisk必须包含必要的模块。默认情况下,这是由mkinitcpioautodetect钩子处理的。否则要在/etc/mkinitcpio.conf中使用MODULES数组包含必要的模块并重新构建ramdisk。

/etc/mkinitcpio.conf
MODULES="virtio virtio_blk virtio_pci virtio_net"

Virtio磁盘公认的前缀为“v”(例如:“v”da, “v”db,等等)。因此,当从virtio磁盘启动时,需要在/etc/fstab/boot/grub/grub.cfg中进行更改。。

提示: 当在/etc/fstab和bootloader中通过UUID引用磁盘时,不需要执行任何操作。

关于使用KVM进行半虚拟化的更多信息,可以参考Boot_from_virtio_block_device

您可以安装qemu-guest-agent来实现对QMP命令的支持,从而增强管理程序的管理能力。安装完成后,您需要启动qemu-guest-agent.service

Windows 客户机

块设备驱动

安装一个新的Windows

Windows没有自带virtio驱动,因此需要在安装时加载该驱动。通常有两种方法:通过Floppy Disk或通过ISO文件加载。这两种镜像文件都可以从Fedora 仓库下载。

通过floppy加载的方法可能比较困难,因为你需要在QEMU一启动的时候就按下F6(Windows主机上需要按下Shift-F6),但是由于我们是通过VNC console连接到Windows的,这个过程会消耗一些时间,导致我们难以办到这一点。不过你可以试试在boot序列中加入delay延迟,参阅qemu(1)了解如何在boot中加入delay。

通过ISO加载是一个比较好的方法,但该方法只对Windows Vista和Windows Server 2008及其之后的版本有效。这个方法的具体操作是在主磁盘设备和Windows安装盘外挂载一个额外的cdrom设备,将系统镜像与virtio驱动一同加载:

$ qemu-system-x86_64 ... \
-drive file=windows_disk_image,index=0,media=disk,if=virtio \
-drive file=windows.iso,index=2,media=cdrom \
-drive file=virtio.iso,index=3,media=cdrom \
...

在安装过程中,Windows Installer会询问你“Where do you want to install Windows?”,其会返回一个警告表示没有找到任何磁盘设备。接下来跟着如下示例中的步骤进行操作(基于Windows Server 2012 R2 with Update):

  • Select the option Load Drivers.
  • Uncheck the box for Hide drivers that are not compatible with this computer's hardware.
  • Click the browse button and open the CDROM for the virtio iso, usually named "virtio-win-XX".
  • Now browse to E:\viostor\[your-os]\amd64, select it, and confirm.

现在应该能看到virtio磁盘出现在列表中了,等待着被选中、格式化并安装。

将现有的Windows VM转为使用virtio

若要将现有的Windows客户机改为从virtio磁盘中启动,前提是客户机中有virtio驱动,且该驱动在boot期间就被加载。

可以在Fedora 仓库内找到virtio磁盘驱动。

现在,先创建一个新的磁盘镜像,用于搜索virtio驱动:

$ qemu-img create -f qcow2 fake.qcow2 1G

挂载fake磁盘(处于virtio模式下),带有驱动的CD-ROM,运行原本的Windows客户机(boot磁盘依旧是处于IDE模式中):

$ qemu-system-x86_64 -m 4G -drive file=windows_disk_image,if=ide -drive file=fake.qcow2,if=virtio -cdrom virtio-win-0.1-185.iso

Windows会自动检测fake磁盘,并搜索适配的驱动。如果失败了,前往Device Manager,找到SCSI驱动器(带有感叹号图标,应处于打开状态),点击Update driver并选择虚拟的CD-ROM。不要定位到CD-ROM内的文件夹了,只选择CD-ROM设备就行,Windows会自动找到合适的驱动的。(已在Windows 7 SP1中完成测试)。

可以让Windows从下一次起为以安全模式启动,可以用Windows上的msconfig.exe工具完成该配置,在安全模式中所有的驱动都会在boot期间被加载,包括我们新装的virtio驱动。只要Windows知道boot期间需要加载virtio,在未来的boot过程中Windows都会记住该设置的。

将客户机配置为安全模式启动后,可以关机并重新启动它,现在可以以virtio模式挂载boot磁盘:

$ qemu-system-x86_64 -m 4G -drive file=windows_disk_image,if=virtio

加载virtio驱动需要以安全模式进行boot,若不需要则可以返回msconfig.exe禁用安全模式,并重启Windows。

注意: 如果使用if=virtio参数时碰上了蓝屏问题,这代表virtio磁盘驱动可能没有在boot期间被安装,以安全模式重启之,然后检查你的驱动配置。

网络驱动

安装virtio网络驱动程序要容易一些,只需如上所述添加-net参数即可。

$ qemu-system-i386 -m 4G -vga std -drive file=windows_disk_image,if=virtio -net nic,model=virtio -cdrom virtio-win-0.1-185.iso

Windows将检测网络适配器并尝试为其找到驱动程序。如果失败,请转到“设备管理器”,找到带有感叹号图标的网络适配器(双击打开),切换到驱动程序并单击“更新驱动程序”,然后选择虚拟CD-ROM。别忘了选中显示要递归搜索目录的复选框。

Balloon 驱动

如果想要追踪客户机内存状态(比如通过virshdommemstat命令)或者在运行时改变客户机内存大小(尽管依然无法改变实际的内存大小,不过可以通过inflating balloon驱动限制内存的使用),那么请在客户机上安装balloon驱动吧。

安装该驱动需要前往Device Manager,其位于System devices内的PCI standard RAM Controller中(未识别的PCI控制设备则在Other devices中)。选择Update driver,在打开的窗口中选择Browse my computer...,然后选择CD-ROM(记得勾上Include subdirectories选项),安装完成后重启,驱动就安装成功了。现在可以如气球打气一般调整内存限制(例如通过hmp命令balloon memory_size,该命令会使balloon从客户机中尽可能地夺取内存,将客户机的内存大小限制至memory_size)。然而,现在依然无法追踪内存的状态,我们还需要正确地安装Balloon服务才行。以管理员身份启动命令行,前往CD-ROM中的Balloon目录,然后在目录下找到对应系统和架构的地方。当深入至 amd64 (x86) 目录时,运行blnsrv.exe -i进行安装,安装之后virsh命令dommemstat就会输出命令所支持的各种数据。

FreeBSD客户机

如果你使用的是FreeBSD 8.3之后10.0-CURRENT之间的版本, 那么无论内核中是否包含了virtio-kmod, 都需要安装emulators/virtio-kmod端口。安装之后请在/boot/loader.conf添加如下内容:

virtio_load="YES"
virtio_pci_load="YES"
virtio_blk_load="YES"
if_vtnet_load="YES"
virtio_balloon_load="YES"

按照下面修改/etc/fstab :

# sed -ibak "s/ada/vtbd/g" /etc/fstab

注意确认/etc/fstab内容与该命令的预期效果相同,如果出错了就通过rescue CD启动,然后将/etc/fstab.bak复制到/etc/fstab

QEMU 监视器

QEMU运行时会提供一个监视器console界面以方便用户同虚拟机进行交互。QEMU监视器提供了许多有趣的功能,例如获取当前虚拟机的信息,热插拔设备,创建快照等。在QEMU监视器console中运行help?命令,或者阅读official QEMU documentation获得完整的命令列表。

访问QEMU监视器Console

图形化界面

当使用默认的std图形选项时,可以通过按下Ctrl+Alt+2组合键或从QEMU窗口上的View > compatmonitor0访问到QEMU监视器。若要返回到虚拟机的图形界面,那么按下Ctrl+Alt+1或者View > VGA就行。

然而,这种标准的访问方式不够方便,而且并不是在QEMU的所有图形化输出方式中都适用。

Telnet

启动QEMU时带上-monitor telnet:127.0.0.1:port,server,nowait参数可以启用telnet。虚拟机启动后可以通过telnet访问到监视器:

$ telnet 127.0.0.1 port
注意: 如果指定 127.0.0.1 作为监听地址,那么只能在运行QEMU的宿主机上连接到该监视器。如果想要远程访问,QEMU需要在0.0.0.0上进行监听:-monitor telnet:0.0.0.0:port,server,nowait。还要记住的是,最好对firewall进行配置,该连接是完全不进行认证和加密的,因此需要通过防火墙确保本地网络环境是可信的。

UNIX socket

通过-monitor unix:socketfile,server,nowait参数运行QEMU,之后就可以通过socatopenbsd-netcat连接到监视器上。

例如,如果QEMU是通过如下命令启动:

$ qemu-system-x86_64 [...] -monitor unix:/tmp/monitor.sock,server,nowait [...]

就可以像这样连接到监视器上:

$ socat - UNIX-CONNECT:/tmp/monitor.sock

或者通过这种方式:

$ nc -U /tmp/monitor.sock

TCP

可以使用-monitor tcp:127.0.0.1:port,server,nowait参数将监视器暴露于TCP端口上,然后用netcat(openbsd-netcatgnu-netcat都可)进行连接:

$ nc 127.0.0.1 port
注意: 为了能够从其它设备上通过TCP socket访问到监视器,而不仅仅从运行QEMU的主机上连接,需要像前面Telnet中描述的那样,在0.0.0.0地址上进行监听。

标准 I/O

如果以-monitor stdio参数运行QEMU,那么其实是可以在运行QEMU的终端下访问到监视器的。

在Monitor conosle下向虚拟机发送按键行为

由于在某些配置下,宿主机可能会拦截一些按键组合另作他用,这导致要在虚拟机中触发一些特定按键组合变得有些困难(一个显然的例子就是Ctrl+Alt+F*组合,该组合用于改变当前的tty)。我们采用在monitor console下发送按键组合的方式解决该问题。只需切换到monitor console下,然后使用sendkey命令,即可将按键转发至虚拟机中,例如:

(qemu) sendkey ctrl-alt-f2

通过 monitor console 创建快照和管理快照

注意: 该特性"只"支持qcow2格式的虚拟机磁盘镜像,对于raw是无效的。

有时候我们很需要将虚拟机的当前状态进行保存,或是将虚拟机重置到之前的快照状态,而且最好是随时能进行这些操作。QEMU monitor console为用户提供了必要的功能,进行快照创建,快照管理,以及快照恢复。

  • Use savevm name 用于创建一个名为name的快照。
  • Use loadvm name 用于将虚拟机状态恢复至快照name
  • Use delvm name 用于删除快照name
  • Use info snapshots 用于查看保存的快照列表,这些快照由一个自增长的ID和标签名(用户创建快照时赋予)进行标识。

以冻结模式运行虚拟机

QEMU支持以冻结态运行虚拟机(需使用-snapshot参数),换句话说,虚拟机关闭时,对于虚拟机的一切修改都会丢弃。当用户对磁盘镜像写入时,这些变动最终写入的位置是/tmp目录下的一个临时文件,QEMU关机时将会把他们丢弃。

不过,即使虚拟机运行于冻结状态下,依旧可以通过monitor console将这些变化写入磁盘镜像(如果你想的话)。使用下面的命令:

(qemu) commit all

另外如果在冻结状态下创建快照,这些快照在QEMU退出时都会被丢弃,除非你显式地commit了他们。

monitor console中的开机和暂停命令

在QEMU monitor console下也可以模拟对物理机的一些操作:

  • system_powerdown 会向虚拟机发送ACPI关机信号,效果就类似物理机上按下电源按钮。
  • system_reset 会重置虚拟机,类似物理机上的重置按钮。该操作可能导致数据丢失或文件系统的损坏,这是因为虚拟机并不是"干净地"重启的。
  • stop 会暂停虚拟机。
  • cont 使暂停的虚拟机恢复运行。

虚拟机截屏

可以在monitor console下运行该命令,获取PPM格式的截屏图片:

(qemu) screendump file.ppm

QEMU 机器协议

QEMU机器协议(QMP)是一个基于JSON格式的协议,使得其他应用程序可以通过该协议控制QEMU实例。类似#QEMU 监视器,其提供了与运行中的虚拟机进行交互的能力,且能够编程进行控制。关于QMP各命令的描述可以在这个qmp-commands链接中找到。

启动 QMP

使用QMP协议来控制虚拟机的通常做法是在启动QEMU时使用-qmp打开一个TCP socket。底下是一个使用TCP 4444端口的例子:

$ qemu-system-x86_64 [...] -qmp tcp:localhost:4444,server,nowait

而与QMP代理进行通信的一个选择是使用netcat:

nc localhost 4444
{"QMP": {"version": {"qemu": {"micro": 0, "minor": 1, "major": 3}, "package": ""}, "capabilities": []} } 

在目前这个阶段,其能识别的命令仅有qmp_capabilities,QMP将进入了命令模式。敲下:

{"execute": "qmp_capabilities"}

现在,QMP可以接收命令了。要查看QMP接受的命令列表,使用:

{"execute": "query-commands"}

即时将子镜像合并至父镜像中

通过发起一个block-commit可以将一个正处于运行态的快照合并到其父结点上。下面的例子是其最简单的一种形式,将把子镜像提交至父镜像中:

{"execute": "block-commit", "arguments": {"device": "devicename"}}

QMP收到该命令后,处理程序将会寻找基镜像,把该镜像由只读转为可读写模式,然后完成commit的任务。

一旦block-commit操作完成,将会触发一个BLOCK_JOB_READY事件,发出同步完成的信号。当然也可以用一种更优雅的方式完成该操作,改为使用block-job-complete

{"execute": "block-job-complete", "arguments": {"device": "devicename"}}

在发出该命令之前,commit操作将会保持活动状态。 任务完成后,基镜像将会保持在可读写模式,并变为active层。另一方面,子镜像将变得不可使用,用户有责任将其清除。

提示: 执行query-block命令并对输出进行解析可以获取设备列表和其对应名字。设备名在device字段中,例如本例中磁盘设备名就是ide0-hd0
{"execute": "query-block"}
{"return": [{"io-status": "ok", "device": "ide0-hd0", "locked": false, "removable": false, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"backing-image": {"virtual-size": 27074281472, "filename": "parent.qcow2", ... } 

即时创建一个新的快照

为运行中的镜像创建一个快照:

{"execute": "blockdev-snapshot-sync", "arguments": {"device": "devicename","snapshot-file": "new_snapshot_name.qcow2"}}

该命令将会创建一个名为new_snapshot_name.qcow2的堆叠文件,该快照也将成为新的active层

技巧

改善虚拟机的性能表现

底下是一些可以改善虚拟机性能表现的技术,例如:

  • 启用#启用 KVM:QEMU的启动命令加上-enable-kvm选项。
  • 通过-cpu host选项让QEMU模拟宿主机上的特定CPU,如果没有该选项QEMU尝试模拟的是一个更为通用的CPU。
  • 特别的,如果客户机是Windows,启用Hyper-V enlightenments可以改善性能:-cpu host,hv_relaxed,hv_spinlocks=0x1fff,hv_vapic,hv_time.
  • 如果宿主机有多个核心,可以用-smp选项为客户机分配更多核心。
  • 检查是否为虚拟机分配的足够的内存。默认情况下,QEMU仅仅为每台虚拟机分配128MiB的内存,可以使用-m选项分配更多的内存。例如,-m 1024代表启动一台内存为1024MiB的虚拟机。
  • 如果客户机操作系统支持相关的驱动,可以使用virtio创建网络设备或块设备,例如:
$ qemu-system-x86_64 -net nic,model=virtio -net tap,if=tap0,script=no -drive file=disk_image,media=disk,if=virtio
  • 使用TAP设备代替user-mode网络,参阅#Tap 网络
  • 如果客户机需要进行大量的磁盘写工作,在宿主机文件系统上设置合适的挂载选项可以优化该工作。例如,可以用barrier=0选项挂载一个ext4 file system。在使用这些性能强化选项之前最好阅读相关文档,因为性能上的提升通常伴随着数据完整性下降的代价。
  • 如果有一块原始磁盘镜像,你可能会想要禁用cache:
$ qemu-system-x86_64 -drive file=disk_image,if=virtio,cache=none
  • 使用原生的Linux AIO:
$ qemu-system-x86_64 -drive file=disk_image,if=virtio,aio=native,cache.direct=on
  • 如果正同时运行多台虚拟机,而它们拥有同样的操作系统,可以通过启用内核页归并节省内存。参阅#开启KSM
  • 在一些情况下,可以在运行时从安装了balloon驱动的客户机上回收内存,这需要QEMU启动该客户机时使用-device virtio-balloon选项。
  • 允许使用一个ICH-9 AHCI控制器的仿真层,尽管它并不稳定。AHCI的仿真模拟支持NCQ,因此可以同时处理多个读写请求:
$ qemu-system-x86_64 -drive id=disk,file=disk_image,if=none -device ich9-ahci,id=ahci -device ide-drive,drive=disk,bus=ahci.0

参阅 https://www.linux-kvm.org/page/Tuning_KVM 获取更多信息

开机时启动QEMU虚拟机

通过libvirt实现

如果虚拟机是通过libvirt设置的,可以用virsh autostart将其配置为开机自启,或者通过virt-managerGUI中虚拟机的Boot Options,选择"Start virtual machine on host boot up"实现开机自启。

通过systemd service实现

可以用如下的systemd unit和config配置开机时启动QEMU VM。

/etc/systemd/system/[email protected]
[Unit]
Description=QEMU virtual machine

[Service]
Environment="haltcmd=kill -INT $MAINPID"
EnvironmentFile=/etc/conf.d/qemu.d/%i
ExecStart=/usr/bin/qemu-system-x86_64 -name %i -enable-kvm -m 512 -nographic $args
ExecStop=/usr/bin/bash -c ${haltcmd}
ExecStop=/usr/bin/bash -c 'while nc localhost 7100; do sleep 1; done'

[Install]
WantedBy=multi-user.target
注意: 为了方便地结束任务,该service会等待至console端口被释放(这意味着VM已被关闭)。

接着创建per-VM配置文件,命名为/etc/conf.d/qemu.d/vm_name,在其中设置好argshaltcmd变量,配置示例:

/etc/conf.d/qemu.d/one
args="-hda /dev/vg0/vm1 -serial telnet:localhost:7000,server,nowait,nodelay \
 -monitor telnet:localhost:7100,server,nowait,nodelay -vnc :0"

haltcmd="echo 'system_powerdown' | nc localhost 7100" # or netcat/ncat
/etc/conf.d/qemu.d/two
args="-hda /srv/kvm/vm2 -serial telnet:localhost:7001,server,nowait,nodelay -vnc :1"

haltcmd="ssh powermanager@vm2 sudo poweroff"

对该变量的描述如下:

  • args - 使用的QEMU命令行参数。
  • haltcmd - 安全关闭虚拟机的命令,在第一个例子中,QEMU monitor是通过-monitor telnet:..选项暴露至telnet,因而关闭虚拟机是通过nc命令在monitor console中发送system_powerdown,完成ACPI关机的工作。在另一个例子里,使用的则是SSH。

若要设置启动时运行哪个虚拟机,enableqemu@vm_name.service这个systemd单元

鼠标整合

添加-usb -device usb-tablet选项以避免点击客户机系统的窗口时鼠标被捕获。该选项代表QEMU能够在不捕获鼠标的情况下,向系统报告鼠标的位置,该选项启用时还会覆盖PS/2鼠标模拟功能。 命令示例:

$ qemu-system-x86_64 -hda disk_image -m 512 -usb -device usb-tablet

如果该命令不起作用,试试-vga qxl参数,并看看#鼠标指针抖动或者不稳定的操作指导。

宿主机的USB设备传递至虚拟机

从客户机访问连接到宿主机USB口的设备是可能的,首先需要识别设备连接的位置,可以用lsusb命令找到设备连接位置,例如:

$ lsusb
...
Bus 003 Device 007: ID 0781:5406 SanDisk Corp. Cruzer Micro U3

上面以粗体显示的数字分别用于标识“ host_bus”和“ host_addr”或者“ vendor_id”和“ product_id”。

基本的思想是在QEMU中-device usb-ehci,id=ehci-device qemu-xhci,id=xhci分别对EHCI (USB 2)或XHCI (USB 3)控制器进行模拟,然后将物理设备通过-device usb-host,..选项进行添加。在本节中的剩余部分,controller_id要么是ehci,要么是xhci

接着,这里有两种方法通过qemu连接到宿主机的USB:

  1. 识别出该设备,并将其连接至任一总线以及宿主机上的地址,通用的语法如下:
    -device usb-host,bus=controller_id.0,vendorid=0xvendor_id,productid=0xproduct_id
    Applied to the device used in the example above, it becomes:
    -device usb-ehci,id=ehci -device usb-host,bus=ehci.0,vendorid=0x0781,productid=0x5406
    此外也可以在上面的选项中添加...,port=port_number设置,用于指定设备添加至虚拟机控制器上的哪个物理端口。该设置在为VM添加多个设备时比很有用。另一个方案是使用QEMU 5.1.0之后出现的usb-host hostdevice属性,语法为:
    -device qemu-xhci,id=xhci -device usb-host,bus=xhci.0,hostdevice=/dev/bus/usb/003/007
  2. 若要添加一个已连接至特定USB总线和地址的设备,语法为:
    -device usb-host,bus=controller_id.0,hostbus=host_bus,host_addr=host_addr
    ,将其中的参数修改至该总线和地址即可,拿上面的例子来说,需修改为:
    -device usb-ehci,id=ehci -device usb-host,bus=ehci.0,hostbus=3,hostaddr=7
注意: 如果运行QEMU时遇到了权限方面的错误,可以阅读udev#About udev rules获取更多信息,并了解如何为设备设定合适的权限。

使用SPICE进行USB重定向

使用#SPICE时可以将USB设备从客户端重定向至虚拟机中,无需使用QEMU命令。还支持为配置USB重定向插槽数(插槽数将决定可同时重定向的最大设备数)。相比于前面那种使用-usbdevice进行重定向的方法,SPICE方法的优势在于可以在虚拟机启动后USB设备热插拔,移除或添加USB设备时无需停机。这个方法还允许通过网络将客户端的USB设备重定向至服务端。总之,其是在QEMU虚拟机中使用USB设备最灵活的方法。

我们需要为每个所需的USB重定向插槽添加一个EHCI/UHCI控制器,以及每个插槽添加一个SPICE重定向通道。例如,将下面的选项加入到所使用的QEMU命令中,以SPICE模式启动虚拟机时将会添加三个用于重定向的USB插槽:

-device ich9-usb-ehci1,id=usb \
-device ich9-usb-ehci1,id=usb \
-device ich9-usb-uhci1,masterbus=usb.0,firstport=0,multifunction=on \
-device ich9-usb-uhci2,masterbus=usb.0,firstport=2 \
-device ich9-usb-uhci3,masterbus=usb.0,firstport=4 \
-chardev spicevmc,name=usbredir,id=usbredirchardev1 -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \
-chardev spicevmc,name=usbredir,id=usbredirchardev2 -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 \
-chardev spicevmc,name=usbredir,id=usbredirchardev3 -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3

参阅 SPICE/usbredir 获取更多信息。

spice-gtk中的spicy(Input > Select USB Devices for redirection)和virt-viewer中的remote-viewer(File > USB device selection)都支持该特性。请确保你已在客户机上安装必要的SPICE Guest Tools,以使得该功能正常运作(参见#SPICE节获得更多信息)。

警告: 需要牢记一点,当将USB设备从客户端重定向至服务端时,在客户端所在的操作系统上都无法使用该USB了,除非停止重定向。尤其重要的是千万不要将输入设备重定向(即不要将鼠标和键盘重定向),这么做会使得你难以访问到SPICE客户端的菜单而无法撤销该操作,因为在输入设备被重定向至虚拟机后,客户端无法响应它们的操作。

开启KSM

Kernel Samepage Merging (KSM) 是Linux内核的一个特性,允许应用程序向内核申请同其他申请页归并的进程进行页归并,KSM机制允许客户虚拟机之间进行页共享。当许多客户机运行相似的操作系统时,这个机制可以节省客观的内存。

注意: 尽管KSM可能减小内存占用量,其增加了CPU负担。此外要注意的是使用该机制可能产生一些安全问题,参见Wikipedia:Kernel same-page merging

启用KSM:

# echo 1 > /sys/kernel/mm/ksm/run

使之常驻,使用systemd的临时文件

/etc/tmpfiles.d/ksm.conf
w /sys/kernel/mm/ksm/run - - - - 1

当KSM启用,而正好有准备进行归并的页,(比如,至少有两个相似的VM正在运行),那么/sys/kernel/mm/ksm/pages_shared应会是非零的。参阅https://www.kernel.org/doc/html/latest/admin-guide/mm/ksm.html了解更多信息。

提示: 查看KSM性能的一种简单方法是列出此目录下的所有文件内容:
$ grep . /sys/kernel/mm/ksm/*

多屏支持

Linux的QXL驱动支持默认支持四头(虚拟屏幕),可以通过qxl.heads=N这一内核参数进行变更。

QXL设备的默认VGA内存大小为16M(VRAM大小为64M),在想使用两块1920x1200显示器时(需要2 × 1920 × 4 (色深) × 1200 = 17.6 MiB VGA内存)是不够用的。用-vga none -device qxl-vga,vgamem_mb=32选项代替-vga qxl可以对该属性进行变更。如果你将vgamem_mb增大至64M以上,则还需要增大vram_size_mb选项的值。

复制和粘贴

在宿主机和客户机之间共享剪贴板的方法之一是使用SPICE远程桌面协议,通过SPICE客户端访问客户机,你需要遵照#SPICE节中描述的步骤,通过该方式运行的客户机将支持与宿主机进行复制粘贴的操作。

Windows专用说明

QEMU可以运行从Windows 95至Windows 10的任何版本Windows。

在QEMU中也可以运行Windows PE

快速启动设置

注意: 改变电源设置需要一个Administrtor账户

根据此论坛帖子所解释的那样,对于Windows8及之后的客户机最好在Control Panel的Power Options中禁用"Turn on fast startup (recommended)",该设置导致客户机在每次启动时卡死。

为了正确应用对-smp选项进行的更改,可能也需要禁用快速启动。

远程桌面协议

如果你使用一个MS Windows客户机,可能会想使用RDP连接到客户机VM。若你正使用VLAN或与客户机处于同一个网络中,先使用:

$ qemu-system-x86_64 -nographic -net user,hostfwd=tcp::5555-:3389

接着通过rdesktopfreerdp连接到客户机。例如:

$ xfreerdp -g 2048x1152 localhost:5555 -z -x lan

在物理设备上克隆一个已安装的Linux系统

安装在物理设备上的Linux系统可以克隆至QEMU VM中运行,参阅Clone Linux system from hardware for QEMU virtual machine

从x86_64环境中Chroot至arm/arm64环境

有时候相比于在基于ARM架构的设备上工作,在磁盘镜像上进行工作更容易。可以通过挂载一个root分区的SD卡/存储设备,然后chroot到该设备实现该目的。

另一个使用ARM chroot的场景是在x86_64机器上构建ARM包 - armutils-gitAUR可以完成该工作。其中,chroot环境是通过Arch Linux ARM的一个镜像tarball文件创建的 - 参阅[2]了解此种方法的细节描述。

无论哪种方式,都应该可以从chroot运行pacman并安装更多软件包,编译大型库等。由于可执行文件是针对ARM体系结构的,因此需要QEMU转换为x86。 。

在x86_64宿主机上从AUR安装binfmt-qemu-staticAURqemu-user-staticAURbinfmt-qemu-static 将会负责qemu二进制文件注册至binfmt服务的部分。

Restart systemd-binfmt.service

需要qemu-user-staticAUR执行其他架构上的程序,这个包与qemu-arch-extra[损坏的链接:replaced by qemu-emulators-full]提供的程序很相似,只是对于chroot来说必须使用"static"变种。比如:

qemu-arm-static path_to_sdcard/usr/bin/ls
qemu-aarch64-static path_to_sdcard/usr/bin/ls

这两个命令分别用于执行32位ARM和64ARM的ls命令。注意如果没有chroot,这些都不会生效,因为那样的话它们将会试图寻找不存在宿主机系统上的一些二进制文件。

qemu-user-staticAUR 允许自动地用为ARM可执行文件添加qemu-arm-staticqemu-aarch64-static前缀。

检查一下ARM可执行的支持被开启了:

$ ls /proc/sys/fs/binfmt_misc
qemu-aarch64  qemu-arm	  qemu-cris  qemu-microblaze  qemu-mipsel  qemu-ppc64	    qemu-riscv64  qemu-sh4    qemu-sparc	qemu-sparc64  status
qemu-alpha    qemu-armeb  qemu-m68k  qemu-mips	      qemu-ppc	   qemu-ppc64abi32  qemu-s390x	  qemu-sh4eb  qemu-sparc32plus	register

每种可执行的文件都须在此处列出。

如果未开启,请reinstall binfmt-qemu-staticAURrestartsystemd-binfmt.service

将SD卡挂载至/mnt/sdcard(设备名可能不同):

# mkdir -p /mnt/sdcard
# mount /dev/mmcblk0p2 /mnt/sdcard

如有需要,挂载启动分区(同样,使用合适的设备名)

# mount /dev/mmcblk0p1 /mnt/sdcard/boot

最后如Change root#Using chroot所描述的那样,chroot到SD卡的root中:

# chroot /mnt/sdcard /bin/bash

此外,可以使用arch-install-scripts提供的arch-chroot替代chroot,可以很容易地获取网络。

# arch-chroot /mnt/sdcard /bin/bash

还可以用systemd-nspawnchroot到ARM环境中:

# systemd-nspawn -D /mnt/sdcard -M myARMMachine --bind-ro=/etc/resolv.conf

--bind-ro=/etc/resolv.conf 是可选的,其在chroot中提供了一个可用的网络DNS。

处理常见问题

鼠标指针抖动或者不稳定

如果QEMU中鼠标指针不受控制地跳来跳去,那么启动QEMU之前在命令进行如下设置或许能解决该问题:

$ export SDL_VIDEO_X11_DGAMOUSE=0

如果成功了,可以把这条命令加入到你的 ~/.bashrc 文件中

看不见鼠标指针

使用-show-cursor选项启动QEMU以显示指针。

如果这么做不起作用的话,请确保你的显示设备被正确设置了。例如: -vga qxl

还可尝试 #鼠标整合 中提到的 -usb -device usb-tablet。该选项会覆盖默认采用的PS/2鼠标仿真,将鼠标作为外设在宿主机和客户机之间同步指针位置。

有两个不同的鼠标指针

遵循 #鼠标整合 提到的方法。

使用VNC时键盘出现问题

使用VNC时,您可能会遇到链接中所描述的键盘问题(详细信息)here。 解决的方法是不使用QEMU的 -k 选项, 并使用 gtk-vncgvncviewer 。另请参见在libvirt邮件列表中发布的消息

键盘像坏了一样或者方向键不起作用

如果发现某些键不起作用或按下一个按键,触发的却是其他按键,那么你可能需要指定键盘布局选项。在 /usr/share/qemu/keymaps 中可以找到键盘布局选项。

$ qemu-system-x86_64 -k keymap disk_image

无法读取键盘映射文件

qemu-system-x86_64: -display vnc=0.0.0.0:0: could not read keymap file: 'en'

这类问题产生的原因是将非法的键盘映射名作为-k的参数传递给qemu。例如,en是错误的键盘映射名称,en-us才是正确的。相关信息参见/usr/share/qemu/keymaps

客户机在调整窗口大小时一起被拉伸了

按下 Ctrl+Alt+u 可以恢复到原来的大小。

ioctl(KVM_CREATE_VM) failed: 16 Device or resource busy

如果在以 -enable-kvm 选项启动QEMU时显示了这样一条错误信息:

ioctl(KVM_CREATE_VM) failed: 16 Device or resource busy
failed to initialize KVM: Device or resource busy

这意味着还有另一个 hypervisor 处于运行状态, 不推荐同时运行多个hypervisor。

libgfapi 的错误信息

启动时若显示如下错误信息:

Failed to open module: libgfapi.so.0: cannot open shared object file: No such file or directory

安装 glusterfs 或者直接忽略这个错误信息, GlusterFS只是一个可选的依赖。

LIVE-environments 上发生内核错误

如果你启动一个live-environment(或者启动一个系统), 可能会遭遇如下错误:

[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown block(0,0)

或者其他阻碍启动过程的东西(比如无法解包initramfs, 无法启动foo服务之类的), 试试启动VM的时候用 -m VALUE 分配合适的RAM大小, 如果内存太小是很有可能遭遇上述的问题的。

Windows 7 客户机的音频质量差

为Windows7使用 hda 音频驱动可能造成音频质量降低, 通过QEMU的 -soundhw ac97 参数选择 ac97 作为音频驱动, 并从Realtek AC'97 Audio Codecs下载AC97驱动,然后在客户机中安装该驱动可能会解决这个问题。参见Red Hat Bugzilla – Bug 1176761以获取更多信息。

Could not access KVM kernel module: Permission denied

如果遇见了如下的错误信息:

libvirtError: internal error: process exited while connecting to monitor: Could not access KVM kernel module: Permission denied failed to initialize KVM: Permission denied

这是因为 Systemd 234 为 kvm 组注册了一个动态ID (参见FS#54943)。可以编辑 /etc/libvirt/qemu.confgroup = "78" 改为 group = "kvm" 避免产生这个错误。

启动Windows VM时产生 "System Thread Exception Not Handled"

Windows 8或Windows 10 guest虚拟机在启动时可能会引发通用兼容性异常,即“System Thread Exception Not Handled”,这通常是由实体机上旧版驱动程序的异常行为引起的。在KVM机器上,通常可以通过将CPU模型设置为 core2duo来解决此问题。

某些Windows游戏/程序导致蓝屏出现

有时,一些能在物理机上正常运行的程序在虚拟机中会意外地崩溃。如果在运行 dmesg -wH时发现有 MSR相关的错误,则导致这些崩溃的原因是KVM注入了GPF(General protection fault),当客户机尝试访问不受支持的寄存器(MSR, Model-specific registers)时会导致客户机上的应用程序崩溃。将ignore_msrs=1传给KVM模块可以使其忽略不支持的MSR, 从而解决大部分的同类问题。

/etc/modprobe.d/kvm.conf
...
options kvm ignore_msrs=1
...

一些添加该选项可能会起作用的场景:

  • GeForce Experience 反映没有可支持的CPU。
  • 星际争霸2和黑色洛城使用 KMODE_EXCEPTION_NOT_HANDLED 对Windows 10的蓝屏做了一些处理,蓝屏信息中将无法识别驱动。
警告: 虽然通常这么做是安全的,对于某些应用程序也可能不起作用,但默默忽略未知的MSR仍可能破坏VM或其他VM中的其他软件。

VM中的应用程序有很高的延迟,或是需要等待很长的时间才会启动

这可能是由于VM中的可用熵不足所致。考虑通过将VirtIO RNG设备添加到VM,或安装诸如Haveged之类的熵生成守护程序,来允许来宾访问主机的熵池。有趣的是,OpenSSH需要一段时间才能在熵不足的情况下开始接受连接,而其日志却不会显示原因。

中断时间过长以及运行不流畅

此问题表现为小停顿(断断续续),在图形密集型应用程序(例如游戏)中尤其明显。

QXL导致视频分辨率降低

QEMU 4.1.0版本带来了一个回退问题,当使用SPICE时,会将QXL视频降到低分辨率。 [3]例如,当KMS启动时,文本分辨率可能会低至4x10个字符。尝试提高GUI分辨率时,它可能会达到最低的支持分辨率。

解决方法是,以这种形式创建设备:

-device qxl-vga,max_outputs=1...

在 VM initramfs 时挂起

Linux 5.2.11带来了KVM回退的问题。某些情况下,在加载或运行initramfs的早期引导阶段,VM可能会永久挂起。 [4] Linux 5.3修复了该问题。主机会显示QEMU使用了100%CPU * 虚拟CPU数量。报告反馈的情况是主机使用了超线程,且给虚拟机分配了超过主机上 nproc/2 个虚拟CPU。尚不清楚究竟是什么情况触发某个线程删除了内存区域而导致此情况。解决方法是:

  • Upgrade to Linux 5.3.
  • Downgrade to Linux 5.2.10
  • 修复之前, 不要给虚拟机分配超过 nproc/2 个虚拟CPU。
  • 自定义Linux进行编译, 回退到commit 2ad350fb4c之前 (注意,这个操作会将一个移除内存插槽时触发的回退Bug再次引入)。

使用启用了安全启动的OVMF时,VM无法启动

edk2-ovmf中的 /usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd是在SMM的支持下完成构建的,如果未在VM中禁用S3支持,则VM可能根本无法启动。

在QEMU命令中添加 -global ICH9-LPC.disable_s3=1以解决该问题。

参见 FS#59465 以及 https://github.com/tianocore/edk2/blob/master/OvmfPkg/README 获取QEMU使用Secure Boot所需要的更多选项。

客户机上的中断没有被触发

如果你是按照OSDev Wiki来编写自己的操作系统,或者只是使用QEMU的gdb接口和-s标志,您需要知道的是,很多仿真器(包括QEMU)通常会实现一些CPU中断,同时还有许多硬件中断没有被实现。了解代码是否触发中断的一种方法是使用:

-d int

可以在输出中查看 interrupts/exceptions。

如要查看QEMU还提供了什么客户机调试功能,使用如下命令:

qemu-system-x86_64 -d help

或者将 x86_64 替换为你选择的其他架构

参阅