使用QEMU搭建U-Boot+Linux+NFS嵌入式开发环境
- QEMU简介
- 使用QEMU的事情
- 使用QEMU学习嵌入式的好处
- 驱动开发技能
- 搭建嵌入式基本开发环境
- 基本环境
- Vmware+Ubuntu18.04
- Vmwaretools
- 更新软件源
- 安装uboot-tools
- 代码编辑管理工具:git/vim
- 交叉编译器:gcc-arm-linux-gnueabi
- 交叉编译
- ABI和EABI
- 安装
- QEMU安装
- 环境
- 自动安装
- 手动编译安装
- QEMU支持的开发板
- ARM express开发板简介
- Vexpress系列开发板
- Vexpress系列支持的CPU
- 系统框图
- 子板系统图
- 开发板基本配置
- 内存映射分布
- 最小系统概念
- 基本配置
- 编译Linux内核和dtb文件
- 下载Linux内核
- 修改内核根目录的Makefile,若没有则添加
- 修改单板配置文件
- 编译内核、模块、dtb文件
- 运行内核
- 关闭qemu
- 使用busybox制作根文件系统
- 文件系统
- 为什么要使用文件系统
- 根文件系统
- busybox
- 编译安装
- 下载源码
- 修改Makefile
- 配置
- 编译安装
- 制作根文件系统
- 制作SD卡文件系统镜像
- 启动内核,挂载rootfs
- 使用LCD启动内核
- 使用u-boot加载Linux内核
- 嵌入式启动概述
- 嵌入式bootloader
- 常见的启动方式
- U-boot编译
- QEMU网络功能设置
- 配置QEMU与主机的网络连接
- 查看主机内核是否支持tun/tap模块
- 配置
- 内核配置编译
- 主机TFTP工具安装
- 启动测试
- 自动化引导(添加如下定义)
- 注意事项
- 挂载NFS文件系统
- NFS
- 主机HOST支持NFS服务
- 安装
- 配置NFS
- 开启NFS服务
- 修改bootargs启动参数
- 内核支持挂载NFS文件系统
- ubuntu18挂载NFS4文件系统
- 注意事项
- 完善根文件系统
- 步骤
- 文件的启动流程
- 运行应用和内核驱动程序
- 运行应用程序
- 运行内核驱动程序
QEMU简介
- QEMU是一个模拟器,可以模拟CPU,ARM、X86、MIPS等架构
- 可以仿真的ARM处理器:ARM926E、ARM1136、Cortex-A8/A9
- 模拟真实的开发板、外设:串口、LCD、网卡、USB、SD卡…
使用QEMU的事情
- 研究内核虚拟化
- 模拟CPU,对于芯片公司,流片之前在QEMU上做验证、仿真、软硬件协同设计,开发BSP和驱动
- 模拟开发板,在模拟平台上进行系统软件开发、驱动开发
- 学生、工程师可以利用qemu-system-arm学习嵌入式开发、研究Bootloader 、Linux内核、驱动开发、应用开发等。
使用QEMU学习嵌入式的好处
- 节省学习成本
- 跳过开发板、硬件的各种“坑”,缩短学习曲线
- 重构嵌入式知识体系和技能,跟硬件无关的放到QEMU上学习
- 跟开发板相关的驱动、BSP针对具体开发板深入突破
- 适应不同CPU、开发板的技术要求
驱动开发技能
- 基本的硬件知识
- Linux内核、系统架构的理解
- 芯片手册、开发板
搭建嵌入式基本开发环境
基本环境
Vmware+Ubuntu18.04
Vmwaretools
更新软件源
- 软件源
Ubuntu采用集中式的软件仓库机制,将软件包分类存在软件仓库中,进行管理。软件仓库放置在各种镜像服务器中,对于用户来说,当使用apt-get install安装软件包时,会从这些服务器下载软件包,这些镜像服务器就是 软件源(Reposity).
- 经常涉及的几个目录
/var/lib/dpkg/available:软件包的描述信息,软件源中所有软件包中的信息,包括安装和未安装的软件包
/var/cache/apt/archives:当apt install安装软件包的临时存放路径
/etc/apt/sources.list:文件里是软件源站点,apt install时,Ubuntu会从这些站点下载软件包到本地并进行安装
/var/lib/apt/lists:使用apt update命令会从/etc/apt/sources.list中下载软件包列表索引,并保存到该目录
- 工作原理
- 执行apt update,程序分析/etc/apt/source.list
- 自动联网寻找list中对应的Packages/Sources/Release列表文件,如果有更新则重新下载之,存入存入/var/lib/apt/lists/目录
- 然后apt install相应的包,下载到本地并进行安装
安装uboot-tools
- 编译的内核镜像需要通过工具生成uboot可引导的格式
- 用来生成适应U-boot引导的镜像文件格式
- $ apt-get install u-boot-tools
代码编辑管理工具:git/vim
交叉编译器:gcc-arm-linux-gnueabi
交叉编译
在一种计算机环境中编译程序,在另外一种环境下运行。
或者说在一个平台上编译生成在另一个平台上运行的可执行代码。
ABI和EABI
- ABI: 二进制应用程序接口(Application Binary Interface (ABI) for the ARMArchitecture) 在计算机中,应用二进制接口描述了应用程序(或者其他类型)和操作系统之间或其他应用程序的低级接口. 涵盖了数据类型的大小、布局和对齐,调用约定
- EABI: 嵌入式ABI
嵌入式应用二进制接口指定了文件格式、数据类型、寄存器使用、堆积
组织优化和在一个嵌入式软件中的参数的标准约定。 - Arm-none-gnueabi-linux在可移植性、兼容性上面比arm-linux-gcc要好.
- 早期u-boot和Linux编译可能使用的都不是一个arm-linux-gcc版本
安装
apt-get install gcc-arm-linux-gnueabi //ubuntu18.04
arm-linux-gnueabi-gcc -v
QEMU安装
环境
自动安装
- sudo apt-get install qemu
手动编译安装
- 首先安装QEMU编译依赖的包
– apt install zlib1g-dev
– apt install libglib2.0-0 libglib2.0-dev
– apt install libsdl1.2-dev
– apt install libpixman-1-dev libfdt-dev - 下载QEMU源码:git clone git://git.qemu-project.org/qemu.git
- 切换到一个稳定版本:git checkout v2.7.0
- 编译配置:./configure --target-list=arm-softmmu --audio-drv-list=
- 编译安装:make ; make install
QEMU支持的开发板
qemu-system-arm -M help //列出支持的开发板
- cubieboard
- i.mx25
- mainstone
- Samsung Exynos4210
- Vexpress-a9
- Vexpress-a15
- …
ARM express开发板简介
Vexpress系列开发板
- 全称versatile express family,ARM公司自己推出的开发板
- 主要用于SOC厂商设计、验证和测试自己的SOC芯片
- 采用主板+子板设计,主板提供各种外围接口,子板提供CPU运算
Vexpress系列支持的CPU
- Cortex-A9:处理器子板 Express A9x4 (V2P-CA9x4)
- Cortex-A5:处理器子板 Express A5x2 (V2P-CA5x2s)
- Cortex-R5:
- Cortex-A15:处理器子板 Express A15x2 (V2P-CA15x2)
系统框图
子板系统图
开发板基本配置
内存映射分布
最小系统概念
- 嵌入式最小系统
- CPU+DDR/SDRAM
- Flash、SD
- 串口+LCD
基本配置
- 内存
- LCD
- 串口
编译Linux内核和dtb文件
下载Linux内核
- www.kernel.org
- git clone
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/
- 下载一个长期维护的版本
修改内核根目录的Makefile,若没有则添加
- ARCH=arm
- CROSS_COMPILE=arm-linux-gnueabi-
修改单板配置文件
- 位于内核根目录/arch/arm/configs中
- 修改vexpress_defconfig,一般默认
- 在内核根目录执行make vexpress_defconfig即可完成配置
编译内核、模块、dtb文件
- make vexpress_defconfig
- make zImage //可采用-jx使用x个线程编译,加快编译速度
- make modules
- make dtbs
执行make vexpress_defconfig遇到的问题
LEX scripts/kconfig/lexer.lex.c
/bin/sh: 1:flex: not found
scripts/Makefile.host:9: recipe for target ‘scripts/kconfig/lexer.lex.c’ failed
make[1]: *** [scripts/kconfig/lexer.lex.c] Error 127
Makefile:591: recipe for target ‘vexpress_defconfig’ failed
make: *** [vexpress_defconfig] Error 2
解决方法sudo apt-get install flex
运行内核
qemu-system-arm -M vexpress-a9 -m 512M -kernel arch/arm/boot/zImage -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append “console=ttyAMA0”
-M vexpress-a9 模拟vexpress-a9单板,你能够使用-M ?參数来获取该qemu版本号支持的全部单板
-m 512M 单板执行物理内存512M
-kernel arch/arm/boot/zImage 告诉qemu单板执行内核镜像路径
-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb 告诉qemu单板的设备树(必须加入)
-nographic 不使用图形化界面,仅仅使用串口
-append "console=ttyAMA0" 内核启动參数。这里告诉内核vexpress单板执行。串口设备是哪个tty。
关闭qemu
- ps -a查看进程号
- kill -9 pid 关闭进程
使用busybox制作根文件系统
文件系统
- 对存储设备上的数据进行组织的机制
为什么要使用文件系统
- Linux的哲学:一切皆文件
- 用户与操作系统进行交互的主要工具:文件系统调用
- 用户和底层存储的接口
根文件系统
- Linux内核启动后第一个挂载的文件系统
- 主要由基本的shell命令、各种库、字符设备、配置脚本组成
- 提供了根目录 /
- RFS(根文件系统root file system)可以放在:nor/nand flash、SD卡、磁盘、网络空间上
busybox
- 一个集成100多个Linux常用命令和工具的软件
- 一个适合制作嵌入式文件系统的软件工具
编译安装
下载源码
sudo apt-get source busybox
修改Makefile
- ARCH=arm
- CROSS_COMPILE=arm-linux-gnueabi-
配置
- make defconfig
- make menuconfig
- 遇到的问题
HOSTCC scripts/kconfig/lxdialog/checklist.o
<command-line>:0:12: fatal error: curses.h: No such file or directory
compilation terminated.
scripts/Makefile.host:120: recipe for target 'scripts/kconfig/lxdialog/checklist.o' failed
make[2]: *** [scripts/kconfig/lxdialog/checklist.o] Error 1
/home/snowynight/Work/busybox_soft/busybox-1.27.2/scripts/kconfig/Makefile:14: recipe for target 'menuconfig' failed
make[1]: *** [menuconfig] Error 2
Makefile:443: recipe for target 'menuconfig' failed
make: *** [menuconfig] Error 2
- 解决方法:sudo apt-get install libncurses5-dev
- 配置编译成静态库
编译安装
- make
- make install
- zImage生成路径:
arch/arm/boot/zImage - modules生成路径:
drivers/video/backlight/*.ko - dtbs生成路径:
arch/arm/boot/dts/vexpress-v2p-ca9.dtb
制作根文件系统
- mkdir rootfs
- mkdir rootfs/lib
- sudo cp -r busybox_soft/busybox-1.27.2/_install/* rootfs 文件系统目录
- sudo cp -p /usr/arm-linux-gnueabi/lib/* rootfs/lib/ 系统动态库
- 创建字符设备,设备文件,文件节点
- mkdir dev 用于存放文件节点
- 在dev目录下创建节点
- mknod –m 666 tty1 c 4 1 字符设备节点,权限666,串口
- mknod –m 666 tty2 c 4 2 主设备号为4,次设备号为2
- mknod –m 666 tty3 c 4 3
- mknod –m 666 tty4 c 4 4
- mknod –m 666 console c 5 1 工作台节点
- mknod –m 666 null c 1 3 垃圾回收站
制作SD卡文件系统镜像
- 生成镜像:dd if=/dev/zero of=rootfs.ext3 bs=1M count=32
- 格式化为exts文件系统:mkfs.ext3 rootfs.ext3
- 将各种文件拷贝到文件系统镜像中
- mount -t ext3 rootfs.ext3 /mnt/ -o loop
-o 是mount命令的一bai个参数,Options的首字母,du后面跟着mount选项喽zhi
loop:用来把一个文dao件当成硬1653盘分区mount到目录
- cp -r rootfs/* /mnt
- umount /mnt
- 遇到的问题
mount: /mnt: wrong fs type, bad option, bad superblock on /dev/loop21, missing >codepage or helper program, or other error.
解决方法
mkfs.ext3 rootfs.ext3
启动内核,挂载rootfs
- qemu-system-arm -M vexpress-a9 -m 512M -dtb ./vexpress-
v2p-ca9.dtb -kernel ./zImage-nographic
-append
"root=/dev/mmcblk0 rw
console=ttyAMA0"-sd rootfs.ext3
使用LCD启动内核
- 使用2.11版本的qemu无法启动图形化
- 据说2.7以下版本可以使用图形化启动,2.7以上版本需要用vnc虚拟机进行连接
- qemu-system-arm -M vexpress-a9 -m 512M -dtb ./vexpress-
v2p-ca9.dtb -kernel ./zImage -append "root=/dev/mmcblk0 rw
console=tty0
"-sd rootfs.ext3
使用u-boot加载Linux内核
嵌入式启动概述
嵌入式bootloader
- 功能类似于PC的BIOS、硬件检测是否正常
- 加载操作系统镜像到RAM
- 设置不同的启动方式
常见的启动方式
- NOR/NAND flash启动
- 从SD卡启动
- Bootloader从网络加载Linux内核启动
U-boot编译
- 功能类似于Windows的BIOS
- 编译u-boot
– 下载: http://ftp.denx.de/pub/u-boot/
– 修改Makefile:ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
– 配置:$ make vexpress_ca9x4_defconfig
– 编译:$ make –j4 - 运行u-boot
– qemu-system-arm -M vexpress-a9
–kernel u-boot
– nographic
– m 512M
- 修改顶层Makefile添加交叉编译工具
- 修改config.mk CPU架构ARCH为arm
- 配置
- 编译
- 测试
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel u-boot \
-nographic \
QEMU网络功能设置
配置QEMU与主机的网络连接
- 采用桥接(bridge)的网络连接与Host通信
- 需要主机内核tun/tap模块支持
查看主机内核是否支持tun/tap模块
配置
- 主机安装工具包: apt install uml-utilities bridge-utils
- 创建tun设备文件:/dev/net/tun
- 修改/etc/network/interfaces文件,重启生效:
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback
auto ens33
auto br0
iface br0 inet dhcp
bridge_ports ens33
- 配置/etc/qemu-ifup、/etc/qemu-ifdown脚本
查看网口
修改/etc/network/interfaces文件
ens33字段是ifconfig查到的虚拟网卡的名称
auto lo
iface lo inet loopback
auto
ens33
auto br0
iface br0 inet dhcp
bridge_ports
ens33
出现br0表示添加成功
内核配置编译
使用U-boot引导内核镜像
- 需要将内核编译为uImage格式
- 需要指定uImage的加载地址
- 编译时指定:$ make LOADADDR=0x60003000 uImage -j4
若无法生成uImage则参考 ---- 生成uImage的方法
有一个很简单的办法u-boot编译结束时,会在tool文件夹下面生成一个mkimage文件,将这个文件复制到交叉编译器目录下的bin文件夹下面,以后编译时就会生成uImage文件,省的用命令行的方式转了.
主机TFTP工具安装
- 安装主机Host的TFTP
- 安装tftp工具:$ apt-get install tftp-hpa tftpd-hpa xinetd
- 修改配置文件: /etc/default/tftpd-hpa
– TFTP_USERNAME=“tftp”
– TFTP_DIRECTORY="/home/snowynight/Work/tftpboot" //qemu会从该目录中寻找镜像文件
– TFTP_ADDRESS=“0.0.0.0:69” //tftp地址
– TFTP_OPTIONS="-l -c -s" - 创建tftp目录: mkdir /home/snowynight/Work/tftpboot
- chmod 777 tftpboot
- 重启tftp服务: /etc/init.d/tftpd-hpa restart
- 拷贝内核镜像(uImage)文件
- 拷贝dtb(cexpress-v2p-ca9.dtb)文件
启动测试
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel u-boot-source/u-boot-2019.07+dfsg/u-boot \
-nographic \
-device virtio-net-device,netdev=tap0 -netdev tap,id=tap0,ifname=tap0 \
# -net nic,vlan=0 -net tap,vlan=0,ifname=tap0 \ //旧版本的qemu
-sd rootfs.ext3
- 实测不需要修改,使用sudo权限执行也可以使用
自动化引导(添加如下定义)
修改include/configs/vexpress_common.h
- #define CONFIG_BOOTCOMMAND \
"tftp 0x60003000 uImage;tftp 0x60500000 vexpress-v2p-ca9.dtb; \
setenv bootargs ‘root=/dev/mmcblk0 console=tty0’; \
bootm 0x60003000 - 0x60500000; " - 配置开发板、主机IP地址
/Netmask/
#define CONFIG_IPADDR 192.168.244.128
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_SERVERIP 192.168.244.129
- 修改后要重新编译u-boot
- make -j4
注意事项
服务器地址由创建的虚拟网卡决定
setenv bootargs ‘root=/dev/mmcblk0 cnotallow=tty0’;参数需要和启动脚本中的一致
挂载NFS文件系统
NFS
- 网络文件系统
- 计算机与计算机之间可以通过网络实现文件共享
主机HOST支持NFS服务
- 主机HOST开启NFS服务
安装
- apt install nfs-kernel-server
配置NFS
- 在/etc/exports文件中添加:
- /home/snowynight/Work/rootfs *(rw,sync,no_subtree_check,no_root_squash)
开启NFS服务
- /etc/init.d/rpcbind restart
- /etc/init.d/nfs-kernel-server restart
修改bootargs启动参数
- 设置NFS为根文件系统
- 设置主机NFS文件系统的地址
内核支持挂载NFS文件系统
- make menuconfig
File systems —>
[*] Network File Systems —>
<> NFS client support
<> NFS client support for NFS version 3
[*] NFS client support for the NFSv3 ACL protocol extension
[*] Root file system on NFS
- 重新编译make LOADADDR=0X60003000 uImage -j4
- 拷贝到tftproot目录下
遇到的问题
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)
ubuntu18挂载NFS4文件系统
#define CONFIG_BOOTCOMMAND \
"tftp 0x60003000 uImage;tftp 0x60500000 vexpress-v2p-ca9.dtb; \
setenv bootargs 'root=/dev/nfs rw \
nfsroot=192.168.3.48:/home/snowynight/Work/rootfs,proto=tcp,nfsvers=4,nolock \
ip=192.168.3.88 console=ttyAMA0'; \
bootm 0x60003000 - 0x60500000;
注意事项
- setenv bootargs 参数
– ,proto=tcp
– ,nfsvers=4
– ,nolock - 开发板ip地址
make menuconfig配置
完善根文件系统
- mkdir etc
- cd etc/
- mkdir init.d
- cd init.d
- sudo vim rcS
echo "....................."
echo "inittab init"
echo "....................."
- sudo chmod 777 rcS
步骤
- 新建etct、mp、sys、var、proc目录
- 在etc目录中添加inittab、init.d/rcS、fstab、profile
- 修改控制台显示格式
参考该笔记.
在etc/profile文件添加
PS1='user@snowynight:\w # '
export PS1
- 启动Linux,挂载NFS文件系统
文件的启动流程
- Linux内核启动之后,挂载NFS根文件系统
- 开启Linux的第一个用户进程:init
- init进程bootargs->init=…->执行inittab脚本
- inittab脚本首先会执行init.d/rcS脚本
- rcS脚本:执行mount –a 读取fstab挂载各种文件系统
- inittab:接着会启动console
– 启动shell:/bin/sh
– 在启动/bin/sh之前先执行profile文件 - /etc/init.d/rcS
#! /bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=/lib
export PATH LD_LIBRARY_PATH
mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
mdev -s
mkdir -p /var/lock
- etc/fstab
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
var /dev tmpfs defaults 0 0
ramfs /dev ramfs defaults 0 0
- etc/inittab
::sysinit:/etc/init.d/rcS
#::respawn:-/bin/sh
#tty2::askfirst:-/bin/sh
#::ctrlaltdel:/bin/umount -a -r
console::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
- etc/profile
PS1='user@snowynight:\w # '
export PS1
运行应用和内核驱动程序
运行应用程序
- 注意采用交叉编译器进行编译
运行内核驱动程序
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");
static int hello_init(void)
{
printk(KERN_ALERT"-----------------!\n");
printk(KERN_ALERT"hello world!\n");
printk(KERN_ALERT"hello vexpress!\n");
printk(KERN_ALERT"-----------------!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT"goodbye, crazy world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
- Makefile
.PHONY:all clean
obj-m := arm_driver.o
KDIR := /home/snowynight/Work/linux-5.4.72
PWD := $(shell pwd)
all:
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm -C $(KDIR) M=$(PWD) modules
clean:
rm -fr *.ko *.o *.mod.o *.mod.c *.symvers *.order .*.ko .tmp_versions
- 挂载和卸载