> 技术文档 > 使用Qemu模拟32位ARM系统_qemu-system-arm

使用Qemu模拟32位ARM系统_qemu-system-arm


一、环境

实验环境如下:

主机:x86_64操作系统:Ubuntu 20.04.6 LTSQemu版本:QEMU emulator version 4.2.1Linux内核版本:linux-4.4.240Busybox版本:busybox-1.35.0

二、前置准备

下载 linux-4.4.240 源码文件: linux-4.4.240

使用Qemu模拟32位ARM系统_qemu-system-arm
下载 busybox-1.35.0:busybox-1.35.0
使用Qemu模拟32位ARM系统_qemu-system-arm

下载 qemu

 sudo apt install qemu-system-arm

qemu 版本号如下:

QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.30)Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

可以使用以下命令 qemu-system-arm -M help 查看 qemu 支持哪些 ARM 开发板,本文将选择 vexpress-a9vexpress-a15 开发板进行模拟:

$ qemu-system-arm -M helpSupported machines are:akita Sharp SL-C1000 (Akita) PDA (PXA270)ast2500-evb Aspeed AST2500 EVB (ARM1176)ast2600-evb Aspeed AST2600 EVB (Cortex A7)borzoi  Sharp SL-C3100 (Borzoi) PDA (PXA270)canon-a1100 Canon PowerShot A1100 IScheetah  Palm Tungsten|E aka. Cheetah PDA (OMAP310)collie  Sharp SL-5500 (Collie) PDA (SA-1110)connex  Gumstix Connex (PXA255)cubieboard  cubietech cubieboard (Cortex-A8)emcraft-sf2 SmartFusion2 SOM kit from Emcraft (M2S010)highbank Calxeda Highbank (ECX-1000)imx25-pdk ARM i.MX25 PDK board (ARM926)integratorcp ARM Integrator/CP (ARM926EJ-S)kzm  ARM KZM Emulation Baseboard (ARM1136)lm3s6965evb Stellaris LM3S6965EVBlm3s811evb  Stellaris LM3S811EVBmainstone Mainstone II (PXA27x)mcimx6ul-evk Freescale i.MX6UL Evaluation Kit (Cortex A7)mcimx7d-sabre Freescale i.MX7 DUAL SABRE (Cortex A7)microbit BBC micro:bitmidway  Calxeda Midway (ECX-2000)mps2-an385  ARM MPS2 with AN385 FPGA image for Cortex-M3mps2-an505  ARM MPS2 with AN505 FPGA image for Cortex-M33mps2-an511  ARM MPS2 with AN511 DesignStart FPGA image for Cortex-M3mps2-an521  ARM MPS2 with AN521 FPGA image for dual Cortex-M33musca-a  ARM Musca-A board (dual Cortex-M33)musca-b1 ARM Musca-B1 board (dual Cortex-M33)musicpal Marvell 88w8618 / MusicPal (ARM926EJ-S)n800  Nokia N800 tablet aka. RX-34 (OMAP2420)n810  Nokia N810 tablet aka. RX-44 (OMAP2420)netduino2 Netduino 2 Machinenone  empty machinenuri  Samsung NURI board (Exynos4210)palmetto-bmc OpenPOWER Palmetto BMC (ARM926EJ-S)raspi2  Raspberry Pi 2realview-eb ARM RealView Emulation Baseboard (ARM926EJ-S)realview-eb-mpcore ARM RealView Emulation Baseboard (ARM11MPCore)realview-pb-a8 ARM RealView Platform Baseboard for Cortex-A8realview-pbx-a9 ARM RealView Platform Baseboard Explore for Cortex-A9romulus-bmc OpenPOWER Romulus BMC (ARM1176)sabrelite Freescale i.MX6 Quad SABRE Lite Board (Cortex A9)smdkc210 Samsung SMDKC210 board (Exynos4210)spitz Sharp SL-C3000 (Spitz) PDA (PXA270)swift-bmc OpenPOWER Swift BMC (ARM1176)sx1  Siemens SX1 (OMAP310) V2sx1-v1  Siemens SX1 (OMAP310) V1terrier  Sharp SL-C3200 (Terrier) PDA (PXA270)tosa  Sharp SL-6000 (Tosa) PDA (PXA255)verdex  Gumstix Verdex (PXA270)versatileab ARM Versatile/AB (ARM926EJ-S)versatilepb ARM Versatile/PB (ARM926EJ-S)vexpress-a15 ARM Versatile Express for Cortex-A15vexpress-a9 ARM Versatile Express for Cortex-A9virt-2.10 QEMU 2.10 ARM Virtual Machinevirt-2.11 QEMU 2.11 ARM Virtual Machinevirt-2.12 QEMU 2.12 ARM Virtual Machinevirt-2.6 QEMU 2.6 ARM Virtual Machinevirt-2.7 QEMU 2.7 ARM Virtual Machinevirt-2.8 QEMU 2.8 ARM Virtual Machinevirt-2.9 QEMU 2.9 ARM Virtual Machinevirt-3.0 QEMU 3.0 ARM Virtual Machinevirt-3.1 QEMU 3.1 ARM Virtual Machinevirt-4.0 QEMU 4.0 ARM Virtual Machinevirt-4.1 QEMU 4.1 ARM Virtual Machinevirt  QEMU 4.2 ARM Virtual Machine (alias of virt-4.2)virt-4.2 QEMU 4.2 ARM Virtual Machinewitherspoon-bmc OpenPOWER Witherspoon BMC (ARM1176)xilinx-zynq-a9 Xilinx Zynq Platform Baseboard for Cortex-A9z2  Zipit Z2 (PXA27x)

