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
执行以下命令可以完全完成编译和安装工作
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-
将编译功能配置为vexpress,在进入GUI编译模式配置编译
make vexpress_defconfig
make menuconfig
报下面这两个错误是因为flex和bison没有装,装了就好
sudo apt install flex bison
这种情况是因为libncurses-dev没装,同理
sudo apt install libncurses-dev
出现这个页面证明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引导的内核,这里暂时用不上,但是我们这里先编译,可能会有以下错误:
装一个u-boot-tools即可解决
sudo apt install u-boot-tools
编译好以后,看镜像文件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启动
有以上显示,证明内核挂载成功。最后提示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-
和kernel一样,输入make menuconfig进行一些配置
(1) 设置一下编辑器环境,方便操作:
Settings —-> [*] vi-style line editing commands (New)
(2) 设置一下安装路径,避免错误安装在根目录
Settings —-> Destination path for ‘make install’
进入以后,输入自己的rootfs的路径(我这里的路径是: /home/nfs)
设置完毕,设置编译安装:
make install -j 4
无需再输入单独的编译命令,没有编译的话默认是会编译好了再安装的
注意!!!!!这里不要以root用户编译也不要加sudo编译,防止安装到根目录里,破坏Ubuntu
我们通常设置的安装模式安装到的/home/tftpboot已经给了最高权限,是肯定可以安装的,如果错误设置不安装到这个地方就会因为没有权限导致安装不上,从而完全避免了安装到根目录导致Ubuntu崩溃的问题。
如下图,就是错误的将安装目录设置为根目录的时候,因为没有权限没有执行安装过程,从而避免了因为错误安装导致的系统崩溃。
正常安装以后:
基本系统的功能是有了,接下来的功能便是安装动态链接库、设置设备节点、设置初始化进程、设置文件系统、设置环境变量
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版本
如果想设置为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
效果图如图:
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-
配置和编译
make vexpress_ca9x4_defconfig
make -j 4
出现这个错误是因为没有openssl,装一个问题就解决了
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成功
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 #修改网卡名称设置
修改/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查看网卡信息
sudo netplan apply
ifconfig
- 修改/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
- 修改/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
输入以后,保存再次编译,再把新的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;
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
但是更改以后可能出现下面这个错误:
这里报错是因为内核大小超出了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"
改好了记得把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修改成这个就好
挂载成功示例:
综上,所有 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 n | CPU的个数,不设置的话,默认是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 n | a从floppy启动,c从光盘,d从硬盘,n从网络启动 |