ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从复位向量到main_loop:ARM平台U-Boot启动流程全解析

从复位向量到main_loop:ARM平台U-Boot启动流程全解析 好这篇文章我想了很久怎么写。一开始我打算直奔源码把arch/arm/lib/和common/下面那些文件挨个拉出来跑一遍后来发现这恰恰是大部分人学U-Boot流程时最容易踩的坑代码太多、函数调用层太深一上来就扎进细节出不来最后只知道“哦这个函数被调用了”但不知道怎么用起来。U-Boot的启动流程说白了就是一块板子上电后从CPU复位向量开始把时钟、DDR、存储、串口、网络这些外设逐个点亮最后把内核镜像交给处理器的过程。这篇文章以ARM平台常见的U-Boot 2020.04为参考把这个流程拆成“整体设计、源码路径、核心细节、实操观察、问题排查”五块来讲适合正在学嵌入式Linux、要移植U-Boot、或者被串口日志卡到怀疑人生的朋友。1. U-Boot启动流程的整体链路与角色定位1.1 启动流程前面的“三段式”ROM固件、SPL、U-Boot proper很多人以为U-Boot流程就是从复位向量开始一路走到命令行提示符其实在U-Boot真正接管CPU之前芯片内部已经有一段固化在ROM里的代码在跑了。这颗ROM里的bootloader可能是厂商自己写的也可能是ARM Trusted Firmware的一部分它会完成最早的时钟设置、介质选择然后去加载下一段代码。从ROM固件开始整个链条大致是ROM固件 → SPL → U-Boot proper → 内核。SPL全称是Secondary Program Loader有些芯片上叫MLO有些叫BootROM引导程序。为什么中间要插一个SPL因为现代DDR内存的初始化时序非常复杂SoC内部SRAM通常只有几百KB根本放不下完整的U-Boot。SoC厂商的做法是用SPL做最小初始化只把CPU、时钟、串口、DDR控制器这些最底层的设备点亮然后把完整U-Boot从SD卡、eMMC或Flash里搬运到DDR里再跳转过去执行。U-Boot proper就是我们从各种厂商源码包里看到的那一大坨包含了完整驱动、命令行、环境变量、网络协议栈、各种镜像格式支持。它运行在DDR里才有足够空间去加载Linux内核。所以你看源码时如果发现SPL和U-Boot是两个不同的defconfig、两套不同的链接脚本不要觉得奇怪这是刻意设计的分工不是代码冗余。1.2 U-Boot到底在“管”哪些事哪些事它不管想理解U-Boot启动流程先要摆正心态U-Boot不是操作系统它只负责“把系统带起来然后立刻让位”。它必须管的事情包括几个维度。第一板级最基础的初始化比如时钟树、电源域、GPIO复用、串口调试输出因为连串口都不通后面所有问题都没法观测。第二内存控制器初始化DDR的时序参数、频率、地址映射这些一般由厂商的dram_init()函数搞定。第三存储和外设驱动MMC、USB、网卡、Flash这几种介质U-Boot至少要知道从哪个设备上把内核读出来。第四环境变量相当于U-Boot和用户之间的“配置文件”告诉你该去哪找内核、传什么参数。第五把镜像加载到指定内存地址并且准备好内核启动时需要的设备树、ramdisk等数据最后跳转。U-Boot不管的事情也很关键比如它不管具体文件系统的解析细节虽然它支持ext4访问但只是“读取文件”这个层面的支持它不管内核运行起来之后的各类驱动那属于Linux的事它也不管安全启动全套流程虽然现在U-Boot里也引入了Verified Boot但那是后续配置问题不属于基础流程。把这些边界划清楚你调试时才不会把内核的锅甩给U-Boot。1.3 看源码之前需要建立的三个基本概念第一个是链接地址与运行地址。U-Boot在编译时会通过u-boot.lds决定每个段放到哪个地址这个地址就是链接地址。但板子上电后U-Boot可能先从Flash里运行或者在SPL阶段临时跑在SRAM里这时候运行地址可能和链接地址不一致。为了解决这个问题U-Boot在运行过程中会把自身代码复制到DDR里一个期望的位置再跳过去继续跑这一步就是重定位英文叫relocate。不理解重定位你就看不懂board_init_f和board_init_r为什么是两套初始化函数。第二个是设备树Flattened Device TreeFDT。U-Boot早期用board_flash这类写死的板级文件来描述硬件后来逐步迁移到设备树。设备树描述CPU、内存、串口、MMC这些硬件的信息U-Boot启动时会把它解析出来最终还要把这个FDT在内存里的地址传给内核。所以U-Boot流程里经常能看到fdt_addr、fdt_high、loadaddr这类环境变量它就是用来管理设备树和镜像在内存中的位置。第三个是Kconfig和defconfig。U-Boot的配置体系是从Linux内核抄过来的功能宏以CONFIG_开头通过defconfig文件指定。不同开发板的差异绝大多数就体现在configs/xxx_defconfig、include/configs/xxx.h和arch/arm/dts/xxx.dts这几个文件里。看流程不能只盯代码哪个宏开关控制哪段逻辑也是流程分析的一部分。2. 从复位向量到main_loop启动源码全路径拆解2.1 reset向量的第一段汇编在做什么ARM平台上U-Boot的入口是arch/arm/lib/vectors.S或者SoC厂商自己的start.S这取决于具体架构版本。CPU复位后首先进入异常向量表U-Boot第一条指令基本就是b reset这种跳转指令跳到真正的启动代码。这段汇编做的事情看起来不多但每一句都很关键。它会先把处理器设置为SVC模式确保后续操作有足够的权限然后关闭中断因为此刻中断向量还没准备好接着初始化Cache、MMU这些控制逻辑有些芯片还会设置CPU频率。最重要的是设置栈指针SP汇编阶段只有先有了栈才能调用C函数。有些平台会先判断当前是从哪种介质启动的比如SD卡、eMMC、Nor Flash然后根据启动方式决定从哪里复制代码。我最早看这段汇编时也很头疼因为不同的SoC厂商会往里面塞大量自家逻辑比如三星、NXP、瑞萨都各有各的花活。我的建议是不要试图完全读懂每个芯片的每个寄存器先抓住主线设置异常向量、进SVC模式、关中断、设栈、然后进入C世界。芯片特定的初始化细节等移植真出问题时再回头扣。2.2 board_init_f把DDR、串口、设备树先点亮从汇编跳进C代码后第一个主要流程是board_init_f。这个“f”是“floating”的意思因为此时U-Boot还没有重定位运行的地址可能不是最终链接地址它正处于一个“临时的”位置。可以先把U-Boot看成一个还在工棚里干活的施工队先搭临时设施等主体建筑起来后再搬进去。common/board_f.c里有一个很大的函数指针数组叫init_sequence_f里面按顺序排着几十个初始化函数。典型的初始化顺序大概是初始化早期堆空间、初始化串口、读取环境变量里的波特率、计算DDR大小、初始化DDR、初始化调试串口、初始化设备树、初始化Cache、最后计算重定位的目标地址。它和Linux内核的启动不太一样不是“一件事做到底”而是“把每个子系统都先弄到一个能用的最低水平”。你可能会问串口初始化为什么要分好几次因为早期打印需要极简的调试串口不依赖设备树和完整驱动模型等DDR和更完整的环境准备好之后才把标准控制台切到真正的serial驱动。这个分段设计也是很多新手看日志时发现“前面几行信息很少”的原因。board_init_f的最后一步是relocate_code。它会根据gd-relocaddr把U-Boot镜像复制到新的地址同时修正所有全局变量、函数指针的偏移然后通过一个跳转指令把控制权交给重定位后的代码。从这一步开始U-Boot才算真正稳定下来后面再调用函数就不会因为地址错乱而崩溃了。2.3 重定位与board_init_rU-Boot从“草台班子”变成完整系统重定位完成后流程进入board_init_r这里的“r”是“relocated”的意思。到了这个阶段U-Boot已经运行在DDR里的最终地址上可以放开手脚初始化各类外设和完整功能了。common/board_r.c里同样有一个初始化数组比如initr_caches、initr_malloc、initr_dm驱动模型、initr_mmc、initr_env、initr_net、initr_jumptable、initr_cli等等。每个函数负责一个子系统的初始化所以串口日志里会看到“MMC: mmcxxxx: 0”“Net: eth0”之类的信息。这个阶段的典型输出大概像这样U-Boot 2020.04-dirty (Jan 01 2024 - 12:00:00 0800) CPU: Freescale i.MX6ULL rev1.1 at 396MHz DRAM: 512 MiB MMC: FSL_SDHC: 0, FSL_SDHC: 1 In: serial Out: serial Err: serial Net: eth0: ethernet20b4000board_init_r跑完后U-Boot会进入run_main_loop也就是主循环。到这里所有硬件初始化基本完成串口命令行交互已经可用剩下的工作就是等待用户输入命令或者执行预设的bootcmd自动启动系统。2.4 main_loop与bootcmd等待按键与自动启动逻辑main_loop是整个U-Boot启动流程的“终点站”它做两件事。第一件是检查启动延时如果用户在规定时间内按了任意键就进入命令行给用户手动操作的机会。第二件事如果超时没人按键就自动执行环境变量bootcmd里面的命令。这段逻辑对应的日志就是大家非常眼熟的Hit any key to stop autoboot: 3倒计时数字由环境变量bootdelay控制。如果你在做产品希望上电后不要等待直接把bootdelay改成0或者-1结果会有些差别。设成0代表立刻启动设成-1则完全禁止自动启动每次都进命令行。这种小细节在量产固件调试时特别有用。bootcmd本身可以很简单也可以很复杂。最简单的启动命令链可能是fatload mmc 0:1 0x82000000 Image booti 0x82000000 - 0x83000000一行行把内核和设备树加载到内存然后跳转。复杂的版本则会包含distro_bootcmd也就是从多个介质里逐个尝试启动像UEFI的启动项扫描一样。很多开发板默认的bootcmd就是这个分布式启动逻辑它会依次尝试USB、网卡、MMC、SATA等设备这也是为什么你有时能看到一串“Trying to boot from ...”的日志。理解了main_loop你就理解了U-Boot启动流程中“人和机器交互”的那个开关。后续想给U-Boot加开机画面、加按键逻辑本质上都是在main_loop这个环节做文章。3. 核心细节补全与关键环节配置要点3.1 内存DDR初始化是启动流程中最容易翻车的环节把DDR初始化单独拉出来讲是因为它在整个流程里的地位太特殊了SPL的核心使命之一就是初始化DDRDDR没起来完整U-Boot压根没法加载。DDR初始化这件事在硬件上涉及的东西极多就算只看软件流程也要处理时钟、复位、功耗、时序参数、DDR控制器寄存器、训练结果存储等一连串操作。在i.MX6ULL这类芯片上DDR初始化通常由厂商提供的一段DCDDevice Configuration Data来完成DCD数据里写着初始化时钟、DDR控制器、内存颗粒时序的一堆寄存器地址和值。在SPL阶段这些数据会被解析并逐条写入寄存器。为什么很多移植教程都叫“要改DCD”“要改DDR初始化参数”就是因为不同板卡使用的DDR颗粒、布线长度、频率不同时序参数必须跟着调整参数不对最直接的后果就是U-Boot在启动早期就死掉串口连一个字都打不出来。调试内存问题时的常见观察点是DRAM:这一行打印出来的容量。如果打印出来的容量和实际内存颗粒不符先检查DDR大小探测逻辑通常是dram_init_banksize()或get_ram_size()那块。还有一点要特别注意很多SoC的DDR控制器支持多个rank和片选U-Boot里gd-ram_base和gd-ram_size直接决定了内核能感知到的物理内存范围。这里的数值一旦和实际DDR布局不一致后面启动内核大概率会遇到“Kernel panic - not syncing: Out of memory”之类的问题。3.2 设备树与U-Boot流程的配合U-Boot流程里和设备树相关的环节经常被一笔带过但实际用起来坑非常多。U-Boot在编译时会把.dtb文件打包进自身固件或者放在独立分区里由启动命令去加载。编译通过后U-Boot会把设备树地址保存到环境变量fdt_addr或fdtcontroladdr里后续booti/bootm命令执行时U-Boot会把这个地址作为参数传给内核启动入口的r2寄存器。这里有一个细节容易被忽略设备树不一定能直接被内核“原样”使用。U-Boot启动过程中可能会修改内存节点、chosen节点甚至根据板子实际配置调整status属性。例如有些SoC默认把FDT放在一个临时位置U-Boot会把它移动到CONFIG_SYS_FDT_LOAD_ADDR指定的地址最终确保内核访问到的FDT不会被U-Boot自身占用或者被解压出的内核覆盖。设备树地址冲突是启动失败的高发原因之一。比如你用fatload把内核Image加载到0x82000000又把FDT加载到0x84000000但要确保Image解压后不会覆盖FDT。还有一种常见问题启动命令里booti 0x82000000 - 0x83000000中间的“-”代表没有ramdisk如果你有多余的ramdisk中间要填它的地址格式是booti kernel ramdisk fdt。中间少写一个地址U-Boot可能不会报错但内核会因为没有正确拿到FDT或initrd而表现异常。3.3 环境变量与保存机制环境变量是U-Boot启动流程里最像“用户配置”的部分但你得明白它的存储原理不然会遇到“为什么我saveenv了下次启动还是老样子”的问题。环境变量在内存里以一组keyvalue的形式存在U-Boot启动时会在指定存储设备上的分区去读取环境变量块这个存储位置由CONFIG_ENV_IS_IN_MMC、CONFIG_ENV_IS_IN_SPI_FLASH、CONFIG_ENV_IS_IN_NAND这些宏决定。每次启动时U-Boot会先判断环境变量块是否有效判断依据是一个CRC32校验值。如果CRC校验失败U-Boot会使用编译进固件的默认环境变量并打印类似“*** Warning - bad CRC, using default environment”的警告。这种问题通常出现在环境变量存储分区的地址或偏移配置和实际烧录内容不匹配时解决办法不是简单重新saveenv而是先确认CONFIG_ENV_OFFSET、CONFIG_ENV_SIZE这些参数是否和你的分区布局一致。还有一个很实用的技巧很多板子的默认环境变量里没有bootcmd或者只写了简单的distro boot逻辑。为了让U-Boot流程完全可控我习惯在编译前就把CONFIG_BOOTCOMMAND和默认bootargs这些环境变量固化到头文件或defconfig里而不是等到板子上再用setenv去输入。生产环境更是要这样因为没人会每次开机前手动敲一遍命令。3.4 booti/bootm/bootz的差别到底该用哪个U-Boot支持好几种启动内核镜像的命令新手经常搞混。bootm是最老牌的支持uImage格式也就是带U-Boot自带头部信息的内核镜像头部会包含加载地址、入口地址、CRC和镜像类型。bootz专门用于ARM平台常见的zImage就是Linux内核常用的自解压镜像。booti则用于ARM64平台的Image格式也就是未经自解压的原始内核镜像。这三个命令执行的大致流程是先校验镜像头bootm或识别格式booti/bootz然后准备FDT和ramdisk最后执行do_bootm_linux这类跳转函数。跳转前U-Boot会在指定的内存地址上做一些内存映射和Cache清理确保内核能看到干净的物理内存然后把设备树地址写入r2寄存器通过一条分支指令进到内核入口。选用哪个命令本质上是看你的内核镜像格式和入口地址。ARM32平台最常见的是zImage所以bootz用得多ARM64平台基本固定用booti老一点的板子见过uImage就会看到bootm。启动失败时第一件事就要确认启动命令对应的镜像格式对不对如果把Image格式传给bootz它很可能会直接拒绝或者解压出乱码。移植时建议每块板子都明确写死启动命令宁可一开始用手动命令验证确认通过后再固化到bootcmd。4. 实操过程观察编译、串口日志与启动分段对照4.1 编译U-Boot的最小步骤与defconfig选择实际操作部分我拿一个通用ARM环境的编译过程举例。U-Boot源码编译看起来简单但很多人第一次都会卡在交叉编译工具链上。ARM32平台的用法通常是这样的export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- make distclean make qemu_arm_defconfig make -j4如果是ARM64平台命令会变成export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- make qemu_arm64_defconfig make -j4编译完成后目录下会生成u-boot、u-boot.bin、u-boot.dtb、u-boot.cfg这些文件。不同SoC平台最终烧录的镜像可能叫u-boot.imx、u-boot-spl.bin、MLO等这些格式差异都来自编译时引入的打包脚本。移植一块新开发板时最合理的起步方式不是从零写代码而是找一块配置最接近的板子复制它的defconfig然后改掉DDR、串口、存储、网卡这些板级差异。我见过最快的移植案例其实就是把同系列板子的defconfig改了几个地址和宏开关半小时就启动到了提示符后续再慢慢调细节。4.2 解析一份真实的串口日志的重要数据点串口日志是观察U-Boot启动流程最直观的手段。假设你在实验环境里跑起来串口输出可能是这样的U-Boot 2020.04 (Jan 01 2024 - 12:00:00 0800) CPU: ARMv7 Processor rev 5 (v7l) CPU: VPFN_* bits from cpuinfo DRAM: 512 MiB WARNING: Caches not enabled Flash: 128 MiB MMC: MMC: 0 In: serial Out: serial Err: serial Net: eth0: virtio-net#0 Hit any key to stop autoboot: 3 别看就这几行每一行都能说明流程走到了哪一步。第一行“U-Boot 2020.04”出现说明board_init_f已经能工作串口已经进行了早期输出。紧接着是“CPU: ...”这对应代码里的CPU信息打印说明架构相关初始化完成。“DRAM: 512 MiB”这行出现说明DDR探测成功内存大小已经读出来这背后的函数是dram_init()返回了gd-ram_size。“MMC: MMC: 0”在board_init_r阶段出现说明驱动模型初始化完成后MMC控制器已经枚举成功。“Hit any key to stop autoboot: 3”说明主循环已经进入等待用户打断自动启动。如果在“DRAM:”之后再也看不到后续日志那基本可以断定是MMC、设备树或者某个驱动的初始化阶段崩了。串口日志里每一行都有定位价值多和代码里的printf对应起来调试速度会提升一个档次。4.3 通过加打印和u-boot命令定位进程状态调试U-Boot流程时千万不要只会看串口日志。“既然日志不够就往代码里加printf”这个思路简单粗暴但极其有效。当然为了不污染源码U-Boot本身提供了调试开关比如DEBUG宏和debug_uart机制。在代码中你经常会看到debug(...)函数它只在CONFIG_LOG或对应调试宏开启时才输出常规运行会被编译掉。现场调试时我会在关键的跳转点临时加入打印比如在board_init_f入口、DDR初始化完成、环境变量加载完成、跳转内核前各打一行。加上后重新编译烧录观察日志卡在哪一行再缩小范围。还有一类非常高效的定位方式是直接在U-Boot命令行里用bdinfo查看板级信息用mm、md命令读写内存用fdt addr命令手动解析设备树。比如bdinfo会打印boot_params、memstart、memsize这些关键数据能帮助你确认U-Boot认为的“世界”是否和实际硬件一致。如果U-Boot已经卡死在流程中没有进入命令行那就需要前面的加打印策略。这里有个小技巧早期打印尽量简单不要用格式化次数太多的printf因为此时串口驱动和环境可能还不稳定有时一个复杂格式串就会让你误以为是崩了。5. 常见问题与排查技巧实录U-Boot启动卡住怎么定位5.1 串口完全无输出先从这四种方向排查U-Boot启动流程中串口完全无输出是最让人头疼的问题因为看不到任何日志所有猜测都可能成立。我的个人排查顺序是这样的。先看硬件线序TX/RX有没有接反电平转换芯片供电是否正常波特率是否和U-Boot配置一致这个在项目初期出现概率最高。再确认编译配置查看defconfig中是否打开了对应UART外设CONFIG_MXC_UART、CONFIG_SYS_NS16550这类宏不能少。然后用逻辑分析仪或示波器量UART TX引脚开机瞬间有没有电平跳变有跳变说明软件已经在输出问题可能在线路或终端软件没跳变说明U-Boot在非常早期就崩了要么DDR没起来要么时钟配置不对要么Flash介质上读到的固件就是坏的。还有一类低概率但实际存在的情况U-Boot的早期串口输出被CONFIG_SILENT_CONSOLE关闭了。静静模式下串口可以不输出任何内容直到环境变量允许才打开。遇到莫名无输出时翻一下defconfig里有没有这类“安静”配置这也是排查方向之一。5.2 启动卡在Early CPU、DRAM、board_init_f阶段如果在串口日志里只能看到“U-Boot SPL ...”然后就停了说明问题几乎肯定在SPL阶段。SPL阶段最常见的三类死法分别是DDR初始化失败、存储介质读取U-Boot proper失败、早期时钟配置不对导致外设无法工作。DDR初始化失败的典型特征是RESET脚不断拉低重启或者串口根本没有任何输出。这类问题要么是DCD参数和颗粒不匹配要么是DDR供电没起来要么是地址线/数据线硬件接触不良。存储介质读取失败则比较容易判断通常SPL能打印“Trying to boot from MMC”或类似信息但读不到u-boot.bin分区这时要检查分区表、文件名、文件系统格式。实际项目的调法中我会把SPL阶段的调试打印全部打开尤其是CONFIG_SPL_DEBUG这类宏让SPL把每个步骤都输出出来。5.3 能启动但环境变量CRC错误或autoboot一直倒计时无法跳过环境变量CRC错误也就是日志里的“bad CRC, using default environment”并不算致命但会带来很多诡异问题。比如你明明保存过bootcmd重启后却执行了默认的distro boot逻辑怎么都进不了你的系统。这通常是因为CONFIG_ENV_OFFSET或CONFIG_ENV_SIZE设置和实际分区布局不符环境变量被写在了一个不该写的位置或者环境变量块被其他固件覆盖。处理办法是重新确认存储介质的分区查看mtdparts或eMMC boot partition配置然后手动擦除指定区域再saveenv。还有一种很隐蔽的情况环境变量块放在某个分区但U-Boot启动时加载环境变量的代码和我们实际烧录的定义不是同一套配置导致两边格式不一致这时要对比编译生成的u-boot.cfg看CONFIG_ENV_IS_IN_*和偏移到底是哪个。至于autoboot倒计时无法跳过多数时候不是按键坏了而是串口接收路径或控制台选择有问题。U-Boot在启动延时阶段会轮询输入设备如果当前控制台输入对应的串口驱动没有正常工作按什么键都进不了命令行这时可以用CONFIG_AUTOBOOT_KEYED这类宏调整交互逻辑或者检查输入设备编号是不是被改到了别的串口。5.4 启动到“Starting kernel ...”后无反应这个场景也比较经典U-Boot打印了“Starting kernel ...”屏幕就再也没反应了。首先说明U-Boot已经成功把控制权交给了内核入口问题基本可以锁定在U-Boot传给内核的数据上。最值得怀疑的是设备树地址还有内核本身的入口地址。如果内核Image被加载到了一个错误地址CPU一跳进去就会取指令异常表现为完全无输出。解决这类问题我一般分三步排查。第一步用fdt addr命令查看FDT地址处是否真的是一个合法设备树用fdt print /看看能不能解析出来。第二步确认内核Image的入口地址和加载地址是否一致如果加载地址和链接地址不一致有些内核原本支持位置无关启动但ARM64平台通常要求booti加载到指定地址。第三步把bootargs里的console参数和实际调试串口对应起来比如U-Boot跑在ttymxc0但内核却从ttymxc1输出那就会看到“死机”的假象实际内核已经在跑了。5.5 收藏级排查命令与调试配置列表最后分享一些我平时必用的命令和配置这块对排查U-Boot启动流程的“黑屏问题”特别有用。首先是bdinfo它能把板级信息全盘打印出来包括内存地址、环境变量地址、CPUID这些。其次是md addr length查看某个内存地址处的二进制数据用来确认加载进内存的镜像是否完整。然后是fdt addr和fdt print手动解析设备树确认FDT内容合法。还有mmc list、usb start、dhcp这类外设探测命令用来判断具体外设是否枚举成功。调试编译配置方面值得打开的开关包括CONFIG_DEBUG_UART它能在极早期用最简单的方式输出调试信息不依赖完整驱动模型CONFIG_BOOTSTAGE可以记录启动耗时和关键阶段信息CONFIG_LOG配合log level可以按等级过滤日志比单纯printf更灵活。这些配置在最终量产固件里可以关掉会减小体积、提高速度但在开发阶段千万不要省它们能帮你省下大量猜测时间。说实话我在实际做板子调试时遇到“串口无输出”也还是会先紧张一下。但经历几次之后你会发现U-Boot启动流程其实并不神秘它就是一个分阶段、分职责的接力过程ROM先交棒给SPLSPL把DDR和基础外设准备好再交棒给完整U-Boot最后由U-Boot把设备树和内核一起交棒给Linux。每一个阶段都有明确的入口、明确的职责、明显的日志特征。只要能定位到日志卡在哪一行问题就缩小了一半。后续如果你要做开机logo、启动动画、快速启动这些优化都可以回到这个流程里去找到对应的插入点因为不管功能怎么加核心链路还是从reset到main_loop这一条。
返回列表