Qemu:QEMU是一款开源的硬件虚拟化软件,可以在不同的主机平台上运行虚拟机。它通过动态的二进制转换,模拟CPU,并且提供一组设备模型,使它能够运行多种未修改的客户机OS。QEMU采用全系统仿真,可以模拟完整的计算机系统,包括处理器、内存、存储和外围设备。它提供硬件仿真,允许在一个虚拟环境中运行不同体系结构的操作系统和应用程序。QEMU可以与KVM一起使用,进而接近本地速度运行虚拟机。
目前,QEMU里面32bit arm平台比较多,如vexpress-a9,versatilepb等, 64bit arm64平台比较少。而Linero开发的”virt”平台可同时支持32bit 和64bit的arm(详见QEMU源码/hw/arm/virt.c )。“virt”支持 PCI,virtio,最新的arm CPU 和 大容量RAM。如果只想在最新的arm cpu上面运行linux虚拟机,不在意特定的硬件,”virt”平台是最佳选择
环境选择
Qemu环境搭建 QEMU emulator version 7.2.8 版本不能太低 否则会报错。
Buildroot版本:buildroot2023.11
Linux kernel版本:5.10.208
Uboot版本: uboot 2018
busybox版本:busybox-1.36-1
首先需要对buildroot进行配置,从而完成uboot,kernel,rootfs的构建。
选择qemu virt平台进行arm64 cortex a57的测试。
编译
以buildroot方式编译三大件的配置方式
buildroot配置:buildroot中提供了qemu-virt环境的默认配置。路径在/buildroot/configs/qemu_aarch64_virt_defconfig中为其默认配置,这里做了一些修改,将其覆盖即可:
BR2_aarch64=y
BR2_cortex_a57=y
BR2_TOOLCHAIN_EXTERNAL=y
BR2_TOOLCHAIN_EXTERNAL_LINARO_AARCH64=y
BR2_TOOLCHAIN_EXTERNAL_GDB_SERVER_COPY=y
BR2_SYSTEM_DHCP="eth0"
BR2_ROOTFS_POST_IMAGE_SCRIPT="board/qemu/post-image.sh"
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="5.10.208"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
BR2_TARGET_ROOTFS_CPIO=y
BR2_TARGET_ROOTFS_CPIO_GZIP=y
BR2_TARGET_ROOTFS_CPIO_UIMAGE=y
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_SIZE="200M"
# BR2_TARGET_ROOTFS_TAR is not set
BR2_TARGET_UBOOT=y
BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
BR2_TARGET_UBOOT_CUSTOM_VERSION=y
BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2018.11"
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="qemu_arm64"
其中对uboot和linux的配置文件均为arm64架构下的qemu virt平台下的配置文件。
配置文件中指定了uboot和kernel的配置文件分别问qemu_arm64.defconfig和"board/qemu/aarch64-virt/linux.config"
进入buildroot目录:
cd buildroot
sudo make qemu_aarch64_virt_defconfig
sudo make
等待编译完成。
编译完成后在buildroot/output/image下会出现镜像文件。第一次编译需要下载很多依赖,需要耐心等待,如果有等待很久下载不下的情况,可以自行下载至buildroot的dl文件夹目录下。重新make的时候buildroot会自动识别安装包并解压。
自行编译三大件的方式
uboot:
sudo make ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-linux-gnu- qemu_arm64_defconfig
sudo make CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-linux-gnu- -j4
需要根目录下的u-boot文件
kernel:
sudo make ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-buildroot-linux-gnu- linux.config
sudo make ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-buildroot-linux-gnu- -j8
需要Image文件,在/arch/arm64/boot目录下
linux.config内容如下:
CONFIG_SCSI_CONSTANTS=y
CONFIG_SCSI_LOGGING=y
CONFIG_SCSI_SCAN_ASYNC=y
CONFIG_SCSI_VIRTIO=y
CONFIG_ATA=y
CONFIG_NETDEVICES=y
CONFIG_DUMMY=y
CONFIG_MACVLAN=y
CONFIG_VIRTIO_NET=y
CONFIG_NLMON=y
CONFIG_INPUT_EVDEV=y
CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_HW_RANDOM=y
CONFIG_HW_RANDOM_VIRTIO=y
CONFIG_TCG_TPM=y
CONFIG_TCG_TIS=y
CONFIG_DRM=y
CONFIG_DRM_VIRTIO_GPU=y
CONFIG_FB=y
CONFIG_RTC_CLASS=y
CONFIG_RTC_DRV_PL031=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_INPUT=y
CONFIG_VIRTIO_MMIO=y
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
CONFIG_MAILBOX=y
CONFIG_PL320_MBOX=y
CONFIG_ARM_SMMU_V3=y
CONFIG_EXT4_FS=y
CONFIG_FUSE_FS=y
busybox:
sudo make ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-buildroot-linux-gnu- defconfig
sudo make ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-buildroot-linux-gnu- -j8
sudo make install ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-linux-gnu-
在_install目录下:
cd busybox-1_36_0
mkdir -p ~/rootfs
cp _install/* ~/rootfs/ -rf
cd ~/rootfs/
# 创建init文件
rm linuxrc
ln -s bin/busybox init
# 创建控制台设备文件
mkdir dev
mkdir proc
mkdir sys
mkdir config
mkdir debug
sudo mknod -m 600 dev/console c 5 1
sudo mknod dev/tty1 c 4 1
sudo mknod dev/tty2 c 4 2
sudo mknod dev/tty3 c 4 3
sudo mknod dev/tty4 c 4 4
# 创建配置文件目录及文件
mkdir etc
touch etc/inittab
#修改 etc/inittab 的内容如下
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
#创建初始化脚本
mkdir -p etc/init.d/
touch etc/init.d/rcS
chmod +x etc/init.d/rcS
#初始化脚本 rcS 的内容为,注意sysfs,debugfs和configfs的mount
#!/bin/sh
export PATH=/sbin:/bin:/usr/bin:/usr/sbin;
echo "minisystem start..."
mount -t sysfs sysfs /sys
mount -t proc procfs /proc
mount -t debugfs debugfsfs /debug
mount -t configfs configfs /config
mount -o rw,remount /
#制作raw格式的最小根文件系统镜像,生成rootfs.img
find . | cpio -o -H newc | gzip > ../rootfs.img
cd ..
mkimage -A arm64 -T ramdisk -d rootfs.img rootfs.cpio.uboot
uboot启动时需要rootfs.cpio.uboot ,qemu启动只需要rootfs.img
参考链接:https://zhuanlan.zhihu.com/p/626683569
启动
启动方式一:无uboot引导,内核和根文件系统同时引导至内存中启动。(initramfs)
此种方式需要再make menconfig中将BR2_TARGET_ROOTFS_INITRAMFS选中。
在buildroot下,已整理好启动的shell命令start_qemu_aarch64_raw.sh
sudo qemu-system-aarch64 -M virt \
-cpu cortex-a57 -nographic -smp 4 -m 512 \
-kernel output/images/Image \
-append "console=ttyAMA0 kmemleak=on loglevel=8" \
此种方式将根文件系统cpio文件集成至内核中,一起在内存中启动,根文件系统挂载在内存设备上,重启会导致丢失。
此时是initramfs启动方式,使用initramfs,命令行参数将不需要"root="命令。
如果没有选中BR2_TARGET_ROOTFS_INITRAMFS,可以手动以initramfs的方式运行,使用“initrd”指定cpio我文件,内核会自动识别该类型文件并在ram中进行启动。
sudo qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -m 1024 -smp 4 \
-kernel output/images/Image \
-append "console=ttyAMA0 kmemleak=on loglevel=8 " \
-initrd output/images/rootfs.cpio.gz \
-nographic \
参考链接:
https://jgsun.github.io/2018/12/17/qemu-virt-arm64/
https://zhuanlan.zhihu.com/p/426026299
https://blog.csdn.net/xunknown/article/details/124521135
启动方式二:无uboot引导,内核引导至内核启动,根文件系统挂载在本地虚拟存储设备/dev/vda上。
该引导方式需要取消buildroot配置时的Filesystem images —> cpio the root filesystem选项和Filesystem images —>initial RAM filesystem linked into linux kernel选项。此时不会将cpio文件集成至内核文件中。
如果执行过方式一,则需要:
sudo make linux-dirclean
sudo make
若没有编译过则:
sudo make
此时编译出来之后会出现/output/images/下会出现rootfs.ext4文件。
但是最保险的方式时先全局make clean再sudo make,但是比较耗时。
使用以下指令可引导启动:
sudo qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -m 1024 -smp 4 \
-kernel output/images/Image \
-append "noinitrd root=/dev/vda rw console=ttyAMA0 loglevel=8" \
-nographic -drive if=none,file=output/images/rootfs.ext4,id=hd0 \
-device virtio-blk-device,drive=hd0 \
--fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none
参考链接:
https://zhuanlan.zhihu.com/p/626683569
https://blog.csdn.net/xunknown/article/details/124521135
如果没有使用buildroot进行ext4文件系统镜像的生成,也可以选择自己手动生成文件系统的镜像:
执行:
sudo dd if=/dev/zero of=rootfs_ext4.img bs=1M count=1024
sudo mkfs.ext4 rootfs_ext4.img
sudo mkdir -p tmpfs
sudo mount -t ext4 rootfs_ext4.img tmpfs/ -o loop
sudo cp -af ../target/* tmpfs/
sudo umount tmpfs
chmod 777 rootfs_ext4.img
使用rootfs_ext4.img为rootfs的文件源,启动指令如下:
sudo qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -m 1024 -smp 4 \
-kernel output/images/Image \
-append "noinitrd root=/dev/vda rw console=ttyAMA0 loglevel=8" \
-nographic -drive if=none,file=output/images/rootfs_ext4.img,id=hd0 \
-device virtio-blk-device,drive=hd0 \
--fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none
实现重启后根文件系统中内容保存。
启动方式三:uboot引导,内核、设备树以网络方式进行引导。
在编译得到uboot.bin后,需要制作flash.bin才能够烧录至虚拟flash:
sudo dd if=/dev/zero of=flash.bin bs=4096 count=16384
sudo dd if=u-boot.bin of=flash.bin conv=notrunc bs=4096
随后运行buildroot下的uboot_start.sh脚本:
sudo qemu-system-aarch64 -machine virt \
-cpu cortex-a57 \
-m 1G \
-drive file=output/images/flash.bin,format=raw,if=pflash \
-nographic \
-netdev type=tap,id=net0,script=board/qemu/scripts/qemu-ifup_virbr0 \
-device e1000,netdev=net0
qemu-ifup_virbr0是qemu相关的网络配置,自己生成:
#!/bin/sh
run_cmd()
{
echo $1
eval $1
}
run_cmd "sudo ifconfig $1 0.0.0.0 promisc up"
run_cmd "sudo brctl addif virbr0 $1"
run_cmd "brctl show"
#point run在进入uboot后,需要完成tftp引导,由于网络已经DHCP配置好,能够ping通宿主机就可以进行传输。
tftpboot ${kernel_addr_r} zhouwenqi/Image&&tftpboot ${ramdisk_addr_r} zhouwenqi/rootfs.cpio.uboot &&tftpboot ${fdt_addr} zhouwenqi/qemu_arm64.dtb&&setenv bootargs "root=/dev/ram0 rw init=/linuxrc earlycon console=ttyAMA0,115200"&&booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr}
zhouwenqi/Image是内核镜像
zhouwenqi/rootfs.cpio.uboot是根文件系统的uimage(buildroot可以自动生成,选中menuconfig中BR2_TARGET_ROOTFS_CPIO_UIMAGE即可,也可以使用mkimage指令制作)
但是这种方式会卡在内核启动start kernel处,猜测是设备树文件不匹配造成。 或者是boot命令和image内核镜像不对应造成。
制作设备树:
-M virt,dumpdtb=coretex-a57.dtb //qemu启动指令中添加此行
dtc -I dtb -O dts coretex-a57.dtb -o coretex-a57.dts //反编译为dts
cp qemu_arm64.dts ./output/build/linux-5.10.208/arch/arm64/boot/dts/arm/ //复制到内核目录下
vim Makefile //末行添加qemu_arm64.dtb
make ARCH=arm64 CROSS_COMPILE=~/buildroot-2023.11/output/host/opt/ext-toolchain/bin/aarch64-linux-gnu- dtbs //使用内核进行编译
但是还是发现起不来,卡了将近一周,最后发现是qemu的uboot启动方式的问题,在flash中通过uboot.bin镜像进行启动会导致卡在kernel_init()->kernel_init_freeable()->do_one_initcall()->of_platform_default_populate_init()->of_platform_populate()->of_platform_bus_create()->amba_device_add()->amba_device_try_add()函数中,原因未知。
故使用如下启动方式:
sudo qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -m 1024 -smp 4 \
-kernel u-boot \
-nographic -netdev type=tap,id=net0,script=board/qemu/scripts/qemu-ifup_virbr0 -device e1000,netdev=net0
后续的操作和上面的#point run一样。
启动方式四:uboot引导,内核、设备树从虚拟SD卡进行引导。
首先根据https://zhuanlan.zhihu.com/p/626683569中的方式制作了uboot.disk 的SD卡镜像,用于存放内核设备树和根文件系统。
根据网上的大部分资料qemu末尾添加 -sd boot.disk -nographic \即可,但是在实际运行中发现并-sd指令无法识别
sudo qemu-system-aarch64 -machine virt \
-cpu cortex-a57 \
-m 1G \
-drive file=flash.bin,format=raw,if=pflash \
-nographic \
-sd boot.disk -nographic \
根据kvm - qemu to emulate SD-Bus and card - Stack Overflow中引导,采用后种方式
sudo qemu-system-aarch64 -machine virt \
-cpu cortex-a57 \
-m 1G \
-drive file=flash.bin,format=raw,if=pflash \
-nographic \
-device sdhci-pci -device sd-card,drive=mydrive -drive id=mydrive,if=none,format=raw,file=boot.disk
虽然能够启动,sd卡却没有办法识别,后来发现是因为qemu virt没有SD卡设备,这种方式不再讨论。