如何基于Buildroot和Qemu搭建嵌入式Linux系统环境 | 8月更文挑战

1,058 阅读5分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

为了能够更好的学习嵌入式Linux内核,本文基于qemu-system-arm工具模拟ARM公司的VersatileExpress硬件环境,基于buildroot工具制作rootfs,并且扩展了buildroot,增加内核配置管理,定制rootfs等功能。

我的上一篇文章,基于Buildroot的rootfs制作,讲述了如何基于Buildroot制作一个rootfs,所以,本文对于该部分进行了省略,有需要的请移步到这篇文章。

准备工具

  1. 安装qemu
Ubuntu安装qemu比较简单,一条命令搞定.

$ sudo apt install qemu libncurses5-dev build-essential
  1. 构建Git版本库
$sudo apt install git

$mkdir ~/work/Linux/LinuxVersatile

$ git init

  1. 基于LinuxVersatile创建buildroot版本库

由于buildroot也是基于Git管理的,所以我们使用git submodule完成对buildroot的管理.

$ git submodule add git://git.busybox.net/buildroot

更新buildroot

$ git submodule update --init

基于builroot定制项目

一般情况下,对于一个特定的项目,我们要做的可能包括如下几方面:

  • 配置buildroot(比如,编译选项,toolchain,bootloader,kernel,根文件系统以及image的类型等).

  • 配置其他组件,比如linux内核、bootloader以及busybox.

  • 定制目标根文件系统.

    • 覆盖目标文件系统的某些文件(使用BR2_ROOTFS_OVERLAY).

    • 修改或者删除目标文件系统中的某些文件,或者执行默写shell命令(使用BR2_ROOTFS_POST_BUILD_SCRIPT).

    • 在生成images文件之前,执行任何命令(使用BR2_ROOTFS_POST_BUILD_SCRIPT).

    • 设置文件的访问权限和所有者(使用BR2_ROOTFS_DEVICE_TABLE).

    • 增加特定的设备文件(使用BR2_ROOTFS_STATIC_DEVICE_TABLE).

  • 增加特定的用户(使用BR2_ROOTFS_USERS_TABLES).

  • 在生成文件系统images之后,执行任何命令(使用BR2_ROOTFS_POST_IMAGE_SCRIPT).

  • 为一些组件增加特定项目需要的补丁(使用BR2_GLOBAL_PATCH_DIR).

  • 增加特定项目需要的组件.

  • 增加特定项目需要的组件.

推荐的目录树结构

理论上,基于buildroot构建一个项目时,可以创建任何文件和目录。buildroot开发者推荐了下面的文件结构,我们可以在buildroot主目录里面创建这些文件,也可以在通过br2-external tree在 buildroot主目录之外创建下面的目录树.

+-- board/
|   +-- <company>/
|       +-- <boardname>/
|           +-- linux.config
|           +-- busybox.config
|           +-- <other configuration files>
|           +-- post_build.sh
|           +-- post_image.sh
|           +-- rootfs_overlay/
|           |   +-- etc/
|           |   +-- <some file>
|           +-- patches/
|               +-- foo/
|               |   +-- <some patch>
|               +-- libbar/
|                   +-- <some other patches>
|
+-- configs/
|   +-- <boardname>_defconfig
|
+-- package/
|   +-- <company>/
|       +-- Config.in (if not using a br2-external tree)
|       +-- <company>.mk (if not using a br2-external tree)
|       +-- package1/
|       |    +-- Config.in
|       |    +-- package1.mk
|       +-- package2/
|           +-- Config.in
|           +-- package2.mk
|
+-- Config.in (if using a br2-external tree)
+-- external.mk (if using a br2-external tree)
+-- external.desc (if using a br2-external tree)

本文基于上述目录树结构来i构建项目,并且使用br2-extern tree这种构建特定项目的模式。需要的注意的是,如果我们选择br2-extern tree模式,上述文件结构中的company和boardname显得就有些多余了.

br2-external tree模式

