qemu(4) -- qemu-system-arm使用
1. 前言
参考网上的资料,使用qemu中的vexpress_a9
板子,跑一下Linux环境。
2. 源码
工作目录的结构如下。
$ tree uboot_linux_busybox/ -L 1uboot_linux_busybox/ # 工作目录├── build # 编译输出目录├── busybox-1.37.0 # busybox源码目录├── linux-5.15.180 # linux源码目录├── script # 辅助脚本目录:根文件系统制作、qemu启动、编译├── source # 源码压缩包目录└── u-boot-v2025.04-rc5 # u-boot源码目录
2.1 u-boot
可以到U-Boot官网下载对应的源码,我下载的是u-boot-2025.04-rc5.tar.gz,大约24MB。
2.2 linux
可以到The Linux Kernel Archives官网下载对应的源码,我下载的是linux-5.15.180.tar.xz,大约121MB。最新的是linux-6.14.4.tar.xz,qemu开启lcd时,6.14
提示Guest has not initialized the display (yet)
,不清楚为什么,所以就选择使用5.15
的版本了。
2.3 busybox
可以到BUSYBOX官网下载对应的源码,我下载的是busybox-1.37.0.tar.bz2,大约2.4MB。1.37.0
版本有些bug,busybox-1.37.0/libbb/hash_md5_sha.c
文件需要进行以下修改,其中1316
和1318
行是新增的,否者编译的时候报错。
1316 # if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))1317 || ctx->process_block == sha1_process_block64_shaNI1318 # endif
3. 依赖
后期可能要经常修改和编译,就选择直接在wsl上进行编译开发不构建容器了,编译过程中发现还需要安装一些依赖包。
# gnutls-dev,编译u-boot时需要# libmpc-dev,编译内核时需要sudo apt install gnutls-dev libmpc-dev
4. 编译
- 编译脚本文件如下。
$ tree uboot_linux_busybox/script/vexpress_a9/uboot_linux_busybox/script/vexpress_a9/ # 编译脚本所在目录├── build.sh # 编译全部├── busybox.sh # 编译busybox├── linux.sh # 编译linux├── mmc.sh # 制作mmc镜像├── private # 私有脚本,被其他脚本引用│ ├── common.sh # 定义环境变量和通用函数,不需要可执行权限│ ├── net_up.sh # qemu网卡加载脚本│ └── rootfs.sh # 创建根文件系统脚本├── qemu.sh # qemu启动脚本└── uboot.sh # u-boot编译脚本
- 编译输出的目录结构如下。
$ tree uboot_linux_busybox/build/vexpress_a9 -L 1uboot_linux_busybox/build/vexpress_a9 # 编译输出目录├── busybox # busybox编译输出目录├── images # 存放qemu启动所需文件的目录├── linux # linux编译输出目录├── rootfs # 根文件系统目录└── uboot # u-boot编译输出目录
- 执行
./build.sh
即可进行全部的编译,也可以根据需要仅编译其中的一项。
# 进入脚本目录$ cd uboot_linux_busybox/script/vexpress_a9/# 开始编译,第一次编译时,u-boot和busybox会弹出menuconfig进行设置$ ./build.sh
4.1 common.sh
#!/bin/bash# 编译环境所需的全局环境变量export ARCH=armexport CROSS_COMPILE=arm-none-linux-gnueabihf-# 项目名称,设置为当前目录的名称PRJNAME=$(basename $(pwd))# 获取主机的cpu核心数,后面命令行中用于设置make的线程数JOBS=$(nproc)# 工作目录路径,当前目录是script/${PRJNAME}/WORKPATH=../../# 根目录路径,相对当前目录ROOTFSPATH=${WORKPATH}build/${PRJNAME}/rootfs/# 镜像路径,相对当前目录IMAGESPATH=${WORKPATH}build/${PRJNAME}/images/# 编译输出路径,相对于源码目录OUTPATH=../build/${PRJNAME}/# 先比较两个文件是否一致,如果不同则拷贝func_cp(){# $1: src file# $2: dst filediff -q \"$1\" \"$2\" > /dev/null 2> /dev/null || \\cp -f \"$1\" \"$2\"}# 创建编译输出目录if [ ! -e ${WORKPATH}build/${PRJNAME} ]; thenmkdir -p ${WORKPATH}build/${PRJNAME}cd ${WORKPATH}build/${PRJNAME}mkdir -p busybox images linux rootfs ubootcd ${OLDPWD}# 执行脚本初始化文件系统的内容./private/rootfs.sh ${ROOTFSPATH}fi
4.2 rootfs.sh
#!/bin/bash# 进入根目录cd $1# 创建顶层目录mkdir -p boot dev etc/init.d mnt proc root tmp var# 创建字符设备sudo mknod -m 666 dev/tty1 c 4 1sudo mknod -m 666 dev/tty2 c 4 2sudo mknod -m 666 dev/tty3 c 4 3sudo mknod -m 666 dev/tty4 c 4 4sudo mknod -m 666 dev/console c 5 1sudo mknod -m 666 dev/null c 1 3# 创建etc目录下文件cat << EOF > etc/fstabproc /proc proc defaults 0 0none /dev/pts devpts mode=0622 0 0mdev /dev ramfs defaults 0 0sysfs /sys sysfs defaults 0 0tmpfs /dev/shm tmpfs defaults 0 0tmpfs /dev tmpfs defaults 0 0tmpfs /mnt tmpfs defaults 0 0var /dev tmpfs defaults 0 0ramfs /dev ramfs defaults 0 0EOFcat << EOF > etc/hosts127.0.0.1 localhost vexpress::1 ip6-localhost ip6-loopbackfe00::0 ip6-localnetEOFcat << EOF > etc/profileexport PS1=\'[\\u@\\h \\W]\\$ \'EOFcat << EOF > etc/passwdroot:CkE1d99EEQf9U:0:0:root:/root:/bin/shEOFcat << EOF > etc/inittab::sysinit:/etc/init.d/rcS::askfirst:-/bin/sh::askfirst:-/bin/sh::ctrlaltdel:/bin/umount -a -rEOFcat << EOF > etc/grouproot:x:0:rootEOFcat << EOF > etc/init.d/rcS#!/bin/shPATH=/bin:/sbin:/usr/bin:/usr/sbinexport LD_LIBRARY_PATH=/lib:/usr/lib/bin/mount -n -t ramfs ramfs /var/bin/mount -n -t ramfs ramfs /tmp/bin/mount -n -t sysfs none /sys/bin/mount -n -t ramfs none /dev/bin/mkdir /var/tmp/bin/mkdir /var/modules/bin/mkdir /var/run/bin/mkdir /var/log/bin/mkdir -p /dev/pts/bin/mkdir -p /dev/shm/sbin/mdev -s/bin/mount -a/sbin/ifconfig lo up/sbin/ifconfig eth0 192.168.101.201 netmask 255.255.255.0 broadcast 192.168.101.1 up/usr/sbin/telnetd/bin/hostname vexpressecho \"-----------------------------------\"echo \"*****welcome to vexpress board*****\"echo \"-----------------------------------\"EOF# 设置rcS文件可执行权限chmod +x etc/init.d/rcS
4.3 uboot.sh
#!/bin/bash# 加载环境变量和通用函数. private/common.sh# 进入u-boot源码目录cd ${WORKPATH}u-boot-v2025.04-rc5# $1不为空则执行相应的命令,执行后退出if [ ! -z \"$1\" ]; thenmake O=${OUTPATH}uboot $1exit $?fi# “O=”指定编译输出目录,使用vexpress_ca9x4板子的配置,如果不存在.config则执行if [ ! -e ${OUTPATH}uboot/.config ]; thenmake O=${OUTPATH}uboot vexpress_ca9x4_defconfig# 编辑Boot options --->,配置启动命令,注意新配置的命令中引号需要使用斜杠转义# (run distro_bootcmd; run bootflash) bootcmd value =》ext4load mmc 0 0x60003000 boot/uImage;ext4load mmc 0 0x60500000 boot/vexpress-v2p-ca9.dtb;setenv bootargs \\\"root=/dev/mmcblk0 rw console=ttyAMA0\\\";bootm 0x60003000 - 0x60500000make O=${OUTPATH}uboot menuconfigfi# about 1minmake O=${OUTPATH}uboot -j${JOBS}# 将编译出的mkimage程序拷贝到PATH目录中,后面linux制作uImage时会用到它# ~/bins已经被我添加到PATH环境变量中了# 如果~/bins/mkimage不存在就执行,也即第一次编译时会执行[ -e ~/bins/mkimage ] || cp ${OUTPATH}/uboot/tools/mkimage ~/bins# 将u-boot的镜像拷贝到images目录,之后qemu启动时使用func_cp ${OUTPATH}/uboot/u-boot ${OUTPATH}images/u-boot
4.4 linux.sh
#!/bin/bash# 加载环境变量和通用函数. private/common.sh# 进入linux源码目录cd ${WORKPATH}linux-5.15.180# $1不为空则执行相应的命令,执行后退出if [ ! -z \"$1\" ]; thenmake O=${OUTPATH}linux $1exit $?fi# 使用vexpress的配置,如果不存在.config则执行if [ ! -e ${OUTPATH}linux/.config ]; thenmake O=${OUTPATH}linux vexpress_defconfigfi# about 6min,编译kernel镜像,zImage表示压缩镜像make O=${OUTPATH}linux zImage -j${JOBS}# 编译设备树make O=${OUTPATH}linux dtbs# 编译模块文件make O=${OUTPATH}linux modules# 使用u-boot-2025.04-rc5编译出的mkimage程序制作uImage,LOADADDR指定uImage中的头信息make O=${OUTPATH}linux uImage LOADADDR=0x60003000# 将dtc工具拷贝到~/bins中,方便以后使用,~/bins已被我添加到PATH中[ -e ~/bins/dtc ] || cp ${OUTPATH}/linux/scripts/dtc/dtc ~/bins# 将内核镜像zImage拷贝到images目录func_cp ${OUTPATH}linux/arch/arm/boot/zImage ${OUTPATH}images/zImage# 将内核镜像uImage拷贝到boot目录,之后通过u-boot加载内核时,从boot目录读取该文件func_cp ${OUTPATH}linux/arch/arm/boot/uImage ${OUTPATH}rootfs/boot/uImage# 将设备树拷贝到images和boot目录,前者用于直接启动,后者用于u-boot启动func_cp ${OUTPATH}linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb ${OUTPATH}images/vexpress-v2p-ca9.dtbfunc_cp ${OUTPATH}linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb ${OUTPATH}rootfs/boot/vexpress-v2p-ca9.dtb
4.5 busybox.sh
#!/bin/bash# 加载环境变量和通用函数. private/common.sh# 进入busybox源码目录cd ${WORKPATH}busybox-1.37.0# $1不为空则执行相应的命令,执行后退出if [ ! -z \"$1\" ]; thenmake O=${OUTPATH}busybox $1exit $?fi# 使用默认的配置,如果不存在.config则执行if [ ! -e ${OUTPATH}busybox/.config ];thenmake O=${OUTPATH}busybox defconfig# 执行menconfig修改一些配置项,修改的内容都在“Settings --->”下面# [*] Build static binary (no shared libs),表示使用静态链接,不使用动态库# (./_install)Destination path for \'make install\' => ../rootfs_${PRJNAME},表示修改安装目录,此处的目录是相对于编译目录而言的# [*] vi-style line editing commands (New),启用vi命令make O=${OUTPATH}busybox menuconfigfi# about 1min,此处install会先编译后安装make O=${OUTPATH}busybox install -j${JOBS}
4.6 mmc.sh
#!/bin/bash# 加载环境变量和通用函数. private/common.sh# 进入镜像目录cd ${IMAGESPATH}if [ ! -e mmc0.img ]; then# 创建一个32MB的文件rootfs.ext4,使用0填充dd if=/dev/zero of=mmc0.img bs=1M count=32# 将mmc0.img文件格式化成ext4文件系统mkfs.ext4 mmc0.img# 创建一个临时目录作为挂载点mkdir tempfi# 将文件mmc0.img挂载到temp/目录上,mount会自动识别文件系统格式,需要管理员权限,-o loop表示挂载的是一个文件虚拟的块设备sudo mount mmc0.img temp/ -o loop# 将根目中的文件拷贝到temp/目录中sudo cp ../rootfs/* temp/ -r# 取消挂载,此时mmc0.img就是一个根文件系统镜像了,其文件系统格式为ext4,且拥有../rootfs/目录下的所有文件sudo umount temp
4.7 build.sh
#!/bin/bash# 编译uboot./uboot.sh# 编译linux./linux.sh# 编译busybox./busybox.sh# 构建mmc镜像./mmc.sh
5. 启动
编辑qemu.sh
脚本内容如下。
#!/bin/bash# 加载环境变量和通用函数. private/common.sh# $1指定启动方式# linux-gui -- 直接启动linux,使能qemu的gui# uboot ------ 启动u-boot,不使用gui# 其他 ------- 直接启动linux,不使用guicase $1 inlinux-gui)# 因为创建了网络,所以需要管理员权限,-E表示使用当前用户的环境变量sudo -E qemu-system-arm \\ -M vexpress-a9 \\ -m 512M \\ -sd ${IMAGESPATH}mmc0.img \\ -nic tap,ifname=tap0,script=private/net_up.sh \\ -kernel ${IMAGESPATH}zImage \\ -dtb ${IMAGESPATH}vexpress-v2p-ca9.dtb \\ -append \"root=/dev/mmcblk0 rw console=tty0\";;uboot)sudo -E qemu-system-arm \\ -M vexpress-a9 \\ -m 512M \\ -sd ${IMAGESPATH}mmc0.img \\ -nic tap,ifname=tap0,script=private/net_up.sh \\ -nographic \\ -kernel ${IMAGESPATH}u-boot;;*)sudo -E qemu-system-arm \\ -M vexpress-a9 \\ -m 512M \\ -sd ${IMAGESPATH}mmc0.img \\ -nic tap,ifname=tap0,script=private/net_up.sh \\ -nographic \\ -kernel ${IMAGESPATH}zImage \\ -dtb ${IMAGESPATH}vexpress-v2p-ca9.dtb \\ -append \"root=/dev/mmcblk0 rw console=ttyAMA0\";;esac
5.1 无gui
# 进入脚本目录$ cd uboot_linux_busybox/script/vexpress_a9/# 无窗口启动,启动日志会直接输出到控制台,先按下CTRL+a再按下x可以退出qemu$ ./qemu.sh....(此处省略许多日志,日志中ALSA等信息是可声卡有关的错误,暂时可以先不理会)-----------------------------------*****welcome to vexpress board*****-----------------------------------Please press Enter to activate this console.(此处按下回车即可登录到控制台)# 进入linux控制台[root@vexpress ~]$ lsbin etc mnt sbin varboot linuxrc proc tmpdev lost+found root usr
5.2 有gui
# 进入脚本目录$ cd uboot_linux_busybox/script/vexpress_a9/# 有窗口启动,可以看到qemu弹出了gui窗口,等待一段时间会在窗口上出现小企鹅,启动日志会输出到gui窗口$ ./qemu.sh linux-gui
有窗口启动时,鼠标单击窗口即可进行输入交互,我在两个电脑上运行过,有个电脑gui窗口交互有些问题,输入的命令总是收的不全,另一台电脑正常。鼠标点进窗口后就不能点击qemu窗口的菜单栏了,根据窗口标题提示,需要先按下Ctrl+Alt+g,表示将鼠标的焦点退出控制台,然后才能点击窗口的菜单栏,通过菜单栏的【Machine】》【Quit】可以退出qemu。
上一篇:qemu(3) – qemu-arm使用
下一篇:qemu(4) – qemu-system-arm使用
目录:全部文章合集
参考
QEMU教程
U-Boot 文档
Arm Versatile Express boards (vexpress-a9, vexpress-a15)