qemu(4) -- qemu-system-arm使用

1. 前言

参考网上的资料,使用qemu中的vexpress_a9板子,跑一下Linux环境。

2. 源码

工作目录的结构如下。

shell 复制代码
$ tree uboot_linux_busybox/ -L 1
uboot_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行是新增的,否者编译的时候报错。

c 复制代码
1316 # if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
1317 || ctx->process_block == sha1_process_block64_shaNI
1318 # endif

3. 依赖

后期可能要经常修改和编译,就选择直接在wsl上进行编译开发不构建容器了,编译过程中发现还需要安装一些依赖包。

shell 复制代码
# gnutls-dev,编译u-boot时需要
# libmpc-dev,编译内核时需要
sudo apt install gnutls-dev libmpc-dev

4. 编译

  1. 编译脚本文件如下。
shell 复制代码
$ 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编译脚本
  1. 编译输出的目录结构如下。
shell 复制代码
$ tree uboot_linux_busybox/build/vexpress_a9 -L 1
uboot_linux_busybox/build/vexpress_a9 # 编译输出目录
├── busybox  # busybox编译输出目录
├── images   # 存放qemu启动所需文件的目录
├── linux    # linux编译输出目录
├── rootfs   # 根文件系统目录
└── uboot    # u-boot编译输出目录
  1. 执行./build.sh即可进行全部的编译,也可以根据需要仅编译其中的一项。
shell 复制代码
# 进入脚本目录
$ cd uboot_linux_busybox/script/vexpress_a9/
# 开始编译,第一次编译时,u-boot和busybox会弹出menuconfig进行设置
$ ./build.sh

4.1 common.sh

shell 复制代码
#!/bin/bash

# 编译环境所需的全局环境变量
export ARCH=arm
export 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 file
	diff -q "$1" "$2" > /dev/null 2> /dev/null || \
	cp -f "$1" "$2"
}

# 创建编译输出目录
if [ ! -e ${WORKPATH}build/${PRJNAME} ]; then
	mkdir -p ${WORKPATH}build/${PRJNAME}
	cd ${WORKPATH}build/${PRJNAME}
	mkdir -p busybox  images  linux  rootfs  uboot
	cd ${OLDPWD}
	# 执行脚本初始化文件系统的内容
	./private/rootfs.sh ${ROOTFSPATH}
fi

4.2 rootfs.sh

shell 复制代码
#!/bin/bash

# 进入根目录
cd $1

# 创建顶层目录
mkdir -p boot dev etc/init.d mnt proc root tmp var

# 创建字符设备
sudo mknod -m 666 dev/tty1    c 4 1
sudo mknod -m 666 dev/tty2    c 4 2
sudo mknod -m 666 dev/tty3    c 4 3
sudo mknod -m 666 dev/tty4    c 4 4
sudo mknod -m 666 dev/console c 5 1
sudo mknod -m 666 dev/null    c 1 3

# 创建etc目录下文件
cat << EOF > etc/fstab
proc    /proc           proc    defaults        0       0
none    /dev/pts        devpts  mode=0622       0       0
mdev    /dev            ramfs   defaults        0       0
sysfs   /sys            sysfs   defaults        0       0
tmpfs   /dev/shm        tmpfs   defaults        0       0
tmpfs   /dev            tmpfs   defaults        0       0
tmpfs   /mnt            tmpfs   defaults        0       0
var     /dev            tmpfs   defaults        0       0
ramfs   /dev            ramfs   defaults        0       0
EOF

cat << EOF > etc/hosts
127.0.0.1 localhost vexpress
::1       ip6-localhost ip6-loopback
fe00::0   ip6-localnet
EOF

cat << EOF > etc/profile
export PS1='[\u@\h \W]\$ '
EOF

cat << EOF > etc/passwd
root:CkE1d99EEQf9U:0:0:root:/root:/bin/sh
EOF

cat << EOF > etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
EOF

cat << EOF > etc/group
root:x:0:root
EOF

cat << EOF > etc/init.d/rcS
#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
export 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 vexpress

echo "-----------------------------------"
echo "*****welcome to vexpress board*****"
echo "-----------------------------------"
EOF

# 设置rcS文件可执行权限
chmod +x etc/init.d/rcS

4.3 uboot.sh

shell 复制代码
#!/bin/bash

# 加载环境变量和通用函数
. private/common.sh

# 进入u-boot源码目录
cd ${WORKPATH}u-boot-v2025.04-rc5

# $1不为空则执行相应的命令,执行后退出
if [ ! -z "$1" ]; then
	make O=${OUTPATH}uboot $1
	exit $?
fi

# "O="指定编译输出目录,使用vexpress_ca9x4板子的配置,如果不存在.config则执行
if [ ! -e ${OUTPATH}uboot/.config ]; then
	make 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 - 0x60500000
	make O=${OUTPATH}uboot menuconfig
fi

# about 1min
make 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

shell 复制代码
#!/bin/bash

# 加载环境变量和通用函数
. private/common.sh

# 进入linux源码目录
cd ${WORKPATH}linux-5.15.180

# $1不为空则执行相应的命令,执行后退出
if [ ! -z "$1" ]; then
	make O=${OUTPATH}linux $1
	exit $?
fi

# 使用vexpress的配置,如果不存在.config则执行
if [ ! -e ${OUTPATH}linux/.config ]; then
	make O=${OUTPATH}linux vexpress_defconfig
fi

# 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

# 将内核镜像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.dtb
func_cp ${OUTPATH}linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb ${OUTPATH}rootfs/boot/vexpress-v2p-ca9.dtb

4.5 busybox.sh

shell 复制代码
#!/bin/bash

# 加载环境变量和通用函数
. private/common.sh

# 进入busybox源码目录
cd ${WORKPATH}busybox-1.37.0

# $1不为空则执行相应的命令,执行后退出
if [ ! -z "$1" ]; then
	make O=${OUTPATH}busybox $1
	exit $?
fi

# 使用默认的配置,如果不存在.config则执行
if [ ! -e ${OUTPATH}busybox/.config ];then
	make 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 menuconfig
fi

# about 1min,此处install会先编译后安装
make O=${OUTPATH}busybox install -j${JOBS}

4.6 mmc.sh

shell 复制代码
#!/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 temp
fi

# 将文件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

shell 复制代码
#!/bin/bash

# 编译uboot
./uboot.sh
# 编译linux
./linux.sh
# 编译busybox
./busybox.sh
# 构建mmc镜像
./mmc.sh

5. 启动

编辑qemu.sh脚本内容如下。

shell 复制代码
#!/bin/bash

# 加载环境变量和通用函数
. private/common.sh

# $1指定启动方式
# linux-gui -- 直接启动linux,使能qemu的gui
# uboot ------ 启动u-boot,不使用gui
# 其他 ------- 直接启动linux,不使用gui
case $1 in
	linux-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

shell 复制代码
# 进入脚本目录
$ 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 ~]$ ls
bin         etc         mnt         sbin        var
boot        linuxrc     proc        tmp
dev         lost+found  root        usr

5.2 有gui

shell 复制代码
# 进入脚本目录
$ 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)

相关推荐
2035去旅行7 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希1238 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡8 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.12 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道12 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.15 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落17 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨17 小时前
WSL2的安装与使用
linux·wsl
微小的鱼17 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng17 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器