一个开发板可能支持不同的CPU,可以通过以下命令查看对应的开发板 vexpress-a9 支持哪些CPU:

$ qemu-system-arm -M vexpress-a9 --cpu helpAvailable CPUs: arm1026 arm1136 arm1136-r2 arm1176 arm11mpcore arm926 arm946 cortex-a15 cortex-a7 cortex-a8 cortex-a9 cortex-m0 cortex-m3 cortex-m33 cortex-m4 cortex-r5 cortex-r5f max pxa250 pxa255 pxa260 pxa261 pxa262 pxa270-a0 pxa270-a1 pxa270 pxa270-b0 pxa270-b1 pxa270-c0 pxa270-c5 sa1100 sa1110 ti925t

安装交叉编译工具链

sudo apt-get install crossbuild-essential-armhf

工具链版本如下:

$ arm-linux-gnueabi-gcc --versionarm-linux-gnueabi-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0Copyright (C) 2019 Free Software Foundation, Inc.This is free software; see the source for copying conditions. There is NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

交叉编译 ARM32 Linux内核

 cd linux-4.4.420/$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- vexpress_defconfig$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)

说明:

  • ARCH:指定目标CPU架构;
  • CROSS_COMPILE:指定交叉编译器;
  • O=build:O是Out的缩写,表示编译输出文件放在build目录,不跟源码混在一起,保持源码的整洁性;
  • 要模拟 vexpress 开发板,所以选择 vexpress_defconfig 配置文件(对应原厂默认内核配置文件arch/arm/configs/vexpress_defconfig ),make 的过程会根据原厂默认配置文件生成可直接用于编译的内核配置文件 .config

查看下内核编译出来的原始内核文件 vmlinux ,是ARM 32-bit版本:

$ file build/vmlinuxbuild/vmlinux: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=1d1a062137cad67734d7da3c441fbda869a74e59, with debug_info, not stripped

vmlinux 不能直接引导Linux系统启动,能引导Linux系统启动的是 bzImage 文件,由 vmlinuxobjcopy 处理后的二进制内核映像,下文用到的内核镜像就是 bzImage

$ file build/arch/arm/boot/zImagebuild/arch/arm/boot/zImage: ARM OpenFirmware FORTH Dictionary, Text length: -509607936 bytes, Data length: -509607936 bytes, Text Relocation Table length: -369098749 bytes, Data Relocation Table length: 24061976 bytes, Entry Point: 0x00000000, BSS length: 3505288 bytes

同时也会生成多个 vexpress 开发板的设备树文件,分别对应前文 ARM Versatile Express提到的三种开发板:

$ ls -l build/arch/arm/boot/dts/vexpress*.dtb-rw-rw-r-- 1 wurusai wurusai 18770 330 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dtb-rw-rw-r-- 1 wurusai wurusai 13092 330 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb-rw-rw-r-- 1 wurusai wurusai 12714 330 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca5s.dtb-rw-rw-r-- 1 wurusai wurusai 14360 330 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb

交叉编译 ARM32 Busybox

BusyBox 将许多常见UNIX实用程序的微型版本组合到单个小型可执行文件中。用来代替通常在 GNU fileutils,shellutils 等大多数实用程序。BusyBox 中的实用程序通常比其功能齐全的GNU表亲少一些选项。但是,所包含的选项提供了预期的功能,并且其行为与GNU对应项非常相似。BusyBox 为任何小型或嵌入式系统提供了一个相当完整的环境。

