buildroot是Linux平台上一個構建嵌入式Linux系統的框架。整個Buildroot是由Makefile腳本和Kconfig配置文件構成的。你可以和編譯Linux內核一樣,通過buildroot配置,menuconfig修改,編譯出一個完整的可以直接燒寫到機器上運行的Linux系統軟件(包含boot、kernel、rootfs以及rootfs中的各種庫和應用程序)。
使用buildroot搭建基於qemu的虛擬開發平台,參考《通過buildroot+qemu搭建ARM-Linux虛擬開發環境》。
1. buildroot入門
首先如何使用buildroot,1.選擇一個defconfig;2.根據需要配置buildroot;3.編譯buildroot;4.在qemu或者目標板上運行buildroot構建的系統。
1.1 buildroot目錄介紹
進入buildroot首先映入眼簾的是一系列目錄,簡要介紹如下:
. ├── arch: 存放CPU架構相關的配置腳本,如arm/mips/x86,這些CPU相關的配置,在制作工具鏈時,編譯uboot和kernel時很關鍵. ├── board ├── boot ├── CHANGES ├── Config.in ├── Config.in.legacy ├── configs: 放置開發板的一些配置參數. ├── COPYING ├── DEVELOPERS ├── dl: 存放下載的源代碼及應用軟件的壓縮包. ├── docs: 存放相關的參考文檔. ├── fs: 放各種文件系統的源代碼. ├── linux: 存放着Linux kernel的自動構建腳本. ├── Makefile ├── Makefile.legacy ├── output: 是編譯出來的輸出文件夾. │ ├── build: 存放解壓后的各種軟件包編譯完成后的現場. │ ├── host: 存放着制作好的編譯工具鏈,如gcc、arm-linux-gcc等工具. │ ├── images: 存放着編譯好的uboot.bin, zImage, rootfs等鏡像文件,可燒寫到板子里, 讓linux系統跑起來. │ ├── staging │ └── target: 用來制作rootfs文件系統,里面放着Linux系統基本的目錄結構,以及編譯好的應用庫和bin可執行文件. (buildroot根據用戶配置把.ko .so .bin文件安裝到對應的目錄下去,根據用戶的配置安裝指定位置) ├── package:下面放着應用軟件的配置文件,每個應用軟件的配置文件有Config.in和soft_name.mk。 ├── README ├── support ├── system └── toolchain
1.2 buildroot配置
通過make xxx_defconfig來選擇一個defconfig,這個文件在config目錄下。
然后通過make menuconfig進行配置。
Target options --->選擇目標板架構特性。 Build options --->配置編譯選項。 Toolchain ---> 配置交叉工具鏈,使用buildroot工具鏈還是外部提供。 System configuration ---> Kernel ---> Target packages ---> Filesystem images ---> Bootloaders ---> Host utilities ---> Legacy config options --->
1.3 make命令使用
通過make help可以看到buildroot下make的使用細節,包括對package、uclibc、busybox、linux以及文檔生成等配置。
Cleaning: clean - delete all files created by build distclean - delete all non-source files (including .config) Build: all - make world toolchain - build toolchain Configuration: menuconfig - interactive curses-based configurator--------------------------------對整個buildroot進行配置 savedefconfig - Save current config to BR2_DEFCONFIG (minimal config)----------------保存menuconfig的配置 Package-specific:-------------------------------------------------------------------------------對package配置 <pkg> - Build and install <pkg> and all its dependencies---------------------單獨編譯對應APP <pkg>-source - Only download the source files for <pkg> <pkg>-extract - Extract <pkg> sources <pkg>-patch - Apply patches to <pkg> <pkg>-depends - Build <pkg>'s dependencies <pkg>-configure - Build <pkg> up to the configure step <pkg>-build - Build <pkg> up to the build step <pkg>-show-depends - List packages on which <pkg> depends <pkg>-show-rdepends - List packages which have <pkg> as a dependency <pkg>-graph-depends - Generate a graph of <pkg>'s dependencies <pkg>-graph-rdepends - Generate a graph of <pkg>'s reverse dependencies <pkg>-dirclean - Remove <pkg> build directory-----------------------------------------清除對應APP的編譯目錄 <pkg>-reconfigure - Restart the build from the configure step <pkg>-rebuild - Restart the build from the build step--------------------------------單獨重新編譯對應APP busybox: busybox-menuconfig - Run BusyBox menuconfig uclibc: uclibc-menuconfig - Run uClibc menuconfig linux: linux-menuconfig - Run Linux kernel menuconfig-----------------------------------------配置Linux並保存設置 linux-savedefconfig - Run Linux kernel savedefconfig linux-update-defconfig - Save the Linux configuration to the path specified by BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE Documentation: manual - build manual in all formats manual-pdf - build manual in PDF graph-build - generate graphs of the build times----------------------------------對編譯時間、編譯依賴、文件系統大小生成圖標 graph-depends - generate graph of the dependency tree graph-size - generate stats of the filesystem size
2. buildroot框架
Buildroot提供了函數框架和變量命令框架(下一篇文章將介紹細節),采用它的框架編寫的app_pkg.mk這種Makefile格式的自動構建腳本,將被package/pkg-generic.mk 這個核心腳本展開填充到buildroot主目錄下的Makefile中去。
最后make all執行Buildroot主目錄下的Makefile,生成你想要的image。 package/pkg-generic.mk中通過調用同目錄下的pkg-download.mk、pkg-utils.mk文件,已經幫你自動實現了下載、解壓、依賴包下載編譯等一系列機械化的流程。
你只要需要按照格式寫Makefile腳app_pkg.mk,填充下載地址,鏈接依賴庫的名字等一些特有的構建細節即可。 總而言之,Buildroot本身提供構建流程的框架,開發者按照格式寫腳本,提供必要的構建細節,配置整個系統,最后自動構建出你的系統。
對buildroot的配置通過Config.in串聯起來,起點在根目錄Config.in中。
配置選項 | Config.in位置 | |
Target options | arch/Config.in | |
Build options | Config.in | |
Toolchain | toolchain/Config.in | |
System configuration | system/Config.in | |
Kernel | linux/Config.in | |
Target packages | package/Config.in | |
Target packages->Busybox | ||
Filesystem images | fs/Config.in | |
Bootloaders | boot/Config.in | |
Host utilities | package/Config.in.host | |
Legacy config options | Config.in.legacy | |
3. 配置Linux Kernel
對Linux內核的配置包括兩部分:通過make menuconfig進入Kernel對內核進行選擇,通過make linux-menuconfig對內核內部進行配置。
3.1 選擇Linux內核版本
如下“Kernel version”選擇內核的版本、“Defconfig name”選擇內核config文件、“Kernel binary formant”選擇內核格式、“Device tree source file names”選擇DT文件,
在“Linux Kernel Tools”中選擇內核自帶的工具,比如perf。
可以選擇“Custom Git repository”來指定自己的Git庫,在“Custom repository version”中指定branch名稱。
選擇“Using an in-tree defconfig file”,在“Defconfig name”中輸入defconfig名稱,注意不需要末尾_defconfig。
選擇“Use a device tree present in the kernel”,在“Device Tree Source file names”中輸入dts名稱,不需要.dts擴展名。
3.1.1 Kernel binary format
可以選擇vmlinux或者uImage。
uImage是uboot專用的映像文件,它是在zImage之前加上一個長度為64字節的“頭”,說明這個內核的版本、加載位置、生成時間、大小等信息;其0x40之后與zImage沒區別。
zImage是ARM Linux常用的一種壓縮映像文件,uImage是U-boot專用的映像文件,它是在zImage之前加上一個長度為0x40的“頭”,說明這個映像文件的類型、加載位置、生成時間、大小等信息。
vmlinux編譯出來的最原始的內核elf文件,未壓縮。
zImage是vmlinux經過objcopy gzip壓縮后的文件, objcopy實現由vmlinux的elf文件拷貝成純二進制數據文件。
uImage是U-boot專用的映像文件,它是在zImage之前加上一個長度為0x40的tag。
選擇vmlinux和uImage的區別在於:
PATH="/bin..." BR_BINARIES_DIR=/home/.../output/images /usr/bin/make -j9 HOSTCC="/usr/bin/gcc" HOSTCFLAGS="" ARCH=csky INSTALL_MOD_PATH=/home/.../output/target CROSS_COMPILE="/home/.../output/host/bin/csky-abiv2-linux-" DEPMOD=/home/.../output/host/sbin/depmod INSTALL_MOD_STRIP=1 -C /home/.../linux uImage
如果是vmlinux,在結尾就是vmlinux。
3.2 對Kernel進行配置
通過make linux-menuconfig可以對內核內部細節進行配置。
讓Linux內核帶符號表:
# CONFIG_COMPILE_TEST is not set
CONFIG_DEBUG_INFO=y
4. 配置文件系統APP
對目標板文件系統內容進行配置主要通過make menuconfig進入Target packages進行。
在Filesystem images中配置文件系統采用的格式,以及是否使用RAM fs。
4.1 ramfs
如果選中“initial RAM filesystem linked into linux kernel”,那么文件系統會集成到vmlinux中。
如不選中,則vmlinux中只包括內核,文件系統會以其他形似提供,比如rootfs.cpio。
如果定義了BR2_TARGET_ROOTFS_INITRAMFS,那么在編譯的末期需要重新編譯內核,將rootfs.cpio加入到vmlinux中。
fs/initramfs/initramfs.mk中:
rootfs-initramfs: linux-rebuild-with-initramfs rootfs-initramfs-show-depends: @echo rootfs-cpio .PHONY: rootfs-initramfs rootfs-initramfs-show-depends ifeq ($(BR2_TARGET_ROOTFS_INITRAMFS),y) TARGETS_ROOTFS += rootfs-initramfs endif
在linux/linux.mk中:
.PHONY: linux-rebuild-with-initramfs linux-rebuild-with-initramfs: $(LINUX_DIR)/.stamp_target_installed linux-rebuild-with-initramfs: $(LINUX_DIR)/.stamp_images_installed linux-rebuild-with-initramfs: rootfs-cpio linux-rebuild-with-initramfs: @$(call MESSAGE,"Rebuilding kernel with initramfs") # Build the kernel. $(LINUX_MAKE_ENV) $(MAKE) $(LINUX_MAKE_FLAGS) -C $(LINUX_DIR) $(LINUX_TARGET_NAME) $(LINUX_APPEND_DTB) # Copy the kernel image(s) to its(their) final destination $(call LINUX_INSTALL_IMAGE,$(BINARIES_DIR)) # If there is a .ub file copy it to the final destination test ! -f $(LINUX_IMAGE_PATH).ub || cp $(LINUX_IMAGE_PATH).ub $(BINARIES_DIR)
在打開initramfs的情況下,重新將rootfs.cpio編譯進內核vmlinxu中。
然后將uImage之類的文件拷貝到BINARIES_DIR中。
5. 添加自己的APP
要添加自己的本地APP, 首先在package/Config.in中添加指向新增APP目錄的Config.in;
然后在package中新增目錄helloworld,並在里面添加Config.in和helloworld.mk;
最后添加對應的helloworld目錄。
5.1 添加package/Config.in入口
系統在make menuconfig的時候就可以找到對應的APP的Config.in。
diff --git a/package/Config.in b/package/Config.in index 43d75a9..6ef9fad 100644 --- a/package/Config.in +++ b/package/Config.in @@ -1868,5 +1868,8 @@ menu "Text editors and viewers" source "package/uemacs/Config.in" source "package/vim/Config.in" endmenu +menu "Private package" + source "package/helloworld/Config.in" +endmenu
如果在make menuconfig的時候選中helloworld,在make savedefconfig的時候就會打開BR2_PACKAGE_HELLOWORLD=y。
5.2 配置APP對應的Config.in和mk文件
helloworld/Config.in文件,通過make menuconfig可以對helloworld進行選擇。
只有在BR2_PACKAGE_HELLOWORLD=y條件下,才會調用helloworld.mk進行編譯。
config BR2_PACKAGE_HELLOWORLD bool "helloworld" help This is a demo to add local app.
buildroot編譯helloworld所需要的設置helloworld.mk,包括源碼位置、安裝目錄、權限設置等。
下面的HELLOWORLD的開頭也是必須的。
################################################################################ # # helloworld # ################################################################################ HELLOWORLD_VERSION:= 1.0.0 HELLOWORLD_SITE:= $(CURDIR)/work/helloworld HELLOWORLD_SITE_METHOD:=local HELLOWORLD_INSTALL_TARGET:=YES define HELLOWORLD_BUILD_CMDS $(MAKE) CC="$(TARGET_CC)" LD="$(TARGET_LD)" -C $(@D) all endef define HELLOWORLD_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/helloworld $(TARGET_DIR)/bin endef define HELLOWORLD_PERMISSIONS /bin/helloworld f 4755 0 0 - - - - - endef $(eval $(generic-package))
如果源碼在git上,需要如下設置:
DMA_TEST_VERSION:=master--------------------------------------倉庫分支名稱 DMA_TEST_SITE:=http://.../dma.git-----------------------------倉庫git地址 DMA_TEST_SITE_METHOD:=git-------------------------------------獲取源碼的方式
_VERSION結尾的變量是源碼的版本號;_SITE_METHOD結尾的變量是源碼下載方法;_SITE結尾變量是源碼下載地址。
_BUILD_CMDS結尾的變量會在buildroot框架編譯的時候執行,用於給源碼的Makefile傳遞編譯選項和鏈接選項,調用源碼的Makefile。
_INSTALL_TARGET_CMDS結尾的變量是在編譯完之后,自動安裝執行,一般是讓buildroot把編譯出來的的bin或lib拷貝到指定目錄。
$(eval$(generic-package)) 最核心的就是這個東西了,一定不能夠漏了,不然源碼不會被編譯,這個函數就是把整個.mk構建腳本,通過Buildroot框架的方式,展開到Buildroot/目錄下的Makfile中,生成的構建目標(構建目標是什么,還記得Makefile中的定義嗎?)。
5.3 編寫APP源碼
簡單的編寫一個helloworld.c文件:
#include <stdio.h> void main(void) { printf("Hello world.\n"); }
然后編寫Makefile文件:
CPPFLAGS += LDLIBS += all: helloworld analyzestack: helloworld.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) clean: rm -f *.o helloworld .PHONY: all clean
5.4 通過make menuconfig選中APP
通過Target packages -> Private package進入,選中helloworld。
然后make savedefconfig,對helloworld的配置就會保存到qemu_arm_vexpress_defconfig中。
5.5 編譯APP
可以和整個平台一起編譯APP;或者make helloworld單獨編譯。
這兩個文件在選中此APP之后,都會被拷貝到output/build/helloworld-1.0.0文件夾中。
然后生成的bin文件拷貝到output/target/bin/helloworld,這個文件會打包到文件系統中。
如果需要清空相應的源文件,通過make helloworld-dirclean。
5.6 運行APP
在shell中輸入helloworld,可以得到如下結果。
添加APP工作完成。
6. uboot配置
使用uboot作為bootloader,需要進行一些配置。
在選中U-boot作為bootloader之后,會彈出一系列相關配置。
“U-Boot board name”配置configs的defconfig名稱。
“U-Boot Version”選擇Custom Git repository,然后在“URL of custom repository”中選擇自己的git地址,並在“Custom repository version”中選擇git的分支。
在“U-Boot binary format”中選擇想要輸出的image格式,比如u-boot.img或者u-image.bin。
還可以選擇“Intall U-Boot SPL binary image”,選擇合適的SPL。
7. Finalizing target
在buildroot編譯的末期,需要對編譯結果進行一些檢查或者其他操作。
buildroot預留了兩個接口:
BR2_ROOTFS_OVERLAY - 指向一個目錄,此目錄下的所有文件將會覆蓋到output/target下。比如一些配置文件,或者預編譯的庫等可以在此階段處理。
BR2_ROOTFS_POST_BUILD_SCRIPT - 一個腳本,更加復雜的對文件進行刪除、重命名、strip等等功能。
BR2_ROOTFS_POST_IMAGE_SCRIPT - 對最終生成的images進行打包處理等。
7.1 FS Overlay
有些應用或者配置不通過編譯,直接采取拷貝的方式集成到rootfs中,可以設置“System configuration”->“Root filesystem overlay directories”。
設置的目錄中的內容,會對output/target進行覆蓋。
相關處理在Makefile中如下:
@$(foreach d, $(call qstrip,$(BR2_ROOTFS_OVERLAY)), \ $(call MESSAGE,"Copying overlay $(d)"); \ rsync -a --ignore-times --keep-dirlinks $(RSYNC_VCS_EXCLUSIONS) \ --chmod=u=rwX,go=rX --exclude .empty --exclude '*~' \ $(d)/ $(TARGET_DIR)$(sep))
7.2 post build
除了fs overlay這種方式,buildroot還提供了一個腳本進行更加復雜的處理。
可以進行文件刪除、重命名,甚至對帶調試信息的文件進行strip等。
@$(foreach s, $(call qstrip,$(BR2_ROOTFS_POST_BUILD_SCRIPT)), \ $(call MESSAGE,"Executing post-build script $(s)"); \ $(EXTRA_ENV) $(s) $(TARGET_DIR) $(call qstrip,$(BR2_ROOTFS_POST_SCRIPT_ARGS))$(sep))
一個post_build.sh范例,對一系列文件進行刪除和strip操作:
#!/bin/sh #set -x set +o errexit cp -a ${BINARIES_DIR}/deepeye1000e_hk.dtb ${BINARIES_DIR}/deepeye1000.dtb #Strip files in tbc_lists.txt. tbc means 'to be stripped'. STRIP=${HOST_DIR}/bin/csky-abiv2-linux-strip for file in `cat ${BR2_EXTERNAL_INTELLIF_PATH}/board/deepeye1000e_hk/tbs_lists.txt` do if [ -e ${TARGET_DIR}${file} ]; then echo Strip ${file}. ${STRIP} ${TARGET_DIR}${file} else echo Not found ${file}. fi done #Delete files in tbd_lists.txt. tbd means 'to be deleted' for file in `cat ${BR2_EXTERNAL_INTELLIF_PATH}/board/deepeye1000e_hk/tbd_lists.txt` do if [ -e ${TARGET_DIR}${file} ]; then echo Delete ${file}. rm ${TARGET_DIR}${file} else echo Not found ${file}. fi done ${BR2_EXTERNAL_INTELLIF_PATH}/board/common/post_build.sh
7.2 post image
post image在post build之后,更傾向於生成完整的release文件。包括進行一些images打包、debug文件打包等等。
.PHONY: target-post-image target-post-image: $(TARGETS_ROOTFS) target-finalize @$(foreach s, $(call qstrip,$(BR2_ROOTFS_POST_IMAGE_SCRIPT)), \ $(call MESSAGE,"Executing post-image script $(s)"); \ $(EXTRA_ENV) $(s) $(BINARIES_DIR) $(call qstrip,$(BR2_ROOTFS_POST_SCRIPT_ARGS))$(sep))
一個范例如下,對images文件進行打包操作。
#!/bin/sh set -x -e IMG_DIR=output/images DEBUG_DIR=${IMG_DIR}/debug KERNEL_DIR=output/build/linux-master ROOTFS_CPIO=${IMG_DIR}/rootfs.cpio KERNEL_IMAGE=${IMG_DIR}/uImage SPL_IMAGE=${IMG_DIR}/u-boot-spl-bh.bin UBOOT_IMAGE=${IMG_DIR}/u-boot.bin IMG_TAR=images.tar.gz DEBUG_TAR=debug.tar.gz IMG_MD5=images.md5 rm -f ${IMG_TAR} ${DEBUG_TAR} ${IMG_MD5} mkdir -p ${DEBUG_DIR} cp -a ${KERNEL_DIR}/vmlinux ${KERNEL_DIR}/System.map ${ROOTFS_CPIO} ${DEBUG_DIR}/ tar -czf ${IMG_TAR} ${KERNEL_IMAGE} ${SPL_IMAGE} ${UBOOT_IMAGE} tar -czf ${DEBUG_TAR} -C ${IMG_DIR} debug/ md5sum ${IMG_TAR} > ${IMG_MD5}
8. buildroot編譯性能
buildroot還提供了一些命令,用於分析buildroot編譯過程中耗時、依賴關系、文件系統尺寸等等。
通過make help發現相關命令:
Documentation: manual - build manual in all formats manual-html - build manual in HTML manual-split-html - build manual in split HTML manual-pdf - build manual in PDF manual-text - build manual in text manual-epub - build manual in ePub graph-build - generate graphs of the build times graph-depends - generate graph of the dependency tree graph-size - generate stats of the filesystem size list-defconfigs - list all defconfigs (pre-configured minimal systems)
8.1 編譯耗時
執行make graph-build會生成如下文件:
其中比較有參考意義的文件是build.hist-duration.pdf文件,按照耗時從大到小排列。
通過此圖可以明白整個編譯流程時間都耗在哪里,針對性進行分析優化,有利於提高編譯效率。
8.2 編譯依賴關系
生成graph-depends.pdf,可以看出各個編譯模塊之間的依賴關系。
buildroot的庫會根據依賴關系被自動下載,通過此圖也可以了解某些某塊被誰依賴。
8.3 編譯結果尺寸分析
通過graph-size.pdf文件可以對整個編譯結果組成有個大概理解。
另外更有參考意義的是file-size-stats.csv和package-size-stats.csv文件。
通過file和package兩個視角,更加詳細的了解整個rootfs空間都被那些文件占用。