使用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 configured

Thanks Jakob for the answer in the mailing list.

意思是,类似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-gccarm-none-eabi-gccaarch64-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-gccaarch64-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 KernelU-Boot 就不行,除非你提前把 CPU 切换到 32 位模式。曾经有个项目使用了一颗四核的 Arm64 芯片,但是内存只有64M,为了节省空间,在 CPU 运行到 U-Boot 之前,我们就把它切到了 32 位模式,后面的 U-BootLinux Kernel,应用全部都用 32 位编译,加上 Thumb 指令集,节省了不少空间。

ARM平台

现在 Arm 平台上用的最广泛的工具链是 Linaro 发布的,大家可以到 Linaro 官网下载,地址如下:

在这个平台上,可以使用 linaro 提供的工具链。

同时,我们也可以不使用这个工具链,自己编译工具链。

安装交叉编译器

1
sudo apt-get install gcc-arm-linux-gnueabi

如果想要卸载:

1
sudo apt-get remove gcc-arm-linux-gnueabi

如果提示缺少相对应的安装库则:

1
sudo apt-get install build-essential pkg-config zlib1g-dev libglib2.0-0 libglib2.0-dev  libsdl1.2-dev libpixman-1-dev libfdt-dev autoconf automake libtool librbd-dev libaio-dev flex bison -y

使用官方的配置开发板

qemu 官方是提供了一些已经配置好的开发板,我们可以直接使用这些配置好的开发板来进行模拟。但是, qemu 也支持自定义的开发板,我们可以自己配置自己的独特的开发板。这个部分我们之后再说

使用命令:

1
qemu-system-arm -M ?

这个查看所有支持的开发板。

准备好qemu

首先,我们需要安装 qemu 软件。

1
sudo apt-get install qemu-system-arm

准备好引导文件——Uboot

需要注意的是,如果直接使用 qemu 运行 Linux 的话其实是不需要 uboot 的的。这是因为其实 qemu 自带了一个引导程序,可以直接引导 Linux 内核。也就是说,如果直接使用 qemu 运行 Linux,我们不需要安装 u-boot。也是可以执行的:

1
2
3
4
5
6
7
8
qemu-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 a9rootfs.ext3

从上面这个命令中可以看出来,这里是直接使用 zImage 作为内核,而不是使用的 uImage作为内核。

如果是直接再电脑上使用 qemu 来运行 Linux,我们可以不用安装 uboot 。但是,对于嵌入式环境下,我们还是需要安装 u-boot 的。因为qemu自带bootloader功能,可以直接引导内核,有点类似于自带BIOS系统的电脑主板。但是嵌入式设备通常是没有这样的条件的,所以我们如果真的要用QEMU完成仿真工作的话,还是得把u-boot加进来。

一般来说,我们使用 u-boot 作为引导程序,使用 u-boot 引导 linux 内核的时候是需要内核的。uboot 的作用主要是引导,引导 kernel、rootfs 等等。

首先从官方网站下载 u-boot 软件包,然后解压:

1
2
3
git clone git@github.com:u-boot/u-boot.gitc

tar -xvf u-boot-2021.04.tar.bz2

然后进行配置:

1
2
3
cd u-boot-2021.04
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm vexpress_ca9x4_defconfig
# 这个由于官方已经提供了相关开发板的默认配置,就不再需要更改了,指定官方已经写好的配置。

编译:

1
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -j8make vexpress_ca9x4_defconfig

编译完毕之后,在当前目录下会生成u-boot文件,这个文件可以使用以下命令启动:

1
2
sudo qemu-system-arm -M vexpress-a9 -m 256 -kernel ./u-boot -nographic
# -nographic 不使用图形化界面,仅仅使用串口

这里在准备好 uboot 之后,我们可以使用 qemu 进行测试

1
2
3
4
5
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel /path_to_uboot/u-boot \
-nographic \

这里可以使用 qemu 来研究 uboot 的启动流程,可以帮助我们理解 uboot 的工作原理。

此时由于缺少 linux 内核,所以无法启动系统,我们需要准备好 linux 内核。