$ cd busybox-1.35.0/$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig

menuconfig 菜单中:

  • 选中 Settings –> Build Options –> [*] Build static binary(no share libs)
  • 取消勾选 Shells ---> [ ] Job control

使用Qemu模拟32位ARM系统_qemu-system-arm

使用Qemu模拟32位ARM系统_qemu-system-arm

接着继续进行编译安装:

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j 8$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install

安装完后会默认安装到源码目录的 _install/ 目录下:

$ ls _install/bin linuxrc sbin usr

最关键的就是 _install/bin/busybox ,其他都是链接文件:

$ file _install/bin/busybox_install/bin/busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=742ae0c792359d80187b66b73e21511b9df9a880, for GNU/Linux 3.2.0, stripped

三、准备启动

方式一

使用busybox制作initramfs

创建虚拟 rootfs 中的 init 启动脚本,并赋予可执行权限:

$ cd busybox-1.35.0/_install/$ mkdir proc sys dev tmp$ touch init$ chmod +x init

init 脚本内容如下:

#!/bin/sh# 挂载一些必要的文件系统mount -t proc none /procmount -t sysfs none /sysmount -t tmpfs none /tmpmount -t devtmpfs none /devechoecho \"Hello 32-bit ARM Linux\"# 显示开机消耗时间echo \"This boot took $(cut -d\' \' -f1 /proc/uptime) seconds\"echo# 停留在控制台exec /bin/sh

制作 initramfs 文件,它是多个文件通过 cpio 打包和 gzip 压缩的文件,是一个 cpio 格式的内存文件系统:

$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

打包出来的文件如下:
使用Qemu模拟32位ARM系统_qemu-system-arm

使用QEMU启动ARM32 Linux内核

1.模拟 vexpress-a9 开发板

vexpress-a9 开发板,其处理器是 32-bit 的 Cortex-A9,4核。

以字符界面方式启动QEMU(不启动图形界面),同时日志输出到控制台:

$ qemu-system-arm \\-M vexpress-a9 \\-cpu cortex-a9 \\-smp 4 \\-m 1G \\-nographic \\-kernel ./linux-4.4.420/build/arch/arm/boot/zImage \\-dtb ./linux-4.4.420/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \\-initrd ./busybox-1.35.0/initramfs.cpio.gz \\-append \"init=/init console=ttyAMA0\"

QEMU参数说明(更多可参考:Standard options):

  • -M:指定模拟的开发板,可通过 qemu-system-arm -M help查看;
  • -cpu:指定模拟的cpu,可通过 qemu-system-arm -M vexpress-a9 --cpu help 查看;
  • -smp:指定cpu核数量,启动后可以使用 nproc 命令核对, vexpress-a9 最多只支持4核,可少不可多,超过启动会报错;
  • -m:指定内存大小,vexpress-a9 最大支持 1GB,可少不可多 ,超过启动会报错,启动后可以使用free -h 命令核对;
  • -kernel:指定启动的内核镜像;
  • -initrd:指定启动的内存文件系统;
  • -append:传递给内核的启动参数;启动后可使用 cat /proc/cmdline 命令核对;
  • -nographic:启动字符界面(不启动图形界面),输出重定向到宿主机命令行,与参数 console=ttyAMA0 组合使用。

2.模拟 vexpress-a15 开发板

以字符界面方式启动QEMU(不启动图形界面),同时日志输出到控制台:

$ qemu-system-arm \\-M vexpress-a15 \\-cpu cortex-a15 \\-smp 2 \\-m 2G \\-nographic \\-kernel ./linux-4.4.420/build/arch/arm/boot/zImage \\-dtb ./linux-4.4.420/build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb \\-initrd ./busybox-1.35.0/initramfs.cpio.gz \\-append \"init=/init console=ttyAMA0\"

说明:

  • vexpress-a15 的CPU数最多支持2核,-smp 配置超过启动不会报错,但启动后只能看到2核;
  • vexpress-a15 的内存最大支持2G,-m 配置超过启动不会报错,但启动后只能看到 2G内存。

注意:在客户机终端按下 ctrl + a,再按一下 x 键就能退出 qemu 然后回到宿主机。

问题

在使用 QEMU 启动 Linux 内核时,如果通过 -initrd 参数直接加载一个预先打包好的 ​initramfs.cpio.gz 作为根文件系统(如你的命令所示),那么默认情况下 ​所有文件操作都仅在内存中进行,关闭 QEMU 后修改会丢失 。

