使用qemu模拟一块开发板
如何使用 QEMU 模拟一块开发板?
前言
事实上,qemu可以用来模拟各种硬件,并且 qemu
官方也提供了一些已经配置好的硬件开发板。所以,我们可以直接使用这些配置好的开发板来进行模拟。同时也可以自己配置自己的独特的开发板。
首先,我们先说如何使用 qemu
官方提供的配置好的开发板。
关于QEMU的杂项
qemu/qemu-kvm/qemu-system-x86_64/qemu-x86_64命令的区别
1
在老版本中有单独的qemu-kvm模块存在,结合qemu一起做虚拟机工作。在后续新版本中,已经将qemu-kvm模块完全合并到qemu中去。因此当需要使用kvm特性时候,只需要qemu-system-x86_64 启动命令中增加参数 –enable-kvm参数使能即可。
2
I asked the mailing list, here’s what I got:
qemu-arch like /usr/local/bin/qemu-x86_64 is for running a program of that arch on the host machine of what ever arch, but not a virtual machine
qemu-system-arch like /usr/local/bin/qemu-system-x86_64 is for running a system of that arch on the host machine to enable kvm support, qemu parameter -enable-kvm is needed, libvirt should have taken care of this if right xml is configuredThanks Jakob for the answer in the mailing list.
- (http://serverfault.com/questions/767212/difference-between-qemu-kvm-qemu-system-x86-64-qemu-x86-64)
意思是,类似qemu-x86_64这种命令是运行某种架构的程序的,qemu-system-x86_64是运行某种架构系统的(虚拟机),如果需要kvm支持,需要加上参数 -enable-kvm, 如果使用libvirt可以配置相应的xml来实现kvm支持。
3
Actually, qemu-kvm is a simple wrapper of qemu-system-x86_64.
In my x86_64 gentoo OS, the content of qemu-kvm script is
#!/bin/sh$ exec /usr/bin/qemu-system-x86_64 –enable-kvm “$@”
意思是,gentoo里面的qemu-kvm就是运行“qemu-system-x86_64 –enable-kvm”, 是一个脚本。
4
The KVM project used to maintain a fork of QEMU called qemu-kvm. All feature differences have been merged into QEMU upstream and the development of the fork suspended.
To use KVM pass –enable-kvm to QEMU.
官方文档,意思是KVM项目以前fork了一个QEMU的镜像并称为qemu-kvm。现在所有不同的特征都被merge到QEMU的upstream了,称为qemu-kvm的fork版本暂停开发了。
暂时的结论
现在的qemu已经整合qemu-kvm,不再有qemu-kvm的说法了。一般创建x86的虚拟机需要用到qemu-system-x86_64这个命令,并需要加上–enable-kvm来支持kvm加速。有些Linux发行版的qemu-kvm命令仅仅是qemu-system-x86_64的软链接或者简单包装。
准备
首先,不管是编译什么软件,在 Linux 中都需要使用编译器等等一系列的工具,我们称之为工具链。由于我们需要编译不同于我们 PC 架构的软件,所以需要安装交叉编译器。
安装各种架构的工具链是一件麻烦事,但是好在有一些开源的工具可以帮助我们安装。
我们首先了解一下相关工具的命令规则:
GCC 的命名规则为:arch [-vendor] [-os] [-(gnu)eabi]-gcc
比如 arm-linux-gnueabi-gcc
, arm-none-eabi-gcc
, aarch64-linux-gnu-gcc
带 [] 的是可选部分。
arch:芯片架构,比如 32 位的 Arm 架构对应的 arch 为 arm,64 位的 Arm 架构对应的 arch 为 aarch64。
vendor :工具链提供商,大部分工具链名字里面都没有包含这部分。
os :编译出来的可执行文件(目标文件)针对的操作系统,比如 Linux。
其中 arm-none-eabi-gcc
一般适用用于 Arm Cortex-M/Cortex-R
平台,它使用的是 newlib
库。
arm-linux-gnueabi-gcc
和 aarch64-linux-gnu-gcc
适用于 Arm Cortex-A
系列芯片,前者针对 32
位芯片,后者针对 64
位芯片,它使用的是 glibc
库。可以用来编译 u-boot、linux kernel 以及应用程序。
另外需要补充一点的是,32
位的 Arm 和 64
位的 Arm,它们的指令集是不同的,所以需要使用不同的工具链。当然,Arm64
为了保证前向兼容,提供了一个 32
位的兼容模式,所以我们用 arm-linux-gnueabi-gcc
编译的应用程序也是可以直接在Arm64 的系统上运行的,但是 Linux Kernel
和 U-Boot
就不行,除非你提前把 CPU
切换到 32
位模式。曾经有个项目使用了一颗四核的 Arm64 芯片,但是内存只有64M,为了节省空间,在 CPU 运行到 U-Boot
之前,我们就把它切到了 32 位模式,后面的 U-Boot
、Linux Kernel
,应用全部都用 32
位编译,加上 Thumb
指令集,节省了不少空间。
ARM平台
现在 Arm 平台上用的最广泛的工具链是 Linaro 发布的,大家可以到 Linaro 官网下载,地址如下:
在这个平台上,可以使用 linaro 提供的工具链。
同时,我们也可以不使用这个工具链,自己编译工具链。
安装交叉编译器
1 |
|
如果想要卸载:
1 |
|
如果提示缺少相对应的安装库则:
1 |
|
使用官方的配置开发板
qemu 官方是提供了一些已经配置好的开发板,我们可以直接使用这些配置好的开发板来进行模拟。但是, qemu 也支持自定义的开发板,我们可以自己配置自己的独特的开发板。这个部分我们之后再说。
使用命令:
1 |
|
这个查看所有支持的开发板。
准备好qemu
首先,我们需要安装 qemu
软件。
1 |
|
准备好引导文件——Uboot
需要注意的是,如果直接使用 qemu 运行 Linux 的话其实是不需要 uboot 的的。这是因为其实 qemu 自带了一个引导程序,可以直接引导 Linux 内核。也就是说,如果直接使用 qemu 运行 Linux,我们不需要安装 u-boot
。也是可以执行的:
1 |
|
从上面这个命令中可以看出来,这里是直接使用 zImage
作为内核,而不是使用的 uImage
作为内核。
如果是直接再电脑上使用 qemu 来运行 Linux,我们可以不用安装 uboot 。但是,对于嵌入式环境下,我们还是需要安装 u-boot
的。因为qemu自带bootloader功能,可以直接引导内核,有点类似于自带BIOS系统的电脑主板。但是嵌入式设备通常是没有这样的条件的,所以我们如果真的要用QEMU完成仿真工作的话,还是得把u-boot加进来。
一般来说,我们使用 u-boot
作为引导程序,使用 u-boot
引导 linux
内核的时候是需要内核的。uboot
的作用主要是引导,引导 kernel、rootfs 等等。
首先从官方网站下载 u-boot
软件包,然后解压:
1 |
|
然后进行配置:
1 |
|
编译:
1 |
|
编译完毕之后,在当前目录下会生成u-boot
文件,这个文件可以使用以下命令启动:
1 |
|
这里在准备好 uboot 之后,我们可以使用 qemu 进行测试
1 |
|
这里可以使用 qemu 来研究 uboot 的启动流程,可以帮助我们理解 uboot 的工作原理。
此时由于缺少 linux
内核,所以无法启动系统,我们需要准备好 linux
内核。
准备好内核——Linux Kernel
首先我们再 GitHub
上下载 linux
内核源码:
1 |
|
配置、编译:
1 |
|
由于我们使用 Uboot 进行引导,所以需要 uImage 作为内核,而不是 zImage。所以需要将 Linux 内核修改为 uImage 格式。二者有什么区别:** uImage 是带有 长度为 0x40 长的信息头的内核镜像,而 zImage 是没有信息头的内核镜像**。
1 |
|
制作 uImage 注意。uboot 有单独的工具生成 uImage,但是 Linux 内核也会自带工具用以从 zImage 来生成 uImage 。但是好像不是所有的 Linux 版本都自带这个工具。
所以我们可以使用 uboot 自带的工具来生成 uImage:
1 |
|
使用 uboot 引导 Linux内核时需要注意,不是直接使用 qemu 命令中的 -kernel uImage
,就可以了。而是要使用其他方法:
- 将 uImage 放到 SD 卡中,并制作启动脚本。这里还可以将根文件系统也放在 SD 卡中
- 使用 USB 启动,并制作启动脚本。
- 使用网络启动,并制作启动脚本。
将内核和根文件系统放到 SD 卡中
介绍一下 parted
命令:
parted
是一个功能强大的命令行分区管理工具,用于磁盘分区的创建、调整大小、移动、删除和文件系统格式化等操作。它支持多种类型的磁盘和分区表格式,包括 MBR(主引导记录)和 GPT(全局唯一标识磁盘分区表)。通常,parted
提供了一个交互式的命令行界面,你可以在其中输入命令并执行。但是,当需要自动化任务或者在脚本中使用 parted
时,交互式界面就不太适用了。这时,可以使用 --script
参数来指示 parted
执行一系列的命令,这些命令在 --script
之后直接写入,没有交互提示。
如果不使用 --script
参数,parted
会进入一个命令行模式,在该模式下,你可以输入命令,但是不能使用交互式命令。下面有一些常用的命令:
1 |
|
首先,我们制作一个 SD 卡:
1 |
|
总结一下,上面的步骤包括:
- 创建一个 64M 的空文件 SD,并格式化为 MS-DOS 分区表。
- 创建两个主分区,第一个分区为 FAT32 文件系统,第二个分区为 ext4 文件系统。
- 建立映射,并格式化两个分区。
- 拷贝内核和根文件系统到两个分区。
- 卸载两个分区。
现在,我们已经完成了 SD 卡的制作,下面我们就可以使用 qemu 启动 Linux 系统了。
1 |
|
启动下面的这几个命令是在 uboot 启动之后的命令,也就是说,这些命令都是 uboot 自带的。
列出 MMC 卡内容(如果使用 MMC 作为存储设备):
fatls mmc 0:1
从 MMC 卡加载内核映像:
fatload mmc 0:1 60003000 uImage
从 MMC 卡加载设备树:
fatload mmc 0:1 60500000 vexpress-v2p-ca9.dtb
设置启动参数:
setenv bootargs 'console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw earlyprintk rootfstype=ext4'
启动 Linux 内核:
bootm 60003000 60500000
在这些步骤中,60003000 是内核映像加载到的内存地址,60500000 是设备树加载到的内存地址。这些地址可能需要根据你的系统进行调整。
使用网络启动
通过网络中启动主要使用利用了 tftp
网络
通过网桥,将OS镜像(uImage),通过网络协议下载到U-Boot中,从而通过U-Boot启动OS:
首先需要安装好一些前置的软件包:
1 |
|
这个地方有两种方法可以实现网络启动:
- 一个是手动配置参数。
- 第二个是直接修改 uboot 的源代码
手动配置参数
这种方法不便于多次使用,而且每次都需要手动配置参数,比较麻烦。
首先第一步是要配置好 tftp 服务。
1
2
3
4
5
6
7
8
9# 设置tftp配置路径文件
sudo vim /etc/default/tftpd-hpa
# 将下面的内容复制近来
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/thinks2/ProgramProject/qemu_study/tftpboot" # 该路径即为tftp可以访问到的路径
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
# 重启tftp服务
sudo /etc/init.d/tftpd-hpa restart第二步需要创建好 tftp 目录,然后将内核、设备树、根文件系统放在这个文件夹中。
1
2
3
4
5
6cd ..
mkdir tftpboot
cd tftpboot
cp ../u-boot-2022.10/u-boot ./
cp ../linux-4.19.269/arch/arm/boot/uImage ./
cp ../linux-4.19.269/arch/arm/boot/dts/vexpress-v2p-ca9.dtb ./第三步配置网络信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21ifconfig # 查看网卡信息
# 修改/etc/netplan/01-network-manager-all.yaml的信息配置:
sudo vim /etc/netplan/01-network-manager-all.yaml
# 将下面的信息添加到其中去
network:
version: 2
renderer: networkd
ethernets:
ens37: #这里设置的是你还需要上网的网卡, ifconfig查看
dhcp4: yes
ens38: #这里设置的是br0桥接到的网卡
dhcp4: no
bridges:
br0: #这里设置的是br0网桥
dhcp4: yes
interfaces:
- ens37 #声明br0网桥接入的网卡是ens37
sudo netplan apply #使得上面的配置生效
ifconfig #重新查看网卡信息第四步需要配置
/etc/qemu-ifdown
文件1
2
3
4
5
6
7
8
9
10
11
12sudo vim /etc/qemu-ifdown
# 将下面的内容添加到文件中
#! /bin/sh
# Script to shut down a network (tap) device for qemu.
# Initially this script is empty, but you can configure,
# for example, accounting info here.
echo sudo brctl delif br0 $1
sudo brctl delif br0 $1
echo sudo tunctl -d $1
sudo tunctl -d $1
echo brctl show
brctl show第五步配置
/etc/qemu-ifup
文件1
2
3
4
5
6
7
8
9
10
11
12sudo vim /etc/qemu-ifup
# 将下面的内容添加到文件中
#!/bin/sh
echo sudo tunctl -u $(id -un) -t $1
sudo tunctl -u $(id -un) -t $1
echo sudo ifconfig $1 0.0.0.0 promisc up
sudo ifconfig $1 0.0.0.0 promisc up
echo sudo brctl addif br0 $1
sudo brctl addif br0 $1
echo brctl show
brctl show
sudo ifconfig br0 192.168.33.145 # 这里设置的是网桥br0的地址回到 tftpboot 目录下,创建一个脚本文件,方便下次使用
1
2
3
4
5
6
7
8
9
10
11
12
13cd ~/tftpboot
vim ./start.sh
#添加下面的内容
#!/bin/sh
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ./u-boot-2022.10/u-boot \
-nographic \
-append "root=/dev/sda1 console=ttyAMA0" \
-sd rootfs.ext3 \运行上面的 start.sh 脚本进入 uboot 启动流程,此时再次对 uboot 进行配置
1
2
3
4
5
6setenv ipaddr 192.168.33.144 # 设置u-boot这边的地址(和br0同一网段即可)
setenv serverip 192.168.33.145 # 设置服务器地址(br0网桥的地址)
tftp 0x60003000 uImage # 从tftp下载uImage
tftp 0x60500000 vexpress-v2p-ca9.dtb # 从tftp下载设备树
setenv bootargs "root=/dev/mmcblk0 rw console=ttyAMA0" # 设置根文件系统挂载位置、权限、控制台设备
bootm 0x60003000 - 0x60500000
至此,已经完成了所有的步骤,下面讲第二种方法,即直接修改 uboot 的源代码。
直接修改 uboot 的源代码
这种方法比较简单,不需要配置额外的脚本文件,直接修改 uboot 的源代码即可。
1 |
|
参考资料——网络启动
- https://www.jianshu.com/p/8619a6739040
- https://www.zhaixue.cc/qemu/qemu-u-boot.html
- https://blog.csdn.net/nature_07/article/details/128607903
准备好 rootfs
Linux发行版的根文件系统很复杂,而我们这里用到的根文件系统很简单,我们要制作的根文件系统 = busybox(包含基础的Linux命令) + 运行库 + 几个字符设备。 根文件系统依赖于每个开发板支持的存储设备,可以放到Nor Flash上,也可以放到SD卡,甚至外部磁盘上。最关键的一点是你要清楚知道开发板有什么存储设备。
下面,我们来制作一个简单的根文件系统。
先在Ubuntu主机环境下,形成目录结构,里面存放的文件和目录与单板上运行所需要的目录结构完全一样,然后再打包成镜像(在开发板看来就是SD卡),这个临时的目录结构称为根目录。**这里其实还有第二中方法,即使用网络进行
下载 busybox
1
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
busybox 编译
1
2
3
4
5
6
7
8
9
10
11tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
make defconfig
#make menuconfig
# 勾选 Settings-> [*] Build static binary (no shared libs)
# 如果勾选了上面的设置的话,编译出来的 busybox 就不会依赖动态链接库,可以减少空间占用。并且可以省略下面的制作根文件系统的时候的:
# cp /usr/lib/arm-linux-gnueabi/lib* rootfs/lib/ 这个步骤可以省略。
make CROSS_COMPILE=arm-linux-gnueabi-
make install CROSS_COMPILE=arm-linux-gnueabi- -j8如果使用
menuconfig
进行配置的话,可以看到有很多选项,我们可以根据自己的需求进行选择:1
2
3
4
5
6(1) 设置一下编辑器环境,方便操作:
Settings —-> [*] vi-style line editing commands (New)
(2) 设置一下安装路径,避免错误安装在根目录
Settings —-> Destination path for ‘make install’通过
make install
命令把生成的根文件创建在顶层目录(busybox-1.32.1根目录)_install
下,如果是通过make defconfig O=…/output
导出配置的,则在相应的output目录下进入_install
目录这个编译的地方还有第二种方法可以使用,即直接更改
Makefile
文件,再Makefile
中找到ARCH
和CROSS_COMPILE
变量,修改成如下内容:1
2ARCH ?= arm
CROSS_COMPILE = arm-linux-gnueabi-至此,我们已经准备好了 busybox ,下面需要的是制作一个根目录结构。
创建根目录结构
1
mkdir -p rootfs/{dev,etc/init.d,lib}mkdir rootfs
将 busybox 复制到根目录下:
1
cp busybox-1.36.1/_install/* -r rootfs/
从工具链中拷贝运行库到lib目录下:
1
cp /usr/lib/arm-linux-gnueabi/lib* rootfs/lib/
创建4个tty端终设备:
1
2
3
4sudo 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
至此,我们已经制作好了一个简单的根文件系统,里面包含了 busybox、运行库、4个字符设备。下面,我们需要制作根文件系统镜像文件。
将上面的根文件打包成镜像文件
生成512M大小的磁盘镜像
1
qemu-img create -f raw rootfs.ext3 512M
格式化成ext3文件系统
1
2
3mkfs -t ext3 ./rootfs.ext3
# 或者
sudo mkfs.ext3 rootfs.ext3- 将文件拷贝到镜像中
1
2
3
4mkdir tmpfs
sudo mount -o loop -t ext3 ./rootfs.ext3 tmpfs/
sudo cp -r rootfs/* tmpfs/
sudo umount tmpfs
其实这里除开上面提到的使用
qemu-img
这个命令之外,还可以使用dd
命令来制作镜像文件。如下所示:1
2
3
4
5
6mkdir -p tmpfs
dd if=/dev/zero of=rootfs.ext3 bs=1M count=512
mkfs.ext3 rootfs.ext3
sudo mount -o loop -t ext3 ./rootfs.ext3 tmpfs/
sudo cp -r rootfs/* tmpfs/
sudo umount tmpfs其实,这种方法和第一种方法差不多。
测试
这是最后一步,我们可以启动模拟器,并将镜像文件作为启动设备来启动系统。进行测试:
1
2
3
4
5
6
7
8qemu-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 rootfs.ext3
启动qemu
注意这里和上面的不同点,一个是 u 一个是 z
1 |
|
参数解释:
- -M:指定模拟器的硬件平台,这里是
vexpress-a9
开发板。可以私用qemu-system-arm -M help
查看所有支持的硬件平台。 - -m:指定模拟器的内存大小,这里是
512M
。 - -kernel:指定内核文件,这里是
uImage
。 - -dtb:指定设备树文件,这里是
vexpress-v2p-ca9.dtb
。**这个设备树文件也是 Linux 内核自己提供的,比如说 arm 架构下的设备树文件存放在linux-6.1.46/arch/arm/boot/dts
- -nographic:不使用图形化界面,仅仅使用串口。
- -append:指定启动参数,这里是
root=/dev/mmcblk0 console=ttyAMA0
,表示启动时挂载根文件系统/dev/mmcblk0
,串口输出到ttyAMA0
。 - -sd:指定 sd 卡设备,这里是用来指定启动设备,这里是
rootfs.ext3
。
使用 QEMU 直接模拟一块开发板
在上面的过程中,我们使用 QEMU 模拟了一块系统自带的开发板。但是 QEMU 的功能还远远不止如此。下面我们使用 QEMU 直接模拟一块开发板。使用 QEMU 来直接定制一块自己的开发板。
QEMU 编程基础
QEMU是一款开源的模拟器及虚拟机监管器(Virtual Machine Monitor, VMM),通过动态二进制翻译来模拟CPU,并提供一系列的硬件模型,使guest os认为自己和硬件直接打交道,其实是同QEMU模拟出来的硬件打交道,QEMU再将这些指令翻译给真正硬件进行操作。
QOM 机制
QOM——The QEMU Object Model
QEMU提供了一套面向对象编程的模型——QOM,即QEMU Object Module,几乎所有的设备如CPU、内存、总线等都是利用这一面向对象的模型来实现的。
QEMU对象模型提供了一个注册用户可创建类型并从这些类型实例化对象的框架。
其实也就是一种OOP IN C(C上实现面对对象)。一段面对对象的程序代码(C++语言)
准备好启动脚本——boot.scr
准备好启动参数——cmdline.txt
准备好镜像文件——Image
准备好启动设备——sdcard.img
准备好启动命令——qemu-system-arm
启动模拟器
参考资料
- 一次搞定 Arm Linux 交叉编译
- 用qemu模拟器搭建arm运行环境
- 从0开始使用QEMU模拟ARM开发环境系列一览表
- 从0开始使用QEMU模拟ARM开发环境
- 宅学部落——QEMU模拟器使用指南
- Qemu使用及常见开发板的模拟
- Qemu搭建ARM vexpress开发环境(二)
- Qemu搭建ARM vexpress开发环境(一)
- Qemu搭建ARM vexpress开发环境(二)
- Qemu搭建ARM vexpress开发环境(三)
- 虚拟机就是开发板
- qemu使用uboot通过网络加载 linux kernel