为了能够更好的学习嵌入式Linux内核,本文基于qemu-system-arm工具模拟ARM公司的VersatileExpress硬件环境,基于buildroot工具制作rootfs,并且扩展了buildroot,增加内核配置管理,定制rootfs等功能。
我的上一篇文章,基于Buildroot的rootfs制作,讲述了如何基于Buildroot制作一个rootfs,所以,本文对于该部分进行了省略,有需要的请移步到这篇文章。
准备工具
- 安装qemu
Ubuntu安装qemu比较简单,一条命令搞定. $ sudo apt install qemu libncurses5-dev build-essential
- 构建Git版本库
$sudo apt install git $mkdir ~/work/Linux/LinuxVersatile $ git init
- 基于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)PATH和BR2_EXTERNAL_(NAME)_PATH和BR2\_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/package1和BR2_EXTERNAL_BAR_42_PATH/package/package1和BR2EXTERNALBAR42PATH/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带来的技术盛宴把!