当前方案的限制
​- initramfs 是一个临时内存文件系统,所有文件在启动时解压到内存中,运行时修改仅影响内存副本,不会回写到原始的 .cpio.gz 文件。

​- 如果在 QEMU 中通过 touch /newfilerm /file 修改文件系统,这些改动仅在本次运行中有效。关闭 QEMU 后,下次启动仍会从原始的 .cpio.gz 文件加载初始状态。若想保留修改,必须重新打包 initramfs 并重启。

实验

1.先启动 qemu

使用Qemu模拟32位ARM系统_qemu-system-arm
2.尝试创建 /home 目录,创建一个文件 a.txt
使用Qemu模拟32位ARM系统_qemu-system-arm
3.退出后重新启动 qemu

使用Qemu模拟32位ARM系统_qemu-system-arm
由此可见之前的修改并没有生效!

方式二(推荐)

busybox 初始化配置

BusyBox 的编写考虑了尺寸的优化和有限的资源。它也是非常模块化的,因此可以在编译时轻松地包含或排除命令(或功能)。这使自定义嵌入式系统变得容易。要创建一个工作系统,只需在 /dev 中添加一些设备节点,在 /etc 中添加一些配置文件,以及一个 Linux 内核。

解压后编译 busybox

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install

编译完成后在 busybox 目录下生成了 _intall 目录,将作为我们构建根文件系统的目录,在根文件系统目录下补充一些内容。 增加以下目录:

执行命令:

mkdir {etc,proc,sys,tmp,dev,lib}

