Qemu简介

Qemu是一款开源免费的仿真软件,跟VMware station类似。Qemu和VMware station不同的是,它可以仿真嵌入式开发板(支持ARM、MIPS、RISC-V等各种架构),模拟的开发板支持各种外设:串口、LCD、网卡、USB、SD卡等,可以在这个开发板上运行U-boot+Linux+Rootfs。

本文以ARM官方的FPGA验证平台:vexpress开发板为例,在Ubuntu下进行qemu仿真,在上面移植u-boot+Linux+NFS。

01步:安装Ubuntu-20.04

1.1 安装VMware

VMware是一个虚拟机仿真软件,通过这个仿真软件,可以直接在Windows下面运行Linux操作系统。

本文使用VMware 16

https://www.vmware.com/cn/products/workstation-pro/workstation-pro-evaluation.html

激活码:ZF3R0-FHED2-M80TY-8QYGC-NPKYF

1.2 下载 Ubuntu20.04 镜像

我们需要在VMware虚拟机上运行Linux,这里选择Ubuntu-20.04,镜像下载地址:

Alternative downloads | Ubuntu

1.3 安装配置Ubuntu20.04在VMware上的虚拟机环境

详细记录-Ubuntu 20.04安装配置过程-虚拟机 - Rabbit's Blog (hjhai.cn)

1.4基本软件安装

sudo apt update
sudo apt install open-vm-tools #只针对于vmware-tools安装没有成功的情况,安装成功了就不必执行了
sudo apt install build-essential openssh-server vim net-tools gcc-arm-linux-gnueabi
sudo apt install gcc、make、ssh、vim

02步:在Ubuntu上安装QEMU

buntu20.04对qemu支持良好,直接apt安装即可。

sudo apt install qemu-system

03步:编译Linux内核镜像

Linux内核源码可以从官网下载(www.kernel.org),也可以从国内的镜像服务器下载,下载速度更快。
国内镜像下载地址: https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x

46

执行以下命令可以完全完成编译和安装工作

cd /home
sudo mkdir tftpboot
sudo chmod 777 tftpboot
cd tftpboot
mkdir kernel
cd kernel
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.10.99.tar.xz
tar -xvf linux-5.10.99.tar.xz
cd linux-5.10.99
gedit Makefile +371

修改Makefile编译,设置为arm-linux-gnueabi-gcc编译器

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

49

将编译功能配置为vexpress,在进入GUI编译模式配置编译

make vexpress_defconfig
make menuconfig

报下面这两个错误是因为flex和bison没有装,装了就好

sudo apt install flex bison

27

28

这种情况是因为libncurses-dev没装,同理

sudo apt install libncurses-dev

29

30

出现这个页面证明kernel配好了,可以安心编译了

make zImage  -j 4
make modules -j 4
make dtbs    -j 4
make LOADADDR=0x60003000 uImage  -j 4

编译好以后,再把镜像文件和设备树文件复制到工程目录里

cp arch/arm/boot/zImage /home/tftpboot/
cp arch/arm/boot/uImage /home/tftpboot/
cp arch/arm/boot/dts/vexpress-v2p-ca9.dtb /home/tftpboot/

zImage为通用内核文件,modules是没有加载进内核的模块(驱动, make menuconfig中设置为(M)的内容), dtb为编译的设备树,uImage是专供u-boot引导的内核,这里暂时用不上,但是我们这里先编译,可能会有以下错误:

39

装一个u-boot-tools即可解决

sudo apt install u-boot-tools

40

编译好以后,看镜像文件zImage, 设备树文件vexpress-v2p-ca9.dtb是否存在,记录下路径
在目录/home/tftpboot下创建一个脚本start.sh

cd /home/tftpboot
touch start.sh
chmod 777 start.sh
gedit start.sh

输入以下内容:

qemu-system-arm \
        -M vexpress-a9 \
        -m 512M \
        -kernel zImage \
        -dtb vexpress-v2p-ca9.dtb \
        -nographic \
        -append "console=ttyAMA0"

改用root登录,su root以后./start.sh启动

31

有以上显示,证明内核挂载成功。最后提示end Kernel panic是因为没有根文件系统,完成下面的部分就可以解决这个问题

04步:制作根文件系统

4.1 编译、安装根文件系统

根文件系统的安装方法有很多,这里我们为了方便,安装最为简单和轻量的busybox

cd /home/tftpboot
mkdir filesys
cd filesys
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -xvf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
sudo mkdir -p /home/nfs
sudo chmod 777 /home/nfs/

配置Makefile

gedit Makefile +191

和编译kernel的时候一样,ARCH和CROSS_COMPILE配置成arm编译器

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

32

和kernel一样,输入make menuconfig进行一些配置
(1) 设置一下编辑器环境,方便操作:
Settings —-> [*] vi-style line editing commands (New)

