U-Boot 构建系统深度剖析
一、构建系统基础认知
1.1 前置知识储备
深入理解 U-Boot 构建体系需具备:Linux 常用操作、Shell 脚本编写能力以及 Makefile 语法基础。
1.2 Makefile 核心范式
任何复杂的构建系统都源于基础规则。Makefile 的本质结构为:
output: prerequisite1 prerequisite2 ...
command
以下是一个嵌入式固件编译的极简示例:
firmware.bin: startup.o flash.o core.o
arm-none-eabi-ld -T flash.ld -o firmware.elf startup.o core.o
arm-none-eabi-objcopy -O binary -S firmware.elf firmware.bin
startup.o: startup.S
arm-none-eabi-gcc -Wall -c -o startup.o startup.S
flash.o: flash.c hw_register.h
arm-none-eabi-gcc -Wall -c -o flash.o flash.c
core.o: core.c hw_register.h
arm-none-eabi-gcc -Wall -c -o core.o core.c
clean:
rm -f firmware.bin firmware.elf *.o
其核心理念可概括为:产出(目标)由原材料(依赖)经加工流程(命令)转化而来。
基础 Makefile 完成后,为达成模块化裁剪与自动化构建,需引入高级机制:
- 动态目标路径指定
- 自动化依赖生成与条件源码筛选
- 隐式规则、模式匹配及工具链参数化
1.3 U-Boot 构建体系组成
U-Boot 作为跨平台引导程序,需从海量源文件中筛选合适内容,采用匹配策略完成编译链接。这依赖多层级 Makefile 协同运作:
| 组件 | 职能说明 |
|---|---|
| 根目录 Makefile | 全局调控编译链接流程,定义最终产物 u-boot.bin |
| 根目录 config.mk | 统一定义编译规则,被各级 Makefile 引用 |
| 根目录 rules.mk | 负责依赖关系生成,供子目录 Makefile 调用 |
| 各级子目录 Makefile | 管控当前目录的编译与归档行为 |
| 根目录 mkconfig | 编译前执行的环境准备脚本 |
编译过程中生成的关键辅助文件:
| 文件 | 说明 |
|---|---|
| include/config.mk | 由 mkconfig 生成,定义 ARCH、CPU 等全局变量,被根目录 Makefile 包含 |
| 各级 .depend | 子目录 Makefile 生成的依赖描述文件,供同级 Makefile 引用 |
二、目标体系解析
2.1 顶层目标
根目录 Makefile 的 all 伪目标由多个子目标构成:
ALL_TARGETS = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
用户可扩展其他格式目标,如 u-boot.dis、u-boot.img、u-boot.hex 等。执行 make all 将无条件重建所有分目标。
2.2 子目录目标
子目录 Makefile 中典型定义:
all: $(obj).depend $(INIT) $(ARCHIVE)
$(ARCHIVE): $(obj).depend $(COBJS) $(SOBJS)
其中 INIT 为启动相关目标文件,ARCHIVE 为待生成的静态库,COBJS 和 SOBJS 分别对应 C 文件与汇编文件编译产生的 .o 文件。
三、依赖管理机制
3.1 最终产物的依赖构成
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
核心依赖项为 $(OBJS) 与 $(LIBS)。编译流程的正向过程为:源文件(*.c *.S)→ 中间目标(*.o *.a)→ 最终固件(*.bin);而 Make 的解析方向恰好相反。
3.2 平台适配与源码裁剪
U-Boot 支持 ARM、PowerPC 等多种架构及 VxWorks、Linux 等操作系统,源码包含 lib_ppc、lib_arm 等平台相关目录。实际编译时需针对特定硬件进行裁剪。
执行 make smdk2410_config 等配置命令后,mkconfig 脚本生成 include/config.mk,其中定义:
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
根目录 Makefile 包含此文件后,依据变量值选择参与编译的目录,如 lib_arm/、board/smdk2410、cpu/arm920t 等。
外设驱动的裁剪则通过 C 预处理实现,配置文件位于 include/configs/<board_name>.h。例如启用 CS8900 网卡:
#define CONFIG_DRIVER_CS8900 1
3.3 依赖变量的动态组装
OBJS = cpu/$(CPU)/start.o
ifeq ($(CPU),i386)
OBJS += cpu/$(CPU)/start16.o
OBJS += cpu/$(CPU)/reset.o
endif
ifeq ($(CPU),ppc4xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc83xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc86xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),bf533)
OBJS += cpu/$(CPU)/start1.o cpu/$(CPU)/interrupt.o cpu/$(CPU)/cache.o
OBJS += cpu/$(CPU)/cplbhdlr.o cpu/$(CPU)/cplbmgr.o cpu/$(CPU)/flush.o
endif
LIBS = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
3.4 子目录依赖追踪
源文件与头文件的变更需触发对应目标重建。GCC 提供 -M 系列选项自动生成依赖描述。U-Boot 通过 rules.mk 在每个子目录构建 .depend 文件:
#########################################################################
_depend: $(obj).depend
$(obj).depend: $(src)Makefile $(TOPDIR)/config.mk $(SRCS)
@rm -f $@
@for f in $(SRCS); do \
g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \
$(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
done
#########################################################################
四、编译规则详解
4.1 顶层链接规则
all: $(ALL_TARGETS)
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(obj)u-boot.img: $(obj)u-boot.bin
./tools/mkimage -A $(ARCH) -T firmware -C none \
-a $(TEXT_BASE) -e 0 \
-n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
sed -e 's/"[ ]*$$/ for $(BOARD) board"/') \
-d $< $@
$(obj)u-boot.dis: $(obj)u-boot
$(OBJDUMP) -d $< > $@
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
$(OBJS):
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
$(LIBS):
$(MAKE) -C $(dir $(subst $(obj),,$@))
$(SUBDIRS):
$(MAKE) -C $@ all
$(NAND_SPL): version
$(MAKE) -C nand_spl/board/$(BOARDDIR) all
$(U_BOOT_NAND): $(NAND_SPL) $(obj)u-boot.bin
cat $(obj)nand_spl/u-boot-spl-16k.bin $(obj)u-boot.bin > $(obj)u-boot-nand.bin
version:
@echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
$(TOPDIR)) >> $(VERSION_FILE); \
echo "\"" >> $(VERSION_FILE)
depend dep:
for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
$(obj)System.map: $(obj)u-boot
@$(NM) $< | \
grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.ng$$\)\|\(LASH[RL]DI\)' | \
sort > $(obj)System.map
4.2 子目录编译规则
模式规则定义于根目录 config.mk:
$(obj)%.s: %.S
$(CPP) $(AFLAGS) -o $@ $<
$(obj)%.o: %.S
$(CC) $(AFLAGS) -c -o $@ $<
$(obj)%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
静态库归档规则在子目录 Makefile 中定义,以 board/smdk2410/libsmdk2410.a 为例:
$(ARCHIVE): $(obj).depend $(COBJS) $(SOBJS)
$(AR) $(ARFLAGS) $@ $(COBJS) $(SOBJS)
关键提示: config.mk 是理解编译选项、链接参数及生成规则的核心文件,分析时需重点关注。
五、完整构建流程
5.1 解析逻辑
生成 u-boot.bin 需先构建其依赖的各库文件与目标文件,这要求递归进入各子目录执行本地 Makefile,由此确定全局编译顺序。
5.2 执行顺序
首先依据依赖声明的先后次序,逐层进入子目录完成编译:
$(OBJS):
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
$(LIBS):
$(MAKE) -C $(dir $(subst $(obj),,$@))
随后返回根目录,执行顶层链接规则,最终产出 u-boot.bin。
六、构建特性总结
6.1 递归编译机制
通过 $(MAKE) -C 进入子目录触发独立 Makefile 执行,实现模块化并行构建。
6.2 子目录 Makefile 的复用设计
子目录 Makefile 通过 include 引入根目录 config.mk 与 rules.mk,保持规则统一的同时降低维护成本。