使用Qemu模拟32位ARM系统_qemu-system-arm

  • etc :主要存放一些配置文件如:inittab (init 进程会解析此文件,看进一步动作);fstab(主要包含一些挂载的文件系统,如sys procinit.rd/rcS(可存放一些可执行脚本,配合 inittab 使用)
  • proc : proc文件系统挂载点
  • syssys文件系统挂载点
  • tmptmp文件系统挂载点
  • dev : 设备文件
  • lib : 库文件目录(如果busybox采用动态链接库,则需要将交叉编译链的库文件拷这里

注意:本文采用的是静态链接库,所以不需要拷贝库到这个 lib 目录下。但是静态链接库,会占用更多的内存。

dev 目录下静态创建如下节点:

sudo mknod -m 666 tty1 c 4 1sudo mknod -m 666 tty2 c 4 2sudo mknod -m 666 tty3 c 4 3sudo mknod -m 666 tty4 c 4 4sudo mknod -m 666 console c 5 1sudo mknod -m 666 null c 1 3

console null 是必须的,如果没有则会报错。
使用Qemu模拟32位ARM系统_qemu-system-arm

etc/inittab 文件内容如下,可参考 busyboxdir/examples/inittab 编写:

::sysinit:/etc/init.d/rcS::askfirst:/bin/sh::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r::restart:/sbin/inittty2::askfirst:/bin/shtty3::askfirst:/bin/shtty4::askfirst:/bin/sh

使用Qemu模拟32位ARM系统_qemu-system-arm

etc/fstab 文件内容如下,主要目的是指明一些文件系统挂载点:

#device mount-point type option dump fsck orderproc /proc proc defaults 0 0#temps /tmp rpoc defaults 0 0none /tmp ramfs defaults 0 0sysfs /sys sysfs defaults 0 0mdev /dev ramfs defaults 0 0

使用Qemu模拟32位ARM系统_qemu-system-arm

etc/init.d/rcS 文件内容如下,inittab 第一条指明了从 rcS 中去执行脚本

#!/bin/shmount -aecho \"/sbin/mdev\" > /proc/sys/kernel/hotplug/sbin/mdev -s # 根据/etc/mdev.conf中的配置进行生成设备节点mount -a

最后修改 rcS 的权限:

chmod 777 etc/init.d/rcS

使用Qemu模拟32位ARM系统_qemu-system-arm

创建根文件系统

使用 dd 命令创建一个空白的 512M (根据实际情况)文件:

dd if=/dev/zero of=rootfs.ext3 bs=1M count=512

使用Qemu模拟32位ARM系统_qemu-system-arm

将该空白文件格式化为 ext3 格式(内核默认支持文件系统,如果使用其他需要配置内核):

mkfs.ext3 rootfs.ext3

将该空白文件,挂载在一个目录下:

mkdir rootfssudo mount -o loop rootfs.ext3 ./rootfs

使用Qemu模拟32位ARM系统_qemu-system-arm

busybox 构建的根文件系统拷贝到挂载点下,然后再卸载:

sudo cp -rf busybox-1.35.0/_install/* ./rootfssudo umount rootfs

使用QEMU启动ARM32 Linux内核

执行以下命令启动:

qemu-system-arm \\-M vexpress-a9 \\-kernel ./linux-4.4.240/build/arch/arm/boot/zImage \\-nographic \\-m 512M \\-smp 4 \\-dtb ./linux-4.4.240/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \\-sd rootfs.ext3 \\-append \"init=/linuxrc root=/dev/mmcblk0 rw rootwait earlyprintk console=ttyAMA0\"

上述命令:

  • -M 指定了目标板;
  • -kernel 指定了linux内核镜像;
  • -nographic 指定无图形显示;
  • -m 512M 指定了运行内存大小;
  • -smp 指定4核;
  • -sd 指定了外部有1个 sd 卡,卡内是 rootfs.ext3 镜像文件;
  • -dtb 指定了设备树文件;
  • -append 指定了 bootargsbootargsinit=/linuxrc 指定了 init 进程是根文件系统下的 linuxrc(busybox生成), root=/dev/mmcblk0 指定了根文件系统为 sd 卡, console 指定了 ttyAMA0,即控制台。

支持文件共享

1.宿主机 创建一个用于文件共享的目录 shared

mkdir shared

2.使用以下命令启动 qemu

qemu-system-arm \\ -M vexpress-a9 \\ -m 512M \\ -kernel /home/wurusai/workspace/linux-4.4.240/arch/arm/boot/zImage \\ -dtb /home/wurusai/workspace/linux-4.4.240/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \\ -nographic \\ -append \"root=/dev/mmcblk0 rw console=ttyAMA0\" \\ -drive file=rootfs.ext3,format=raw,if=sd,readonly=off \\ -fsdev local,id=host0,path=./shared,security_model=passthrough \\ -device virtio-9p-device,fsdev=host0,mount_tag=host0

3.客户机 创建一个用于共享的文件夹 /mnt/shared,并且挂载:

mkdir -p /mnt/sharedmount -t 9p -o trans=virtio host0 /mnt/shared/

最终效果如下:

宿主机:

使用Qemu模拟32位ARM系统_qemu-system-arm

客户机:
使用Qemu模拟32位ARM系统_qemu-system-arm

解决文件共享的问题

目前文件共享有一个问题——就是每次启动 qemu 都需要手动来挂载共享目录 /mnt/shared。为了解决这个问题,可以把这个挂载命令写在脚本 S90_mount_shared 里面,然后放在 /etc/init.d/ 目录下,这样每次启动这个脚本就会被自动执行,不需要手动挂载目录了。

详情参考:嵌入式Linux设置开机自动运行程序(基于BusyBox init)

/etc/inittab

::sysinit:/etc/init.d/rcS::askfirst:/bin/sh::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r::restart:/sbin/inittty2::askfirst:/bin/shtty3::askfirst:/bin/shtty4::askfirst:/bin/sh# Stuff to do before rebooting::shutdown:/etc/init.d/rcK::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r

/etc/init.d/rcS

#!/bin/shmount -aecho \"/sbin/mdev\" > /proc/sys/kernel/hotplug/sbin/mdev -s # 根据/etc/mdev.conf中的配置进行生成设备节点mount -a# Start all init scripts in /etc/init.d# executing them in numerical order.#for i in /etc/init.d/S??* ;do # Ignore dangling symlinks (if any). [ ! -f \"$i\" ] && continue case \"$i\" in *.sh) # Source shell script for speed. ( trap - INT QUIT TSTP set start . $i ) ;; *) # No sh extension, so fork subprocess. $i start ;; esacdone

脚本文件 /etc/init.d/S90_mount_shared

#!/bin/shumask 077start() { echo \"Mounting shared directory successfully!\" mount -t 9p -o trans=virtio host0 /mnt/shared}stop() { echo \"Umounting /mnt/shared successfully!\" umount /mnt/shared}restart() { stop start}case \"$1\" in start) start ;; stop) stop ;; restart|reload) restart ;; *) echo \"Usage: $0 {start|stop|restart}\" exit 1esacexit $?

最终启动效果如图:

使用Qemu模拟32位ARM系统_qemu-system-arm

四、参考

  • QEMU启动ARM32 Linux内核
  • 使用qemu运行自己编译的arm内核
  • 在QEMU虚拟机和宿主机之间传输文件