准备好内核——Linux Kernel

首先我们再 GitHub 上下载 linux 内核源码:

1
git clone git@github.com:torvalds/linux.git

配置、编译:

1
2
3
4
5
6
7
8
# 解压
tar xf linux-5.10.tar.xz
cd linux-5.10
# 配置,要指定ARCH=arm(也可以设置环境变量),才会从arm架构中找配置文件,这里注意Linux内核已经帮我们编写好了一些配置文件,可以去 /arch 下的文件夹中找。注意 arm 和 arm64 是不同的,一个是 32 位 一个是 64 位。
make ARCH=arm vexpress_defconfig
# 编译,可以使用参数zImage,modules,dtbs编译指定的部分,默认全部编译
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -j8
# 生成的文件位于arch/arm/boot下

由于我们使用 Uboot 进行引导,所以需要 uImage 作为内核,而不是 zImage。所以需要将 Linux 内核修改为 uImage 格式。二者有什么区别:** uImage 是带有 长度为 0x40 长的信息头的内核镜像,而 zImage 是没有信息头的内核镜像**。

1
2
make zImage -j8 #注意,由于 uImage 是由 zImage 来生成的
make uImage

制作 uImage 注意。uboot 有单独的工具生成 uImage,但是 Linux 内核也会自带工具用以从 zImage 来生成 uImage 。但是好像不是所有的 Linux 版本都自带这个工具

所以我们可以使用 uboot 自带的工具来生成 uImage:

1
2
sudo apt install u-boot-tools
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm LOADADDR=0x60003000 uImage -j8

使用 uboot 引导 Linux内核时需要注意,不是直接使用 qemu 命令中的 -kernel uImage,就可以了。而是要使用其他方法:

  • 将 uImage 放到 SD 卡中,并制作启动脚本。这里还可以将根文件系统也放在 SD 卡中
  • 使用 USB 启动,并制作启动脚本。
  • 使用网络启动,并制作启动脚本。

将内核和根文件系统放到 SD 卡中

介绍一下 parted 命令:

parted 是一个功能强大的命令行分区管理工具,用于磁盘分区的创建、调整大小、移动、删除和文件系统格式化等操作。它支持多种类型的磁盘和分区表格式,包括 MBR(主引导记录)和 GPT(全局唯一标识磁盘分区表)。通常,parted 提供了一个交互式的命令行界面,你可以在其中输入命令并执行。但是,当需要自动化任务或者在脚本中使用 parted 时,交互式界面就不太适用了。这时,可以使用 --script 参数来指示 parted 执行一系列的命令,这些命令在 --script 之后直接写入,没有交互提示。

如果不使用 --script 参数,parted 会进入一个命令行模式,在该模式下,你可以输入命令,但是不能使用交互式命令。下面有一些常用的命令:

1
2
3
4
5
6
7
8
parted [设备路径] # 启动 parted
print # 打印磁盘信息
mklabel [分区表类型] # 创建新分区,设置磁盘的分区表类型,例如 msdos 或 gpt。
mkpart [分区类型] [起始位置] [结束位置] # 创建一个新的分区,其中 分区类型 可以是 primary、logical 或 extended,起始位置 和 结束位置 是以扇区为单位的。
mkfs [文件系统类型] [分区号] # 格式化指定的分区,文件系统类型 可以是 ext2、ext3、ext4、xfs、ntfs 等。
resizepart [分区号] [新大小] # 调整指定分区的大小。
rm [分区号] # 删除指定的分区号
quit # 退出 parted 命令行界面。

首先,我们制作一个 SD 卡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
dd if=/dev/zero of=SD  bs=1M count=64
sudo parted SD --script -- mklabel msdos
# 这个命令在 SD 文件上创建一个新的分区表,使用 MS-DOS(MBR)分区表类型。

# 这里和2048对齐一下,减少一个警告
sudo parted SD --script -- mkpart primary fat32 2048s 40956s
# 这个命令在 SD 文件上创建第一个主分区,使用 FAT32 文件系统,从磁盘的第 2048 个扇区开始,到第 40956 个扇区结束。
sudo parted SD --script -- mkpart primary ext4 40960s -1
# 这个命令在 SD 文件上创建第二个主分区,使用 ext4 文件系统,从第 40960 个扇区开始,一直到文件的末尾。

