首发于术道经纬
QEMU的配置和使用

QEMU的配置和使用

平时如果需要快速地做一些特性的验证(比如进行kmemleak内存泄露测试),每次都下载到物理板卡上进行试验实在是颇为耗费时间,当特性不是和外设紧密相关时,使用QEMU这样的虚拟化环境就会显得更加地方便和高效。以下将讲解在宿主机上(选用Ubuntu16.04,以下称host),利用QEMU搭建并启动一个运行在ARM64上的Linux系统(以下称guest)的操作步骤和方法。

QEMU一般是通过命令行进行配置的,启动时需要加长长的一串各种参数,这刚开始可能会令人望而生畏,但折腾一番之后,也就能慢慢知晓这些参数的含义了。

环境准备

在Ubuntu系统上,QEMU可以直接通过软件包安装命令(比如"apt")进行安装,也可以下载源码后手动configure再make编译。后者的实际操作过程其实并不麻烦,且可以提高配置的灵活度,所以还是建议使用自己编译源码的方式。

除了安装QEMU,还有两件很重要的事就是制作guest的内核镜像和根文件系统镜像。这三个环节的具体操作步骤,请参考这篇文章,里面介绍的很详细。唯一需要改动的是,在使用./configure配置QEMU的时候,需要加上一个参数"--enable-virtfs",这在我们后面的实验中要用到(另外,ARM版本使用"make menuconfig"配置编译的内核可能无法启动,"make defconfig"可以,原因未知)。

作者在这篇文章里,提供了两种方法来制作根文件系统,一种是基于busybox的,另一种是基于Ubuntu的rootfs的。我采用的是busybox,比较小巧,当然也比较简陋,很多etc配置都没有。

制作的大致过程就是从busybox中拷贝"bin"和"sbin"的可执行文件,从交叉编译工具链中拷贝"lib"的动态链接库,再自己生成"proc", "sys"等目录,以便之后挂载procfs, sysfs这样的内存文件系统。

我编译好的内核和根文件系统镜像都以压缩包的形式放在这里了,你可以直接拿来验证。对于根文件系统镜像,可以mount挂载后进行修改,但是对于内核镜像,就不能直接修改了,所以最好还是都自己编译一下。

启动虚拟机

环境准备后之后,就可以启动QEMU创建的guest虚拟机了,基础的启动参数是这样的:

qemu-system-aarch64 -cpu cortex-a57 -smp 4 -m 2048M -M virt -nographic \
	-kernel build_arm64/arch/arm64/boot/Image \
	-append "console=ttyAMA0 root=/dev/vda init=/linuxrc rw" \
	-drive format=raw,file=../busybox_rootfs/busybox-1.31.1-rootfs_ext4.img
  • 第一行的"cpu"指定了处理器的架构,"smp"指定了处理器的个数,"m"指定了内存的大小。
  • 第二行的"kernel"指定编译生成的内核镜像的存放位置,第三行的"append"是内核启动的附加参数。
  • 第四行的"drive"指定之前制作生成的根文件系统的镜像位置。

因为QEMU后面跟的参数实在太长了,所以我们用了"\"来分割,就会显得清晰很多,也便于修改,不过需要注意的是,直接粘贴到bash的终端去执行是不行的,而是应该放到支持这种语法格式的shell脚本里,再在bash终端去执行这个shell脚本。本文实验所用的shell脚本也放在github上了(名称为qemu-start.sh),供大家参考。

如果制作根文件系统的时候没有在"/etc"中添加挂载信息,那么启动后"/proc"和"/sys"目录下都是空的,可以填写"/etc/fstab"文件让系统启动时自动挂载procfs, sysfs和debugfs,像这样:

proc	/proc 	  	  proc	  defaults    0	  0
sysfs   /sys      	  sysfs   defaults    0   0
debugfs /sys/kernel/debug debugfs defaults    0   0

之后就可以查看一些系统信息了,比如当前内存的使用情况和一些内核参数。

文件共享

不过,这时guest还是没法和host共享文件的,而我们进行功能验证的时候,通常会选择在host上编译测试程序,再拷贝到guest上执行,因此共享文件是一个“刚需”。最容易想到的方法当然是借助传统的TCP/IP网络,之后什么SCP啦,NFS啦,就任君选择了。

不过,TCP/IP是可以用于全球网络通信的,如果只是为了在host和guest之间共享文件的话,其实没有必要使用那么重量级的手段。guest和host在物理上本来就是共享磁盘空间的,如果借助9P协议,那么可以非常快速方便地实现共享,只需要在QEMU启动时附加一句("path"处填上欲共享的host目录):

-fsdev local,security_model=passthrough,id=fsdev0,path=... \
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare

guest启动后执行一下"mount -t 9p -o trans=virtio,version=9p2000.L hostshare /mnt/"命令,就可以在guest端的"/mnt"目录下看到host端共享出来的文件目录了(也就是第一行"path"指定的路径,请根据自己的需要修改)。第二行的"mount_tag"需要和guest的mount命令中对应的参数保持名称的一致。为了下次启动的时候能自动mount,可以把这条mount命令添加到"/etc/init.d/rcS"文件的末尾。更多细节信息请参考这篇文章

9P协议源自大名鼎鼎的贝尔实验室,它最初是作为Plan 9操作系统的文件系统协议而存在的。Plan 9虽然没有被大规模商用,也不被大多数人所熟知,但它其实是一个很有意思的分布式操作系统。在Linux中,像socket, device等都可以被抽象成文件,但是要说到“一切皆文件”,那还得看Plan 9。

在Plan 9中,所有的对象都可以用一致的方式来命名及定址,每个对象都是一个独一无二的路径名称,就像网络的URL地址一样。Plan 9的特性还有很多,在此就不展开了,有兴趣的话强烈推荐阅读下这篇文章

我们在mount命令指定的"version"为"9p2000.L",其中"9p2000"代表9P的第四版,而"L"代表这是可以为Linux提供更好支持的衍生版。在QEMU中使用9P需要virtfs的支持,这也是我们之前在编译QEMU的时候要额外加上"--enable-virtfs"的原因。

网络通信

如果要使用网络通信,情况就要稍微复杂一些。在QEMU中,创建网络的方式大致有三种:user模式、端口转发和TAP桥接,详情请参考这篇WiKi

对于user模式,需要guest端提供一个网卡,这可以通过QEMU模拟出一个e1000的虚拟网卡来实现(需要内核编译时配置CONFIG_E1000),启动时添加的参数最简单可以是这样的:

-nic user,model=e1000e

"nic"指定了网络的模式,这个参数经历了从"-net"到"-netdev",再到"-nic"的发展过程,也就是从前端和后端的分开配置到统一配置的演变,具体信息请参考这篇文章

对于端口转发,可以参考IBM的这篇文章。对于TAP桥接,请参考这篇文章。其实桥接模式还挺有意思的,先创建一个网桥,然后把这个网桥和host上的网卡连起来,再为guest创建一个TAP,并把这个TAP也和网桥连接起来。这三种网络连接方式我都尝试过,总体不是很成功,还是9P来的方便一些。


参考:

利用Qemu-4-0虚拟ARM64实验平台

Wikibooks, open books for an open world

panic:QEMU新的-nic选项

利用主机端口转发实现对QEMU虚拟机的访问 - IBM中国Linux与虚拟化实验室


原创文章,转载请注明出处。

编辑于 2020-05-02 19:42