用qemu搭建虚拟arm环境
- 引言
- 1. VMware + ubuntu20.04 + qemu安装
- 2.安装交叉编译工具
- 3.编译内核kernel
- 4.u-boot编译
- 5.制作根文件系统
- 测试HelloWorld应用程序
- 如何关闭qemu虚拟机
- 补充:
- 参考文献:
引言
Qemu是什么?
Qemu是一个开源的托管虚拟机,通过纯软件来实现虚拟化模拟器,几乎可以模拟任何硬件设备。比如:Qemu可以模拟出一个ARM系统中的:CPU、内存、IO设备等,然后在这个模拟层之上,可以跑一台ARM虚拟机,这个ARM虚拟机认为自己在和硬件进行打交道,但实际上这些硬件都是Qemu模拟出来的。
正因为Qemu是纯软件实现的,所有的指令都要经过它的转换,所以性能非常低。所以在生产环境中,大多数的做法都是配合KVM来完成虚拟化工作,因为KVM是硬件辅助的虚拟化技术,主要负责比较繁琐的CPU和内存虚拟化,而Qemu则负责I/O虚拟化,两者合作各自发挥自身的优势,相得益彰。这部分不是重点,就不具体深入介绍了。
Qemu的两种模式
1.用户模式(User mode):利用动态代码翻译机制来执行不同主机架构的代码,例如:在x86平台上模拟执行ARM代码,也就是说:我们写一条ARM指令,传入整个模拟器中,模拟器会把整个指令翻译成x86平台的指令,然后在x86的CPU中执行。
2.系统模式(System mode):模拟整个电脑系统,利用其它VMM(Xen, KVM)来使用硬件提供的虚拟化支持,创建接近于主机性能的全功能虚拟机。
使用Qemu虚拟机的几种选择
利用Qemu来运行ARM虚拟机,你有2个选择:
简单方式:直接下载别人编译好的映像文件(包含了内核,根文件系统),直接执行即可。
缺点是:别人编译好的也许不适合你的需求,没法定制。
复杂方式:自己下载内核代码、根文件系统代码(例如:busybox),然后进行编译。
优点是:可以按照自己的实际需求,对内核、根文件系统机型裁剪。
在第2种复杂模式中,又可以有2个选择:
2-1. 内核代码、根文件系统代码全部自己手动编译,最后把这些编译结果手动组织在一个文件夹中,形成自己的根目录;
2-2. 利用 buildroot 整个框架,只需要手动进行配置(比如:交叉编译器在本机上的位置、输出路径、系统的裁剪),然后就可以一键编译出一个完整的系统,可以直接烧写到机器!
安装版本
VMware Workstation Pro:16.1.1
ubuntu:20.04 64位
qemu:8.1.50
搭建虚拟arm开发板:
busybox:1.36.0
kernel:5.10
u-boot:2020.10
1. VMware + ubuntu20.04 + qemu安装
参考这篇文章:【VMware + ubuntu20.04 + qemu安装】
下面将正式开始搭建虚拟的arm开发板环境
2.安装交叉编译工具
交叉编译器的作用就不需要详细解释了,因为我们是在x86平台上进行编译,而运行的平台是ARM系统,这2个平台的指令集不一样,所以需要交叉编译得到ARM系统上可以执行的程序。
sudo apt-get install gcc-arm-linux-gnueabi
验证安装结果
dpkg -l gcc-arm-linux-gnueabi
显示如下:
3.编译内核kernel
下载内核kernel压缩包
wget https://mirror.bjtu.edu.cn/kernel/linux/kernel/v5.x/linux-5.10.tar.xz
这里我们使用 vexpress-a9 这款开发板。vexpress-a9 是 Arm 公司自己设计的一款 4 核 Cortex-A9 开发板,U-Boot、Linux Kernel 和 QEMU 对这款开发板都做了完整的支持。当然,如果想搭其它开发板,也不难,只要qemu和内核对它有成熟的支持就够了。
解压:
tar -xvf linux-5.10.tar.xz
在解压后的linux-5.10目录下,生成vexpress开发板子的config文件:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm vexpress_defconfig
编译32位kernel:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm
生成的内核镱像位于arch/arm/boot/zImage
设备树 arch/arm/boot/dts/vexpress-v2p-ca9.dtb
补充:编译64位kernel
make ARCH=arm64 defconfig CROSS_COMPILE=aarch64-linux-gnu- # 如果需要调整配置选项,则使用menuconfig make ARCH=arm64 menuconfig CROSS_COMPILE=aarch64-linux-gnu- make ARCH=arm64 Image -j8 CROSS_COMPILE=aarch64-linux-gnu-
生成的内核镱像位于arch/arm/boot/Image
4.u-boot编译
拉取:
wget https://ftp.denx.de/pub/u-boot/u-boot-2020.10.tar.bz2
下载完后,可以看到 configs 目录下有针对这款开发板的配置文件。ca9x4表示cortexA9架构,4核心,vexpress_ca9x4_defconfig
。
ls configs/ | grep vexpress
编译:
make vexpress_ca9x4_defconfig make CROSS_COMPILE=arm-linux-gnueabihf- all
最终编译生成 elf 格式的可执行文件 u-boot 和纯二进制文件u-boot.bin,其中 QEMU 可以启动的为 elf 格式的可执行文件 u-boot 。
5.制作根文件系统
内核在启动之后、执行到最后步骤时,需要挂载根文件系统,然后执行文件系统中指定的执行程序(初始化程序,本文未设置),例如:/etc/rc.local。
如果没有跟文件系统,那么内核在执行到最后就提示:panic…。
根文件系统放在哪里?
其实依赖于每个开发板支持的存储设备,可以放到Nor Flash上,也可以放到SD卡,甚至外部磁盘上。最关键的一点是你要清楚知道开发板有什么存储设备。
本文介绍了使用SD卡做为存储空间,文件格式为ext3格式。
第一步:下载、编译和安装busybox
下载busybox1.36.0:
wget http://www.busybox.net/downloads/busybox-1.36.0.tar.bz2
编译,安装:
make defconfig make CROSS_COMPILE=arm-linux-gnueabi- make install CROSS_COMPILE=arm-linux-gnueabi-
安装完成后,会在busybox目录下生成_install目录,该目录下的程序就是单板运行所需要的命令。
第二步:形成根目录结构
先在Ubuntu主机环境下,形成目录结构,里面存放的文件和目录与单板上运行所需要的目录结构完全一样,然后再打包成镜像(在开发板看来就是SD卡),这个临时的目录结构称为根目录。
- 首先创建rootfs目录(根目录),根文件系统内的文件全部放到这里:
mkdir -p rootfs/{dev,etc/init.d,lib}
- 把busybox中的文件复制到rootfs根目录下,主要是一些基本的命令:
sudo cp busybox-1.20.2/_install/* -r rootfs/
- 把交叉编译工具链中的库文件复制到rootfs根目录的lib文件夹下:
sudo cp -P /usr/arm-linux-gnueabi/lib/* rootfs/lib/
- 创建4个tty端终设备:
sudo mknod rootfs/dev/tty1 c 4 1 sudo mknod rootfs/dev/tty2 c 4 2 sudo mknod rootfs/dev/tty3 c 4 3 sudo mknod rootfs/dev/tty4 c 4 4
第三步:制作根文件系统镜像
制作根文件系统镜像 根文件系统镜像就相当于一个硬盘,就是把上面rootfs根目录中的所有文件复制到这个硬盘中。
第一种:
生成512M大小的镜像:
dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=32
格式化成ext3文件系统:
mkfs.ext3 a9rootfs.ext3
挂载,将文件拷贝到镜像中:
sudo mkdir tmpfs sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop sudo cp -r rootfs/* tmpfs/ sudo umount tmpfs
qemu启动ARM虚拟机运行:
qemu-system-arm -M vexpress-a9 -m 512M -kernel /path/to/kernel/dir/arch/arm/boot/zImage -dtb /path/to/kernel/dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0 console=ttyAMA0" -sd a9rootfs.ext3
“从内核启动打印,到命令行提示符出现”
第二种:
生成512M大小的磁盘镜像:
qemu-img create -f raw disk.img 512M
把磁盘镜像格式化成ext4文件系统:
mkfs -t ext4 ./disk.img
将rootfs根目录中的所有文件复制到磁盘镜像中 操作步骤是:创建挂载点-挂载-复制文件-卸载:
mkdir tmpfs sudo mount -o loop ./disk.img tmpfs/ sudo cp -r rootfs/* tmpfs/ sudo umount tmpfs
使用file指令检查一下:
file disk.img
qemu启动ARM虚拟机运行:
qemu-system-arm -M vexpress-a9 -m 512M -kernel /path/to/kernel/dir/arch/arm/boot/zImage -dtb /path/to/kernel/dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0 rw console=ttyAMA0" -sd disk.img
“从内核启动打印,到命令行提示符出现”
测试HelloWorld应用程序
在Ubuntu任意一个目录,编写HelloWorld可执行程序hello.c:
touch hello.c vi hello.c
按i进入输入模式,复制下面代码,然后esc,shift+:,wq保存退出:
#include <stdio.h> int main() { printf("HelloWorld! \n"); return 0; }
交叉编译hello.c,得到arm的可执行程序hello:
arm-linux-gnueabi-gcc hello.c -o hello
通过file指令,查看一下hello程序:
file hello
把hello可执行程序复制到磁盘镜像disk.img中 操作步骤是:挂载-复制文件-卸载:
sudo mount -o loop ./disk.img tmpfs/ cp hello tmpfs/ sudo umount tmpfs
执行hello程序 再次启动虚拟机,此时可以在根目录下面看到hello文件,直接执行即可看到输出结果。
总结:
在以上的操作步骤中,我们把一个ARM系统在启动应用程序之前,所需要的程序都手动编译、操作了一遍。看一遍很容易就明白,亲手操作一遍印象会更深刻。
这里的操作过程有些还需要继续深入,比如:在系统启动之后,自动挂载宿主机(Ubuntu系统)中的某个文件夹,这样就可以把hello等可执行程序复制到挂载目录中,然后在ARM系统中直接执行了,而不用再执行下面在一连串的操作(停止虚拟机-挂载磁盘镜像-复制文件-卸载-启动虚拟机)。
如何关闭qemu虚拟机
停止虚拟机 在Ubuntu另一个终端窗口中,通过killall指令来停止。
killall qemu-system-arm
或者直接 Ctrl+a 再按x,直接退出qemu环境。
补充:
关于”make: arm-linux-gnueabihf-gcc: Command not found“问题
解决方法:sudo apt-get install gcc-arm*
参考:https://blog.csdn.net/HGGshiwo/article/details/120479087
关于qemu启动ARM虚拟机运行指令解析
qemu-system-arm -M vexpress-a9 -m 512M -kernel /path/to/kernel/dir/arch/arm/boot/zImage -dtb /path/to/kernel/dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0 console=ttyAMA0" -sd a9rootfs.ext3
-M vexpress-a9 模拟vexpress-a9单板,你可以使用-M ?参数来获取该qemu版本支持的所有单板
-m 512M 单板运行物理内存512M
-kernel /path/to/kernel/dir/arch/arm/boot/zImage 告诉qemu单板运行内核镜像路径
-nographic 不使用图形化界面,只使用串口
-append “console=ttyAMA0” 内核启动参数,这里告诉内核vexpress单板运行,串口设备是那个tty。
因为不同单板串口驱动类型不尽相同,创建的tty设备名当然也是不相同的。那vexpress单板的tty设备名是哪个呢? 其实这个值可以从生成的.config文件CONFIG_CONSOLE宏找到。
如果搭建其它单板,需要注意内核启动参数的console=参数值,同样地,可从生成的.config文件中找到。
参考文献:
如果想了解更多细节,包括:创建设备结点,设置初始化进程/etc/rcS,u-boot加载内核等,可参考:
【宅学部落的qemu教程】