(2) 设置一下安装路径,避免错误安装在根目录
Settings —-> Destination path for ‘make install’

进入以后,输入自己的rootfs的路径(我这里的路径是: /home/nfs)

34

设置完毕,设置编译安装:

make install -j 4

无需再输入单独的编译命令,没有编译的话默认是会编译好了再安装的
注意!!!!!这里不要以root用户编译也不要加sudo编译,防止安装到根目录里,破坏Ubuntu
我们通常设置的安装模式安装到的/home/tftpboot已经给了最高权限,是肯定可以安装的,如果错误设置不安装到这个地方就会因为没有权限导致安装不上,从而完全避免了安装到根目录导致Ubuntu崩溃的问题。
如下图,就是错误的将安装目录设置为根目录的时候,因为没有权限没有执行安装过程,从而避免了因为错误安装导致的系统崩溃。

33

正常安装以后:

35

基本系统的功能是有了,接下来的功能便是安装动态链接库设置设备节点设置初始化进程设置文件系统设置环境变量

4.2 完成完整的根文件系统的构建

4.2.1 安装动态链接库

通过apt安装的arm编译器,动态链接库路径通常为:/usr/arm-linux-gnueabi/lib,复制到根文件系统的lib下

cd /home/nfs
mkdir lib
cd /usr/arm-linux-gnueabi/lib
cp *.so* /home/nfs/lib -d

4.2.2 创建设备结点

cd /home/nfs
mkdir dev
cd dev
sudo mknod -m 666 tty1 c 4 1
sudo mknod -m 666 tty2 c 4 2
sudo mknod -m 666 tty3 c 4 3
sudo mknod -m 666 tty4 c 4 4
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3

4.2.3 设置初始化进程/etc/rcS

cd /home/nfs
mkdir -p etc/init.d
cd etc/init.d
touch rcS
chmod 777 rcS
gedit rcS

将以下内容加入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
echo "-----------------------------------"
echo "*****welcome to vexpress board*****"
echo "-----------------------------------"

4.2.4 设置文件系统/etc/fstab

cd /home/nfs/etc
touch fstab
gedit 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

4.2.5 设置初始化脚本/etc/inittab

cd /home/nfs/etc
touch inittab
gedit inittab

输入以下内容:

::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r

4.2.6 设置环境变量/etc/profile

cd /home/nfs/etc
touch profile
gedit profile

输入以下内容:

USER="root"
LOGNAME=$USER
export HOSTNAME=`cat /etc/sysconfig/HOSTNAME`
export USER=root
export HOME=/root
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

4.2.7 增加主机名/etc/sysconfig/HOSTNAME

cd /home/nfs/etc
mkdir sysconfig
cd sysconfig
touch HOSTNAME

输入以下内容:

vexpress

4.2.8 创建剩下的文件夹

cd /home/nfs
mkdir mnt proc root sys tmp var

4.3 封装构建好的根文件系统,并挂载

