在QEMU中启动U-Boot和内核
作为Linux内核工程师,QEMU是日常工作中经常接触的一个虚拟化工具,通过其CPU以及外设的模拟能力,我们很容易搭建出一套用于测试或开发的虚拟环境。以前在使用时通常都是直接去引导内核,但其实QEMU并不只是能够引导内核,任何拥有硬件初始化和管理能力的二进制程序它都可以引导运行。今天我们就使用qemu引导uboot,并在uboot中启动内核。
为什么要使用U-Boot
既然QEMU本身就具备引导Linux内核的功能,我们为什么还要先引导U-Boot呢?我想主要有一下几点好处:
- U-Boot能够提供更灵活的内核引导方式,它不仅可以从本地获取内核文件,还能通过网络获取
- U-Boot可以提供更完整的调试环境,如果直接使用QEMU,我们就很难看到bootloader做了哪些事情
- 对于嵌入式系统的学习,U-Boot也是重要的一环,因此了解其工作原理有时也很重要
- 对于跨平台模拟,如Arm,当涉及到DTB加载和引导压缩内核镜像的时候,QEMU有时并不能正确处理
安装QEMU
QEMU在主流的发行版中都可以直接从源上下载安装。
- Arch:
pacman -S qemu
- Debian/Ubuntu:
apt-get install qemu
- Fedora:
dnf install @virtualization
- Gentoo:
emerge --ask app-emulation/qemu
- RHEL/CentOS:
yum install qemu-kvm
- SUSE:
zypper install qemu
当然也可以从源码编译安装,参考QEMU网站的Build instructions。
QEMU为每一种Arch生成一个对应的可执行程序,我们主要关注以下两个程序:
qemu-system-arm: 模拟32位Arm CPU,如Arm9/Arm11、Cortex-A7/A9/A15
qemu-system-aarch64: 模拟64位Arm CPU,如Cortex-A53/A57
可以通过qemu-system-arm -machine help
来查看支持哪些开发板的模拟。
这里我们使用 vexpress-a9 这款开发板。vexpress-a9 是 Arm 公司自己设计的一款 4 核 Cortex-A9 开发板,U-Boot、Linux Kernel 和 QEMU 对这款开发板都做了完整的支持。
安装交叉编译器
请参照这篇文章安装所需的交叉编译工具链:
编译U-Boot
下载源码
1 | git clone https://gitlab.denx.de/u-boot/u-boot.git |
编译
1 | make vexpress_ca9x4_defconfig |
最终编译生成ELF格式的可执行文件 u-boot 和纯二进制文件u-boot.bin,其中 QEMU 可以启动的为ELF格式的可执行文件 u-boot。
编译文件系统
这里我们使用Buildroot来快速构建一个我们需要的文件系统。
下载源码
1 | git clone git://git.buildroot.net/buildroot |
配置
首先进入menuconfig:
1 | make menuconfig |
Target options
Target Architecture:
ARM (little endian)
大部分 Arm 都是小端模式,所以选上little endian。
Target Architecture Variant:
cortex-A9
这款开发板的 CPU 是 cortex-A9。
Enable VFP extension support:
enable
Target ABI:
EABIhf
我们将使用Linaro GCC进行编译,Linaro的GCC默认都打开了hardfloat的支持,所以选上VFP extension和EABIhf。
Build options
Location to save buildroot config:
ca9_mini_defconfig
该选项是设置最后生成的配置文件的保存路径,buildroot可以针对不同的板子生成特定的defconfig文件,默认保存在configs目录下。自己修改各项配置后,执行make savedefconfig命令,就会生成新的defconfig文件。下次编译之前,可以直接执行
make ca9_mini_defconfig
命令来加载已有的配置。Download dir
该选项设置 buildroot 下载的各种第三方包的存储路径,默认在 dl 目录下。
Toolchain
Toolchain type:
External toolchain
Toolchain:
Custom toolchain
因为这里使用电脑上自己安装的toolchain,所以我们这里选External toolchain和Custom toolchain。
Toolchain path:
/usr/local/toolchain/gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf
然后在Toolchain path中填写toolchian在电脑上安装的位置。
Toolchain prefix:
$(ARCH)-none-linux-gnueabihf
另外要注意Toolchain prefix这个前缀别写错。
External toolchain gcc version:
10.x
设置toolchain的版本。
External toolchain kernel headers series:
4.20.x
设置用来编译这个toolchain的内核头文件的内核的版本。这个版本可以在toolchain里面的version.h这个文件查到,打开这个文件:
1
2
3{toolchain_location}/arm-linux-gnueabihf/libc/usr/include/linux/version.h
define LINUX_VERSION_CODE 267277267277对应的16进制为0x4140D,如果版本号为M.m.p,那么
1
2
3M = ( LINUX_VERSION_CODE >> 16 ) & 0xFF /* 0x04 = 4 */
m = ( LINUX_VERSION_CODE >> 8 ) & 0xFF /* 0x14 = 20 */
p = ( LINUX_VERSION_CODE >> 0 ) & 0xFF /* 0x0D = 13 */所以我们设置为4.20.x。
External toolchain C library:
glibc/eglibc
System configuration
Run a getty (login prompt) after boot
TTY port:
ttyAMA0
vexpress_a9内核启动的控制台的名字叫做ttyAMA0。
Filesystem images
cpio the root filesystem:
enable
- Compression method:
lz4
我们把编译的rootfs以initramfs的形式和Linux Kernel链接在一起,为了让根文件系统镜像尽量小,可以对文件系统采用lz4压缩。
- Compression method:
编译
1 | make |
编译内核
下载源码
1 | git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git |
配置
把前面buildroot编译的rootfs.cpio.lz4拷贝到Linux Kernel根目录下:
1 | cp {buildroot_dir}/output/images/rootfs.cpio.lz4 ./ |
然后加载vexpress_a9这块开发板的默认配置,并进入menuconfig做进一步配置:
1 | make ARCH=arm vexpress_defconfig |
General setup
- Initial RAM filesystem and RAM disk (initramfs/initrd) support:
enable
- Initramfs source file(s):
rootfs.cpio.lz4
- Initramfs source file(s):
- Initial RAM filesystem and RAM disk (initramfs/initrd) support:
Kernel hacking
printk and dmesg options
Show timing information on printks:
enable
这样打印的内核 log 前面会附带有时间戳信息。
编译
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8 |
启动QEMU
QEMU可以模拟sd卡等外设。我们就把编译好的固件放在一个模拟的sd卡上,让QEMU从这张模拟的sd卡上启动Linux系统。
制作sd卡镜像,并将它格式化成fat格式
1
2dd if=/dev/zero of=sd.img bs=4096 count=4096
mkfs.vfat sd.img把编译好的kernel zImage和DTB文件拷贝到sd.img中
1
2
3
4sudo mount sd.img /mnt/ -o loop,rw
sudo cp arch/arm/boot/zImage /mnt/
sudo cp arch/arm/boot/dts/vexpress-v2p-ca9.dtb /mnt/
sudo umount /mnt在QEMU中启动U-Boot
1
qemu-system-arm -M vexpress-a9 -m 512M -kernel ${u-boot_dir}/u-boot -nographic -sd sd.img
从sd卡中加载Linux Kernel和DTB
1
2fatload mmc 0:0 0x62008000 zImage
fatload mmc 0:0 0x64008000 vexpress-v2p-ca9.dtb这里面的0x62008000和0x64008000分别对应zImage和dtb文件在内存中的加载地址。我们可以在
arch/arm/Makefile
里面搜索textofs
:1
141 textofs-y := 0x00008000
这个
textofs
定义的就是Linux Kernel zImage执行地址对应的内存偏移地址,默认偏移为0x8000。在U-Boot命令行中输入
bdinfo
命令,可以查到这块开发板内存的起始地址:1
2
3
4
5=> bdinfo
boot_params = 0x60002000
DRAM bank = 0x00000000
-> start = 0x60000000
-> size = 0x20000000可以看到这块开发板的内存其实地址为0x60000000,所以对应内核的起始地址为:0x62008000。DTB的加载地址没有特别的要求,一般注意和 Linux Kernel Image 避开,不要重叠即可。
通过
bootz
命令启动Linux Kernel1
bootz 0x62008000 - 0x64008000