# 建立映射,然后格式化两个分区
sudo losetup --show /dev/loop15 SD
# sudo apt install kpartx
sudo kpartx -va /dev/loop15
sudo mkfs.vfat /dev/mapper/loop15p1
# 格式化第一个分区为 FAT32 文件系统
sudo mkfs.ext4 /dev/mapper/loop15p2
# 格式化第二分区为 ext4 文件系统

# 拷贝内核zImage、vexpress-v2p-ca9.dtb到第一个分区
sudo mount /dev/mapper/loop15p1 /mnt
sudo cp ../linux-5.10/arch/arm/boot/uImage /mnt
sudo cp ../linux-5.10/arch/arm/boot/dts/vexpress-v2p-ca9.dtb /mnt
# 拷贝文件系统内的所有文件到第二个分区
sudo mount /dev/mapper/loop15p2 /media
sudo cp -r ../busybox-1.32.0/rootfs/* /media
sudo chown -R root.root /media/*
# 卸载
sudo umount /mnt
sudo umount /media​

总结一下,上面的步骤包括:

  1. 创建一个 64M 的空文件 SD,并格式化为 MS-DOS 分区表。
  2. 创建两个主分区,第一个分区为 FAT32 文件系统,第二个分区为 ext4 文件系统。
  3. 建立映射,并格式化两个分区。
  4. 拷贝内核和根文件系统到两个分区。
  5. 卸载两个分区。

现在,我们已经完成了 SD 卡的制作,下面我们就可以使用 qemu 启动 Linux 系统了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# u-boot启动SD卡中的kernel和文件系统
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ./u-boot-2022.04/u-boot \
-nographic \
-append "root=/dev/mmcblk0 console=ttyAMA0" \
-sd ./rootfs/SD

# 启动
fatls mmc 0:1
fatload mmc 0:1 60003000 uImage
fatload mmc 0:1 60500000 vexpress-v2p-ca9.dtb
setenv bootargs 'init=/linuxrc root=/dev/mmcblk0p2 rw rootwait earlyprintk console=ttyAMA0'
bootm 60003000 - 60500000

# tips
这里对文件系统的修改会保存

启动下面的这几个命令是在 uboot 启动之后的命令,也就是说,这些命令都是 uboot 自带的。

  1. 列出 MMC 卡内容(如果使用 MMC 作为存储设备):

     fatls mmc 0:1
    
  2. 从 MMC 卡加载内核映像:

     fatload mmc 0:1 60003000 uImage
    
  3. 从 MMC 卡加载设备树:

     fatload mmc 0:1 60500000 vexpress-v2p-ca9.dtb
    
  4. 设置启动参数:

     setenv bootargs 'console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw earlyprintk rootfstype=ext4'
    
  5. 启动 Linux 内核:

     bootm 60003000 60500000
    

在这些步骤中,60003000 是内核映像加载到的内存地址,60500000 是设备树加载到的内存地址。这些地址可能需要根据你的系统进行调整。

使用网络启动

通过网络中启动主要使用利用了 tftp 网络

通过网桥,将OS镜像(uImage),通过网络协议下载到U-Boot中,从而通过U-Boot启动OS:

首先需要安装好一些前置的软件包:

1
sudo apt install tftp-hpa tftpd-hpa xinetd uml-utilities bridge-utils

这个地方有两种方法可以实现网络启动:

  1. 一个是手动配置参数。
  2. 第二个是直接修改 uboot 的源代码

手动配置参数

这种方法不便于多次使用,而且每次都需要手动配置参数,比较麻烦。

  1. 首先第一步是要配置好 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
  2. 第二步需要创建好 tftp 目录,然后将内核、设备树、根文件系统放在这个文件夹中。

    1
    2
    3
    4
    5
    6
    cd ..
    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 ./
  3. 第三步配置网络信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ifconfig # 查看网卡信息
    # 修改/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 #重新查看网卡信息

  4. 第四步需要配置 /etc/qemu-ifdown 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    sudo 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
  5. 第五步配置 /etc/qemu-ifup 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    sudo 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的地址
  6. 回到 tftpboot 目录下,创建一个脚本文件,方便下次使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    cd ~/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 \
  7. 运行上面的 start.sh 脚本进入 uboot 启动流程,此时再次对 uboot 进行配置

    1
    2
    3
    4
    5
    6
    setenv 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd ./u-boot
vim ./etc/config/vexpress_commom.h
#在文件下面添加

#define CONFIG_IPADDR 192.168.33.144
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_SERVERIP 192.168.33.145

#注意,下面的内容需要在比较新U-boot上面才可以使用。在makemenuconfig中找到: make menuconfig -> Boot options -> 勾除Enable a default value or bootcmd 注意需要首先勾除,然后再添加下面的内容。如果你使用的是旧版本的u-boot,则需要在makemenuconfig中去添加下面的内容。

#ifndef CONFIG_BOOTCOMMAND
#define PING_COMMAND "ping 192.168.70.30;"
#define CONFIG_BOOTCOMMAND \
PING_COMMAND \
"tftp 0x60003000 uImage;" \
"tftp 0x60500000 vexpress-v2p-ca9.dtb;" \
"setenv bootargs 'root=/dev/mmcblk0 rw console=ttyAMA0';" \
"bootm 0x60003000 - 0x60500000;"
#endif

参考资料——网络启动

准备好 rootfs

Linux发行版的根文件系统很复杂,而我们这里用到的根文件系统很简单,我们要制作的根文件系统 = busybox(包含基础的Linux命令) + 运行库 + 几个字符设备。 根文件系统依赖于每个开发板支持的存储设备,可以放到Nor Flash上,也可以放到SD卡,甚至外部磁盘上。最关键的一点是你要清楚知道开发板有什么存储设备。

下面,我们来制作一个简单的根文件系统。

先在Ubuntu主机环境下,形成目录结构,里面存放的文件和目录与单板上运行所需要的目录结构完全一样,然后再打包成镜像(在开发板看来就是SD卡),这个临时的目录结构称为根目录。**这里其实还有第二中方法,即使用网络进行

  1. 下载 busybox

    1
    wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
  2. busybox 编译

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    tar -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 中找到 ARCHCROSS_COMPILE 变量,修改成如下内容:

    1
    2
    ARCH ?= arm
    CROSS_COMPILE = arm-linux-gnueabi-

    至此,我们已经准备好了 busybox ,下面需要的是制作一个根目录结构。

  3. 创建根目录结构

    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
      4
      sudo 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个字符设备。下面,我们需要制作根文件系统镜像文件。

  1. 将上面的根文件打包成镜像文件

    • 生成512M大小的磁盘镜像

      1
      qemu-img create -f raw rootfs.ext3 512M
    • 格式化成ext3文件系统

    1
    2
    3
    mkfs -t ext3 ./rootfs.ext3
    # 或者
    sudo mkfs.ext3 rootfs.ext3
    • 将文件拷贝到镜像中
      1
      2
      3
      4
      mkdir 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
    6
    mkdir -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

    其实,这种方法和第一种方法差不多。

  2. 测试

    这是最后一步,我们可以启动模拟器,并将镜像文件作为启动设备来启动系统。进行测试:

    1
    2
    3
    4
    5
    6
    7
    8
    qemu-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
2
3
4
5
6
7
8
qemu-system-arm\
-M vexpress-a9 \
-m 512M \
-kernel /path_to_kernel_dir/arch/arm/boot/uImage \
-dtb /path_to_kernel_dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb\
-nographic \
-append "root=/dev/mmcblk0 console=ttyAMA0" \
-sd rootfs.ext3

参数解释:

  • -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

启动模拟器

参考资料

参考资料——自定义开发板


使用qemu模拟一块开发板
https://ysc2.github.io/ysc2.github.io/2024/05/11/使用qemu模拟一块开发板/
作者
Ysc
发布于
2024年5月11日
许可协议