Linux u-boot
2026/9/23 1:28:19 网站建设 项目流程

一、U-boot功能

uboot(启动内核)=裸机
1.uboot源码如何组织
芯片架构(arm、x86…)->soc厂商相同架构不同的芯片产品->板卡厂商相同芯片不同的板卡产品

uboot目的是启动内核,所以uboot需要先初始化内存、时钟和其他硬件,将内核从Flash拷贝到内存中。
内核的作用是去启动APP。APP在Flash中,其中内核需要能读写Flash(驱动程序,包括U盘、网络、LCD、输入设备)、读写文件(文件系统)、找到并启动APP。

2.驱动如何使用设备树
烧录的u-boot=uboot.bin+某个dtb(配置文件)

dtb中保存了配置文件,保证uboot.bin不臃肿。uboot启动后会去读dtb配置文件。相较于内核的设备树文件,uboot的设备树文件时简化版本。

二、Makefile使用技巧

    # 将在make过程中使用到的Makefile中的规则和变量写入1.txtmake-p>1.txt

      makefile的remake功能
      当 make 运行过程中,检测到自己所依赖的 Makefile/include 文件被更新,就自动重新加载 Makefile、重新运行 make。下例中引用test文件时并未创建test文件,后续创建test文件后自动重新make。

      三、默认配置的过程


      工具及源码位置:

      执行默认配置命令后会生成conf工具用于将默认配置写入根目录下的 .config 文件,工具及源码位置如上图。
      在相关的 Makefile 中的依赖关系如下

      # 上图第一条Makefile命令用conf工具生成.config相当于 scripts/kconfig/conf --defconfig=configs/mx6ull_14x14_evk_deconfig Kconfigscripts/kconfig/conf--defconfig=arch/../configs/mx6ull_14x14_evk_deconfig Kconfig

      分析scripts/kconfig/conf.c代码总结以下配置过程:

      defconfig_file = "arch/../configs/mx6ull_14x14_evk_deconfig"; name = "Kconfig" conf_paarse(name); //解析uboot根目录下的Kconfig conf_read(defconfig_file); //读配置文件 conf_set_all_new_symbols(def_default); //设置new_symbols为默认值 conf_write(NULL); //写到.config

      重要:
      对于.config中存在,但 defconfig 默认配置文件中没有显式设置的配置选项,来源于整套 Kconfig 树,根 Kconfig 为入口,各子模块 Kconfig 定义具体选项,(从顶层 Kconfig 入口,递归 source 所有子目录 Kconfig,读取全部配置项):一部分通过select被已开启配置自动选中;另一部分采用 Kconfig 定义的default默认值。defconfig 只保存用户显式指定的配置,其余配置项由 Kconfig 的依赖、选择、默认值规则推导生成最终的.config。defconfig和Kconfig都由conf工具解析。

      四、make过程

      1.过程解读

      1.根据 .config 自动生成头文件:include/config.h、include/autoconf.h,还有 u‑boot.cfg

      • config.h / autoconf.h:把.config里的CONFIG_XXX=y转成 C 宏#define CONFIG_XXX 1,C 代码#include <config.h>,就可以直接使用#ifdef CONFIG_XXX条件编译。
      • u‑boot.cfg:给构建系统、脚本使用的配置文本,不是给 C 代码用。
      • 关键点:include/config.h 是自动生成的文件,源码仓库里原本没有,make 才会生成。

      2.制作工具(编译主机端工具)

      • U‑Boot 会先编译运行在 PC 主机上工具:tools/下面的 mkimage、dtc 等;后续打包 u‑boot 镜像会依赖这些主机工具,必须先编译出来。

      3.交叉编译各个 C / 汇编源文件

      • 根据.config的各个 CONFIG 宏,Kbuild/Makefile 决定编译哪些目录、哪些.c/.S文件;
      • .c文件#include <config.h>,通过宏拿到.config的配置项,做条件编译;
      • 注意:.c 不能直接读取.config,必须经过 config.h 头文件中转。

        在编译过程中生成的u-boot.cfg作用:
        Kconfig + 板级头文件所有生效 CONFIG_xxx 的全集,调试查看用,编译过程不会读取它。移植时,想确认某个宏到底有没有生效,直接看 u‑boot.cfg。.config看不到板头文件里#define CONFIG_XXX,但 u‑boot.cfg 可以看到。

      在Makefile中会根据.config中的配置设置变量obj如下,之后会进入obj变量指定的目录中的Makefile决定编译某个文件。

      # 等于目录 obj -y += xxx/ # 等于.o文件 obj -y += xxx.o # -某个配置项等于某个目录 obj -$(CONFIG_XXX) += X_dir/ # 某个配置项等于某个.o文件 obj -$(CONFIG_XXX) += X_file.o


      例如你想编译 env_dataflash.o 在配置时就要将 CONFIG_ENV_IS_IN_DATAFLASH 配置为 y ,在编译的时候变量$(CONFIG_ENV_IS_IN_DATAFLASH) 就会为 y 即 obj-y += env_dataflash.o。

      2.make过程中生成的各类配置文件

      执行make xxx_defconfig只完成初步配置,生成顶层.config;真正执行 make 编译时,还会再跑一遍 silentoldconfig,等价执行make include/config/auto.conf,完成二次配置。
      include/config/auto.conf:Kconfig 输出,.config的副本,存放所有 Kconfig 的 CONFIG_* 变量,被顶层 Makeinclude 包含,给 Make 读取 Kconfig 的配置项。

      • 1.生成链接文件
        在include/config/下创建符号链接,把板子对应的配置头文件链接到include/config.h。

      • 2.生成include/config.h
        C语言代码使用的配置头文件,将把 Kconfig 中所有CONFIG_*选项全部转成 C 宏定义。C 代码#include <config.h>就可以直接拿到开发板板级配置。

      • 3.生成u-boot.cfg
        完整配置文本快照文件,把全部 CONFIG 配置项原样输出保存,用于调试、查看当前完整配置,不参与编译过程。

      • 4.生成include/autoconf.mk
        Makefile 使用的配置文件,非常关键。把 CONFIG_xxx 转换成 Makefile 变量,供各个子目录 Makefile 使用。顶层config.mk会‑include include/autoconf.mk,子目录中COBJS‑$(CONFIG_CMD_NET) += net.o就依靠这个文件的变量来控制编译哪些源文件。

      • 5.生成include/autoconf.mk.dep
        依赖关系文件,记录autoconf.mk依赖哪些源文件(Kconfig、板头文件等)。make 工具读取.dep文件:当依赖的 Kconfig / 头文件被修改时,会自动重新生成autoconf.mk,实现增量编译;如果没有这个 dep,修改 Kconfig 后 make 不会自动重跑配置,会出现配置不更新的 bug。


      u-boot编译过程总结
      MX_6ULL为例

      1.链接u-boot得到一个elf格式的u-boot
      2.生成u-boot-nodtb.bin是imx文件依赖之一
      3.编译设备树是imx文件依赖之二,dt.dtb是由imxull-14x14-evk.dtb复制来的。
      4.拼接dt.dtb和u-boot-nodtb.bin生成u-boot-dtb.bin
      5.制作u-boot-dtb.imx文件(这一步是芯片相关处理,对于6ull会添加DDR等配置文件,对于stm32mp157则直接将u-boot-dtb.bin拷贝为u-boot.bin再添加一个头部生成u-boot.stm32)

      五、核心脚本

      以/lib目录作为所要编译的目录为例

      顶层Makefile

      #在 arch/arm/Makefile 中指定了第一个文件 head-y := arch/arm/cpu/$(CPU)/start.o #顶层Makefile中定义了要编译哪些目录 u-boot-init := $(head-y) libs-y += lib/ libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/ libs-$(CONFIG_OF_EMBED) += dts/ libs-y += fs/ libs-y += net/ libs-y += disk/ libs-y += drivers/ libs-y += drivers/dma/ libs-y += drivers/gpio/ libs-y += drivers/i2c/ libs-y += drivers/mmc/ libs-y += drivers/mtd/ libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/ libs-y += drivers/mtd/onenand/ libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/ libs-y += drivers/mtd/spi/ libs-y += drivers/net/ libs-y += drivers/net/phy/ libs-y += drivers/pci/ libs-y += drivers/power/ \ drivers/power/domain/ \ drivers/power/fuel_gauge/ \ drivers/power/mfd/ \ drivers/power/pmic/ \ drivers/power/battery/ \ drivers/power/regulator/ libs-y += drivers/spi/ #顶层Makefile中定义了如何编译子目录,重点在https://100ask.org/course/52scripts/Makefile.build脚本 #下面的脚本使用 **Makefile.build** 递归编译子目录,生成子目录/build-in.o ,再链接start.o与各个子目录的build-in.o $(u-boot-dirs): prepare scripts # make -f $(scrtree)/scripys/Makefile.build obj=arch/arm/cpu/arm7 $(Q)$(MAKE) $(build)=$@

      lib/Makefile

      lib/Makefile内容如下:

      ifndef CONFIG_SPL_BUILD # 若想将efi编译进u-boot,在配置的时候就要将CONFIG_EFI设置成y obj-$(CONFIG_EFI) += efi/ obj-$(CONFIG_EFI_LOADER) += efi_loader/ obj-$(CONFIG_LZMA) += lzma/ obj-$(CONFIG_LZO) += lzo/ obj-$(CONFIG_ZLIB) += zlib/ obj-$(CONFIG_BZIP2) += bzip2/ obj-$(CONFIG_TIZEN) += tizen/ obj-$(CONFIG_FIT) += libfdt/ obj-$(CONFIG_CMD_DHRYSTONE) += dhry/ obj-$(CONFIG_AVB_SUPPORT) += avb/ obj-$(CONFIG_IMX_TRUSTY_OS) += trusty/ql-tipc/sysdeps/ obj-$(CONFIG_AES) += aes.o obj-$(CONFIG_USB_TTY) += circbuf.o obj-y += crc7.o obj-y += crc8.o

      lib/Makefile 提供待编译文件清单(obj‑y 系列),scripts/makefile.build 解析该清单并生成实际编译目标。

      scripts/makefile.build

      makefile.build如何编译某个目录
      以lib目录为例:
      在顶层Makefile中传入的参数时lib即:

      make -f $(scrtree)/scripys/Makefile.build obj=lib

      1.确定 src 源路径

      # Modified for U-Boot prefix := tpl src := $(patsubst $(prefix)/%,%,$(obj)) ifeq ($(obj),$(src)) prefix := spl src := $(patsubst $(prefix)/%,%,$(obj)) ifeq ($(obj),$(src)) prefix := .

      2.包含Makefile
      在这里会去包含要编译子目录中的Makefile.

      # The filename Kbuild has precedence over Makefile kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) # 查找 lib/ 下存在 Kbuild 还是 Makefile 赋给变量 kbuild-file kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) # 此时 $(kbuild-file) 就等于 lib/Makefle 下面的语句将其包含进来 include $(kbuild-file)

      2.编译.o

      # 编译.o执行部分 # Built-in and composite module parts $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c)

      3.编译子目录

      # 编译子目录执行部分 # To build objects in subdirs, we need to descend into the directories $(sort $(subdir-obj-y)): $(subdir-ym) ;

      4.链接build-in.o得到u-boot

      # Makefile.build的第一个目标 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @: # builtin-target 的依赖 $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target) # 链接命令 $(LD) cmd_link_o_target = $(if $(strip $(obj-y)),\ $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)

      __build是Makefile.build脚本的第一个目标,是子目录构建入口。

      lib子目录编译总结

      顶层Makefile → make -f scripts/makefile.build obj=lib → makefile.build载入lib/Makefile,解析obj‑y/subdir‑y等 → 执行脚本第一个目标 __build → 依赖 builtin‑target(lib/built‑in.o)、subdir‑ym、always → built‑in.o又依赖lib目录下各个obj‑y的*.o → *.o规则由makefile.build内部的%.o自动规则生成 → subdir‑ym触发更深子目录再次调用makefile.build

      六、设备树编译

      1.在 auto.conf 中会指定设备树文件如下。

      CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-evk"

      2.编译

      arch/arm/dts/imx6ull-14x14-evk.dts ⬇ arch/arm/dts/imx6ull-14x14-evk.dtb ⬇ 复制.dtb文件到根目录下的 dts/dt.dtb

      最终 dt.dtb 与 u-boot-nodtb.bin 合并得到 u-boot-dtb.bin 。

      七、启动过程

      XIP设备

      一上电CPU必定从XIP设备中执行第一条指令。对于支持Nand启动、USB启动、UART启动、SD卡启动的芯片,里面一定有BootROM进行硬件初始化、把程序从非XIP设备复制到RAM中执行。

      RAM 不属于 XIP 设备,它本身就是 CPU 可直接寻址的内存。XIP 设备一般指支持原地执行的 NOR‑Flash,该 Flash 被硬件映射到 CPU 系统地址空间,CPU 可以直接读取 Flash 中的指令并运行,不需要把程序拷贝到 RAM;但运行时栈、全局变量等数据仍然需要存放在 RAM 中。而 SD 卡属于非 XIP 块设备,CPU 无法直接对其地址取指访问,必须通过 SD/eMMC 主机控制器把程序读取复制到 RAM 之后,CPU 才可以执行。

      板子上电并不是直接运行uboot,而是运行BROM(BootROM)中固化的程序,将Falsh或SD卡中的uboot拷贝到内存中再去运行uboot。

      之后根据这个流程分析源码
      uboot启动流程(分为XIP设备启动与非XIP设备启动两种情况)

      情况一、对于在XIP设备中启动(例:uboot是存储在XIP设备上的,对于Nor Flash就是XIP设备
      1.上电复位,BootROM 配置地址映射,CPU 复位向量指向 Nor,SPL 直接在 Nor 上 XIP 执行(无拷贝)
      2.SPL 做极早期初始化:时钟、DDR 时序 / 训练、基础串口
      3.SPL 从 Nor 把 U-Boot proper 拷贝到 DDR,跳转 DDR(DDR是易失性存储设备,而Nand Flash属于非易失性存储设备)
      4.U-Boot proper 在 DDR 运行:外设初始化、环境变量、命令行
      5.读 Flash 把内核 + dtb 拷贝到 DDR
      6.启动内核

      情况二、对于在非XIP设备中启动(例:uboot存储在SD卡这样的非XIP设备
      1.上电复位,先跑 BootROM(本身 XIP)
      2.BootROM 通过块设备驱动,把SPL 读取拷贝到片上 SRAM,跳转到 SRAM 执行(核心区别)
      3.SPL 在 SRAM 中初始化 DDR
      4.SPL 再从 SD/eMMC/NAND 读取完整 U-Boot proper 拷贝到 DDR,跳转 DDR
      5.U-Boot proper 在 DDR 运行,外设初始化
      6.加载内核到 DDR
      7.启动内核

      SPL (Secondary Program Loader,二级程序加载器,是 U-Boot 体系里的精简先导引导程序,**非XIP设备的头部信息**) U-Boot porper中有重定位程序,在将 U-Boot porper 复制到DDR后运行 U-Boot porper 首先会进行重定位。重定位的两种方式: 1.修改程序,将原来的地址都改为新地址。2.将程序复制到新地址。

      BootROM的作用:
      硬件初始化、将程序从非XIP设备复制到RAM中执行。

      BootROM如何选择启动设备?
      a.通过芯片的bootpin引脚选择某个设备。例:6ull SD卡、EMMC或USB启动。
      b.通过芯片的bootpin引脚选择某个顺序。例:全志 从 SD卡->SPI->Nand 或 SPI->Nand->SD 依次尝试。

      U-Boot porper中有重定位程序,在将 U-Boot porper 复制到DDR后运行 U-Boot porper 首先会进行重定位。重定位的两种方式:
      1.修改程序,将原来的地址都改为新地址。2.将程序复制到新地址。

      需要专业的网站建设服务?

      联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

      立即咨询