面一节介绍了如何构建特定项目的目录树,并且指定本项目使用br2-extern tree模式,下面介绍一下如何创建该目录树,并且说明一下如何实现相关的配置文件。需要思考的一个问题是,特定的项目 如何与buildroot进行集成呢?其实很简单,我们只需通过 BR2_EXTERNAL将目录树的位置通过make参数的形式传递给buildroot即可,待到整个项目结构搭建完成之后,我们就会看到这种集成方式。

buildroot/$ make BR2_EXTERNAL=/path/to/foo menuconfig
注意,执行maked的目录为buirdroot的根目录。而且,我们可以再次执行上述命令从而导入另一个项目的目录树位置。

br2-external tree布局

br2-external tree必须包含下面几个文件:

  • external.desc

  • external.mk

  • Config.in

除去上面几个必须的文件之外,其他的文件或目录可以按照需要自行添加,后续会给出一个具体的br2-externnal tree布局示例。

下面详细介绍一下,上面三个主要文件的含义。

external.desc

该文件主要用于说明br2-external tree的名字和基本描述。

该文件的基本格式是:每行表示一个具体描述项,每行的开头是一个关键字,后面跟一个冒号,然后是若干个空格,最后就是一个用于描述关键字的字符串。主要包括两个关键字:

  • name,必须定义的描述项,用于描述br2-external tree的名字。name的命名字符必须是[A-Za-z0-9_],其他的字符不允许出现。Buildroot会设置BR2_EXTERNAL_$(NAME)_PATH环境变量,用于指定br2-exteranl tree的绝对位置,所以,我们可以在自己的br2-external tree中使用该环境变量。同时,因为buildroot可以同时引用多个br2-extreanl tree,所以,这里在命名name时,应该防止与其他的br2-exteranal tree冲突。

  • desc,可选的,用一段简短的话,来描述一下该br2-exteranal tree。buildroot使用环境变量BR2_EXTERNAL_$(NAME)_DESC来指定该描述。

本项目的上述两个关键字定义如下:

name:LinuxVersatile desc: Linux For QEMU ARM Versatile

注意:BR2_EXTERNAL_(NAME)PATHBR2_EXTERNAL_(NAME)_PATH和BR2\_EXTERNAL\_(NAME)_DESC两个环境变量对于:Kconfig、Makefile、post-build、post-image和in-fakeroot scripts都是可见的,在上述文件中我们可以直接使用上面两个环境变量。

Config.in和external.mk

这两个文件主要用于定义如何配置和编译项目相关的组件,其与Buildroot中组件的管理方式类似。我们可以把我们自己的应用程序或者buildroot不支持的第三方库放到这里。如果项目没有特别的组件,那么这个文件为空。

顾名思义,Config.in用于管理组件的配置,externnal.mk用于管理makefile文件。Buildroot会将Config.in包含的所有组件的Config.in,显示到Buildroot的顶级配置菜单里,供用户选择配置,同时,Buildroot也会包含external.mk包含的所有组件的编译配置文件。

Config.in的一般形式如下:

source "$BR2_EXTERNAL_BAR_42_PATH/package/package1/Config.in"
source "$BR2_EXTERNAL_BAR_42_PATH/package/package2/Config.in"

每个组件中同样会包含自己的配置文件:Config.in。

external.mk文件的定义如下:

include $(sort $(wildcard $(BR2_EXTERNAL_BAR_42_PATH)/package/*/*.mk))

而后,在BR2EXTERNALBAR42PATH/package/package1BR2_EXTERNAL_BAR_42_PATH/package/package1和BR2_EXTERNAL_BAR_42_PATH/package/package2中创建组件的副本,并定义相应的Config.in和.mk文件。

也可以在Config.in中定义特殊的配置选项,在external.mk中定义make相关的逻辑。

configs目录

该目录用于保存Buildroot的defconfigs,这个可以定义基于特定项目的defconfig文件,Buildroot可以自动的识别这些文件,并可以通过make list-defconfig展示出来,所以,我们可以使用make _deconfig来加载defconfig。

本项目的配置文件名为qemu_linux_versatile_defconfig,里面保存了该项目相关的配置文件。主要的配置包括硬件平台的定义、交叉编译器的配置、根文件系统相关的配置以及Linux内核相关的配置。

下面为qemu_linux_versatile_defconfig的配置内容:

#Target options 
BR2_arm=y       
BR2_cortex_a9=y 
BR2_ARM_INSTRUCTIONS_THUMB2=y
                
#Build optinons 
BR2_DL_DIR="/home/lhl/develops/linuxCard/businesscard-linux/buildroot/dl"
BR2_JLEVEL=2    
BR2_CCACHE=y    
                
#Toolchain      
BR2_TOOLCHAIN_BUILDROOT_USE_SSP=y
BR2_TOOLCHAIN_BUILDROOT_CXX=y
BR2_PACKAGE_HOST_GDB=y
                
#System Configuration
BR2_TARGET_GENERIC_HOSTNAME="qemu-arm"
BR2_TARGET_GENERIC_ISSUE="Welcome to Linux Versatile!"
BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0"
BR2_SYSTEM_DHCP="eth0"
BR2_ROOTFS_OVERLAY="/home/lhl/work/Linux/LinuxVersatile/board/LinuxVersatile/rootfs_overlay"                                                                                                                                                                                       
BR2_ROOTFS_POST_BUILD_SCRIPT="/home/lhl/work/Linux/LinuxVersatile/board/LinuxVersatile/misc/post-build.sh"
 BR2_ROOTFS_POST_IMAGE_SCRIPT="/home/lhl/work/Linux/LinuxVersatile/board/LinuxVersatile/misc/after-build.sh "
 
#Kernel         
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_GIT=y
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="v5.5.3"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="/home/lhl/work/Linux/LinuxVersatile/board/LinuxVersatile/misc/linux_defconfig"
BR2_LINUX_KERNEL_DTS_SUPPORT=y
BR2_LINUX_KERNEL_CUSTOM_DTS_PATH="/home/lhl/work/Linux/LinuxVersatile/board/LinuxVersatile/misc/linuxVersatile.dts"
                
#Target packages
BR2_PACKAGE_ASCII_INVADERS=y
BR2_PACKAGE_CHOCOLATE_DOOM=y
BR2_PACKAGE_SL=y
                
# Filesystem images
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_LABEL="rootfs"

生成上述配置文件的方式,可以使用BUildroot的make savedefconfig命令,该命令的具体执行方式如下:

#进入buildroot主目录
$cd buildroot

#进行buildroot配置
$ make menuconfig 

#另存buildroot配置文件
$make savedefconfig BR2_DEFCONFIG=/home/lhl/work/Linux/LinuxVersatile/configs/qemu_linux_versatile_defconfig

项目树

按照buildroot的br2-external tree的配置要求,下面是本项目的目录树节结构。

lhl@ubuntu18:~/work/Linux/LinuxVersatile$ tree 
.
├── board
│   └── LinuxVersatile
│       ├── misc
│       │   ├── linux_defconfig
│       │   ├── linuxVersatile.dts
│       │   └── post-build.sh
│       └── rootfs_overlay
├── buildroot
 |... ...
├── Config.in
├── configs
│   └── qemu_linux_versatile_defconfig
├── external.desc
├── external.mk
├── package
└── README.md

7 directories, 8 files

其中,buildroot目录为Buildroot的主目录,因为里面的文件太多,所以并未一一列出。

项目编译

项目的配置文件完成之后,可以进行下一步的编译工作了,切换到buildroot的目录,执行如下命令:

#加载项目配置文件
$ make BR2_EXTERNAL=$PWD/../ qemu_linux_versatile_defconfig

#编译
$ make

qemu\_linux\_versatile_defconfig的配置项BR2_JLEVEL=2 用于标明make执行的并发CPU数量,这里配置的是2。相当于make -j 2参数,不过buildroot不支持该这种使用方式。

因为需要下载相关的组件,所以编译时十分的缓慢,请耐心等待。

测试

编译完成之后,在buildroot的output为编译产生的文件,我们主要关注的是images中的文件。

lhl@ubuntu18:~/work/Linux/LinuxVersatile/buildroot/output/images$ tree
.
├── linuxVersatile.dtb
├── rootfs.ext2
├── rootfs.ext4 -> rootfs.ext2
├── rootfs.tar
├── vexpress-v2p-ca9.dtb
└── zImage

0 directories, 6 files

运行qemu-system-arm命令,测试内核和rootfs的正确性。

$ qemu-system-arm -M vexpress-a9 -smp 2 -m 1024M -kernel ./zImage -append "root=/dev/mmcblk0 rw console=ttyAMA0" -nographic -dtb ./linuxVersatile.dtb -sd ./rootfs.ext4

各个参数的含义如下:

- M:表示硬件平台,本项目为vexpress-a9
-smp:CPU核心数量,本项目为2个
-m:指定内存大小,本项目为1GBytes
-kernel:指定Linux内核文件
-append:指定Linux内核启动时的'cmdline',本项目中的root=/dev/mmcblk0 rw,指定rootfs文件系统保存在mmcblk0设备中,console=ttyAMA0表示内核的启动信息打印到ttyAMA0虚拟终端
-dtb:指定Linux内核相关的dts配置数据
-nograhpic:关闭图形界面信息输出,并将输出信息重定向到串口终端上,本项目就是上面的ttyAMA0虚拟串口
-sd:挂在sd文件,该文件保存了rootfs系统,内核识别的设备文件为mmcblk0

下面为内核启动后进入文件系统

Booting Linux on physical CPU 0x0
Linux version 5.5.3 (lhl@ubuntu18.04) (gcc version 8.3.0 (Buildroot 2020.02-git-01375-g25b1dc4613)) #2 SMP Wed Feb 19 21:24:36 CST 2020
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
OF: fdt: Machine model: V2P-CA9
Memory policy: Data cache writealloc
Reserved memory: created DMA memory pool at 0x4c000000, size 8 MiB
OF: reserved mem: initialized node vram@4c000000, compatible id shared-dma-pool
cma: Reserved 16 MiB at 0x9f000000
percpu: Embedded 19 pages/cpu s45644 r8192 d23988 u77824
Built 1 zonelists, mobility grouping on.  Total pages: 260096
Kernel command line: root=/dev/mmcblk0 rw console=ttyAMA0
printk: log_buf_len individual max cpu contribution: 4096 bytes
printk: log_buf_len total cpu_extra contributions: 12288 bytes
printk: log_buf_len min size: 16384 bytes
printk: log_buf_len: 32768 bytes
printk: early log buf free: 14896(90%)

... .... 

Welcome to Linux Versatile!
qemu-arm login: root
# uname -a
Linux qemu-arm 5.5.3 #2 SMP Wed Feb 19 21:24:36 CST 2020 armv7l GNU/Linux
# cat /etc/os-release 
NAME=Buildroot
VERSION=2020.02-git-01375-g25b1dc4613
ID=buildroot
VERSION_ID=2020.02-git
PRETTY_NAME="Buildroot 2020.02-git"
# 

我们,可以将上述命加到after-build.sh中,这样buildroot执行完之后,就会自动启动qemu,从而测试系统的正确性。

#!/bin/bash
   
 qemu-system-arm -M vexpress-a9 -smp 2 -m 1024M -kernel $BASE_DIR/images/zImage -append "root=/dev/mmcblk0 rw console=ttyAMA0" -nographic -dtb $BASE_DIR/images/linuxVersatile.dtb -sd $BASE_DIR/images/rootfs.ext4

好了,以上就是使用Buildroot来管理Linux内核和根文件系统,然后通过qemu模拟ARM 平台进行相关测试的例子。尽情享受Linux带来的技术盛宴把!