cd /home/
sudo mkdir temp
sudo dd if=/dev/zero of=rootfs.ext3 bs=1M count=32
sudo mkfs.ext3 rootfs.ext3
sudo mount -t ext3 rootfs.ext3 temp/ -o loop
sudo cp -r nfs/* temp/
sudo umount temp
sudo mv rootfs.ext3 tftpboot
cd /home/tftpboot
sudo gedit start.sh

更改启动脚本start.sh:
和前面一样,root登录才能启动

qemu-system-arm \
        -M vexpress-a9 \
        -m 512M \
        -kernel zImage \
        -dtb vexpress-v2p-ca9.dtb \
        -nographic \
        -append "root=/dev/mmcblk0 rw console=ttyAMA0" \
        -sd rootfs.ext3

如图,挂载成功,内核版本也是课程配套的5.10.99版本

36

如果想设置为LCD启动的话,将start.sh改一下就好

qemu-system-arm \
        -M vexpress-a9 \
        -m 512M \
        -kernel zImage \
        -dtb vexpress-v2p-ca9.dtb \
        -append "root=/dev/mmcblk0 rw console=tty0" \
        -sd rootfs.ext3

效果图如图:

50

05步:使用u-boot加载内核

5.1 u-boot的存在的必要性

如果是之前玩过实体开发板的朋友,会疑惑一件事情,u-boot去哪了?
这是因为qemu自带bootloader功能,可以直接引导内核,有点类似于自带BIOS系统的电脑主板。
但是嵌入式设备通常是没有这样的条件的,所以我们如果真的要用QEMU完成仿真工作的话,还是得把u-boot加进来。

5.2 u-boot的编译和安装

下载完毕以后,存入目录/home/user/vexpress/u-boot里,老样子,解压,配置编译器,配置BSP,编译,运行

cd /home/tftpboot
mkdir uboot
cd uboot
wget https://ftp.denx.de/pub/u-boot/u-boot-2022.07-rc3.tar.bz2
tar -xvf u-boot-2022.07-rc3.tar.bz2
cd u-boot-2022.07-rc3
gedit Makefile +272

和前面一样,输入以下内容,配置编译器

ARCH = arm 
CROSS_COMPILE = arm-linux-gnueabi-

51

配置和编译

make vexpress_ca9x4_defconfig
make -j 4

出现这个错误是因为没有openssl,装一个问题就解决了

52

sudo apt install libssl-dev

这里我们先写一个测试脚本start_uboot.sh脚本

touch start_uboot.sh
chmod 777 start_uboot.sh
gedit start_uboot.sh

输入以下内容:

 qemu-system-arm    -M vexpress-a9 \
                     -kernel u-boot \
                     -nographic      \
                     -m 512M          \

运行u-boot:

./start_uboot.sh

效果如下图,qemu加载u-boot成功

38

5.3 u-boot+kernel+根文件系统全真模拟

细心的朋友可能发现了,4.2中我们加载u-boot的方法使用还是-kernel选项,也就是是把u-boot当做内核挂载的。
系统启动不可能同时挂载两个内核启动(只能选择一个启动)
有没有一个可行发方法来模拟呢?有!

u-boot实体机中我们常常采用tftp引导内核,我们也可以在启动u-boot以后,让u-boot通过tftp引导内核
我们需要做两件事情:1.搭建好tftp环境 2.构建网桥,让qemu可以访问,从而让qemu和ubuntu之间可以通过该网桥来访问

5.3.1 安装tftp和相关依赖,设置好路径

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

设置tftp配置路径文件:

sudo vim /etc/default/tftpd-hpa

将下面的内容复制进来

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/tftpboot" #该路径即为tftp可以访问到的路径
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"

配置完以后,创建tftp目录,给最高权限,把内核、设备树、根文件系统、u-boot全部复制到这里,重启tftp服务

cd  /home/tftpboot
cp /home/tftpboot/uboot/u-boot-2022.07-rc3/u-boot .
cp /home/tftpboot/kernel/linux-5.10.99/arch/arm/boot/uImage .
sudo /etc/init.d/tftpd-hpa restart

5.3.2 修改网卡信息,设置桥接

ifconfig                     #查看网卡名
sudo gedit /etc/netplan/01-network-manager-all.yaml             #修改网卡名称设置

53

修改/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
  1. 输入sudo netplan apply使其生效,再输入ifconfig查看网卡信息
sudo netplan apply
ifconfig
  1. 修改/etc/qemu-ifdown信息配置
sudo gedit /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
  1. 修改/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的地址

再启动修改start.sh再启动(start.sh存入tftpboot中)

cd /home/user/tftpboot
touch start.sh
chmod 777 *
ls -l
gedit start.sh

输入启动指令

qemu-system-arm                 \
        -M vexpress-a9          \
        -kernel ./u-boot        \
        -nographic              \
        -m 512M                 \
        -nic tap,ifname=tap0    \
        -append "root=/dev/mmcblk0 rw console=ttyAMA0" \
        -sd  rootfs.ext3

root登录以后,./start.sh启动u-boot,此时敲击enter键,进入u-boot交互模式,输入以下内容:

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                             # 设置启动地址

和前面kernel+busybox一样,挂载成功

5.4 u-boot的一些小修改

5.3中在u-boot中的配置还是很麻烦的,如果是实体机的话因为有在NAND/MMC Flash给了一个分区存参数,使用saveenv指令就能把参数存下来,但是我们这部分还没有把NAND/MMC相关支持完全做好,所以这样是不行的。

每次都要在u-boot这样处理,那也太麻烦了
有更简单的方法吗?
有!我们把u-boot地址、br0地址、内核加载命令bootcmd和根文件系统加载命令bootargs写入u-boot原程序里编译好了再放出来了就可以了。

5.4.1 设置u-boot和br0的IP地址

回到u-boot所在路径,再在vexpress的配置头文件里加入这么几行

cd /home/tftpboot/uboot/u-boot-2022.07-rc3/
gedit include/configs/vexpress_common.h +202

输入以下内容:

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

54

输入以后,保存再次编译,再把新的u-boot复制到tftp路径

cd /home/tftpboot/uboot/u-boot-2022.07-rc3
make -j 2
cp u-boot /home/tftpboot
cd /home/tftpboot

5.4.2 设置启动选项

Boot options —> Enable a default value for bootcmd, 输入以下内容

tftp 0x60003000 uImage;tftp 0x60500000 vexpress-v2p-ca9.dtb;setenv bootargs 'root=/dev/mmcblk0 console=ttyAMA0';bootm 0x60003000 - 0x60500000;

55

5.4.3 编译, 传回tftpboot,再测试

cd /home/tftpboot/uboot/u-boot-2022.07-rc3
make -j 2
cp u-boot /home/tftpboot
cd /home/tftpboot
su root
./start.sh

用户可以输入reboot重启测试一下,可以发现也是能正常工作的

06步:挂载NFS根文件系统

6.1 NFS的安装、配置

前面的根文件系统是.ext3镜像格式,不方便开发调试。为了方便开发,我们可以将开发板的根文件系统设置成NFS网络文件系统,这样我们级不需要往开发板往复拷贝文件了,直接在主机上操作NFS即可。
NFS安装使用apt就可以,很方便

sudo apt install nfs-kernel-server

我们前面配置busybox的根文件系统目录为/home/nfs。我们将开发板的根文件系统挂载路径设置到这里就可以了:设置挂载目录路径的配置文件为/etc/exports。打开这个文件:

sudo gedit /etc/exports

加入内容:

/home/nfs *(rw,sync,no_root_squash,no_subtree_check)

重启NFS服务器

sudo /etc/init.d/rpcbind restart
sudo /etc/init.d/nfs-kernel-server restart

6.2 NFS的兼容问题

方法一:重新编译内核,使之支持NFS V4
位置: File System —> Network File Systems—>NFS client support for NFS version 4

43

但是更改以后可能出现下面这个错误:

44

这里报错是因为内核大小超出了5M,后面加载的设备树文件覆盖了内核镜像,内核在5M的位置被覆盖,通不过CRC校检导致的。解决方法:将设备树文件加载到内存的地址往后移动,移动到从0x60500000移动到0x60800000。

方法二:设置Ubuntu20.04的NFS,使之兼容NFS-V2和NFS-V3

这个方法相对就容易很多,修改文件/etc/default/nfs-kernel-server,加入NFS的2,3,4的所有支持,加入调试功能,即

sudo gedit /etc/default/nfs-kernel-server

输入以下内容

RPCSVCGSSDOPTS="--nfs-version 2,3,4 --debug --syslog"

45

改好了记得把NFS重启一下

sudo /etc/init.d/rpcbind restart
sudo /etc/init.d/nfs-kernel-server restart

7.3 最终测试

修改启动脚本start.sh:

qemu-system-arm \
    -M vexpress-a9 \
    -kernel u-boot \
    -nographic \
    -m 512M \
    -nic tap

执行脚本:

cd /home/nfs
touch test-nfs
cd /home/tftpboot/
su root
./start.sh

启动以后,快速按空格,输入以下的启动指令:

tftp 0x60003000 uImage;tftp 0x60800000 vexpress-v2p-ca9.dtb;setenv bootargs 'root=/dev/nfs rw nfsroot=192.168.33.145:/home/nfs,proto=tcp,nfsvers=3,nolock init=/linuxrc ip=192.168.33.144 console=ttyAMA0';bootm 0x60003000 - 0x60800000;

即可完成NFS的引导,如果想设置为自动引导,需要将bootcmd修改成这个就好

58

挂载成功示例:

57

综上,所有 Ubunt20.04+QEMU 的环境配置工作完成。

qemu参数大全

qemu支持不同的平台,比如arm、mips等
qemu同时也支持很多参数,用来描述仿真的开发板参数,不同的qemu参数,可以指定开发板的不同配置。

以仿真的ARM平台vexpress为例,qemu-system-arm各个参数的使用示例及说明如下:

参数说明
-M vexpress-a9指定要仿真的开发板:vexpress-a9
-m 512M指定DRAM内存大小为512MB
-cpu cortex-a9指定CPU架构
-smp nCPU的个数,不设置的话,默认是1
-kernel ./zImage要运行的镜像
-dtb ./vexpress-vap-ca9.dtb要加载的设备树文件
-append cmdline设置Linux内核命令行、启动参数
-initrd file使用file文件作为初始化ram disk
-nographic非图形化启动,使用串口作为控制台
-sd rootfs.ext3使用rootfs.ext3作为SD卡镜像文件
-net nic创建一个网卡
-net nic -net tap将开发板网卡和主机网卡建立桥接(Bridge)

其他配置

参数说明
-mtdblock file使用file作为片上Flash镜像文件
-cdrom file使用file作为CDROM镜像文件
-display vnc= display设置显示后端类型
-vnc display-display vnc=的简写形式
-display none默认:-vnc localhost:0,to=99,id=default
-boot a c d na从floppy启动,c从光盘,d从硬盘,n从网络启动
最后修改:2022 年 09 月 25 日
如果觉得我的文章对你有用,请随意赞赏