资讯动态

从上电到内核:4412开发板U-Boot启动全解析

发布时间:2026/10/17 13:06:13 来源:尧图企业网站定制
聊到嵌入式Linux很多人把注意力都放在内核和驱动上uboot属于那种“能跑就行”的东西。但我在讯为4412开发板上折腾过一段时间后发现uboot启动过程恰恰是最值得搞明白的一环——它决定了一块板子从上电到加载内核之间谁在干活、按什么顺序干活。这次以4412为例把uboot从上电到进入Linux的启动链路完整拆一遍。如果你正在学嵌入式Linux、第一次接触bootloader或者做驱动移植时反复卡在启动阶段这篇文章应该能帮你把那层窗户纸捅破。uboot的启动过程并不神秘无非是“谁加载谁、谁初始化什么、最后把控制权交给谁”。但4412这套方案和经典的ARM9/ARM11不太一样它多了一层片内固化的iROM引导导致很多人在看启动日志时对不上号。下面我从整体链路讲起再拆start.S再把C语言初始化部分讲透最后给实操参考和排障经验。1. 先看清链路4412从上电到uboot的三级跳1.1 谁来决定从哪启动OM引脚的秘密开发板上电的一瞬间CPU核心还没跑起来片内固化代码先接管了芯片。4412处理器在设计时留了一组启动配置引脚通常叫OM引脚Operating Mode板子设计者把这些引脚通过上拉电阻接到高电平或下拉电阻接到低电平形成一个几位的二进制编号用来告诉芯片“你要从哪个存储介质读取启动代码”。4412支持从SD/MMC启动、从eMMC启动、从USB启动、从UART启动等多种模式。不同模式对应不同的OM引脚组合。你在讯为4412开发板上看到的那几个拨码开关或者原理图上标注OM0~OM5的网络名就是干这个用的。SD卡启动和eMMC启动是最常用的两种前者适合调试阶段频繁烧写后者适合做产品定型。这里有一个容易被忽略的地方OM引脚不是单纯的高或低就行它有一定时序配合要求而且某些启动模式下引脚是复用的。如果你换了启动设备却发现板子不启动先去看原理图上OM引脚的接法而不是怀疑uboot烧坏了。我遇到过几次“烧了镜像还是不启动”的情况最后都是OM配置跟实际启动介质对不上导致的。1.2 iROM读完就交棒BL1的身世OM引脚决定启动介质之后片内的那段固化代码就该干活了。4412片内有一块64KB的ROM出厂时写死了启动引导程序业内叫它iROM也叫BL0。这段代码用户碰不到、也没必要碰它就是用来“启动UBoot之前的那段启动”的。iROM干的事情很有规律先是关看门狗不然看门狗一超时系统就复位了那启动流程永远走不完接着配置最基本的时钟让CPU在一个已知频率下运行然后使能SRAM4412内部的iRAM一共64KB因为下面要往SRAM里放代码再往下iROM读OM引脚配置初始化对应的存储控制器比如初始化SD/MMC控制器随后从存储设备的固定位置把BL1拷贝到iRAM里跳到BL1执行。BL1是一段很小的引导程序通常只有8KB左右。为什么这么小因为iROM代码是从存储设备前固定扇区读取的早期拷贝时只能依赖固化在iROM里的逻辑不可能支持复杂的文件系统所以按扇区读最可靠而SRAM空间又有限于是BL1必须瘦身。这里我想强调一个概念BL1不是uboot也不归你写。对于4412BL1通常由芯片厂商以二进制形式提供或者一并在厂商的BSP源码里编译出来。你最终要烧写的文件名可能是BL1.bin也可能是一个打包好的镜像但它的作用都一样——把运行环境从“只有片内SRAM可用”过渡到“DDR内存已经能用”。1.3 BL2才是完整uboot加载与跳转BL1接管后核心任务只有一个初始化DRAM。因为iRAM一共才64KB连一个完整uboot的零头都不够更别说后续解压内核了。所以BL1要先把DDR控制器配置好让CPU能访问外部的内存颗粒然后再把真正的uboot镜像从存储设备搬到DDR的高地址处跳转过去。从这一刻起uboot才算真正开始运行。由于BL1是从外部存储按固定偏移动读取镜像所以uboot镜像在SD卡或eMMC里的存放位置是约定好的。比如SD卡方案里BL1存放在扇区1开始的位置uboot本体存放在扇区17或者更靠后的位置。这些偏移量由厂商BSP决定不能随意挪动否则BL1按固定地址去读读到的就是空白数据系统直接卡死。完整uboot镜像在内存中运行后会执行标准的两阶段启动逻辑英文里叫“Phase 1”和“Phase 2”。第一阶段主要是汇编完成CPU、时钟、内存、栈等基础环境的准备第二阶段进入C语言世界做板级外设初始化和内核引导准备。很多人对“uboot两阶段”的理解是从经典教材里来的但放在4412上还需要把它和BL0/BL1/BL2这条链分开看否则会觉得“怎么既有BL2又有两个阶段”。1.4 看懂SD卡和eMMC里的镜像分布搞懂整条链之后再看存储设备里的布局就一目了然了。以常见的SD卡启动为例典型布局长这样位置内容说明扇区0MBR/保留区分区表区域可能无实际功能扇区1 ~ 16BL1约8KB由iROM加载到SRAM扇区17 ~ 48uboot.bin完整uboot镜像由BL1加载到DDR后续扇区内核、设备树、根文件系统由uboot读取注意上面这个布局是典型参考具体偏移量以你的开发板BSP文档为准。烧写时不是把uboot.bin直接扔到SD卡根目录那么简单而是要写到指定扇区所以一般要借助厂商提供的烧写工具或者用dd命令按偏移写。eMMC启动的布局逻辑类似只是介质变成了eMMC的boot分区。eMMC通常有独立的boot1/boot2分区容量不大专门用来存放引导代码。烧写eMMC时还要注意把boot分区使能enable否则eMMC上电后不会主动暴露boot分区给外部控制器访问。这个问题在量产时特别常见明明烧进去了板子就是不启动查到最后是boot分区被关了。2. uboot内部第一棒start.S的汇编世界2.1 从reset向量开始BL1跳转到DDR里的uboot入口后执行的第一个文件是和SoC型号对应的start.S。它是整个uboot的入口汇编文件入口符号通常是_start对应uboot链接脚本里指定的入口地址。ARM处理器的启动方式是从固定地址取第一条指令。4412属于Cortex-A9架构复位后CPU会去读取异常向量表。虽然是uboot在DDR里跑异常向量表依然要放在uboot链接地址的起始位置。_start处通常是一个跳转指令CPU一上电或复位就必须能从这里找到第一条指令然后跳到真正的初始化代码。异常向量表本身包含了一组固定长度的跳转指令分别对应复位、未定义指令、软中断、预取中止、数据中止、IRQ、FIQ等异常类型。对uboot来说当前阶段并不需要处理复杂的异常但向量表必须存在否则一旦发生中断CPU会从异常向量表地址处取一条“垃圾指令”执行整个系统直接跑飞。start.S开头还有一段“中断向量表重定位”的过程。这是因为u-boot可能会把自己搬运到其他地址运行如果向量表还在原地址中断来了还是跳到旧地址代码都搬走了自然找不到处理函数。所以在初始化过程中会把这16字节一份的向量表复制到当前运行地址处。2.2 为什么一上来就切模式和关中断进入start.S之后第一件事通常是修改CPSR寄存器把CPU切到SVC模式并屏蔽中断。Cortex-A9有用户态、系统态、SVC、IRQ、FIQ等多种模式uboot作为一个bootloader必须运行在特权模式下才能操作硬件寄存器所以常规选择是SVC模式。屏蔽中断这件事不是“懒得处理”而是启动早期根本没有能力处理中断。中断控制器还没有初始化中断向量表虽然有了但对应的处理逻辑并不完整任何中断发生都会导致行为不可控。所以通过设置CPSR的I位和F位分别屏蔽IRQ和FIQ中断让CPU在整个uboot初始化阶段只有一条主线在执行。紧接着是关闭MMU和D-Cache。这一点很多初学者不理解Linux内核里MMU开着跑得好好的为什么uboot一上来就要关掉因为uboot启动早期CPU访问的还是物理地址页表还没建立如果贸然开着MMU访问地址翻译会进入无效页表直接data abort。而D-Cache的问题在于启动早期代码可能已经通过Cache预取了一部分数据但内存控制器还没有完全配置好Cache读取到的可能是脏数据。所以先把MMU和Cache关掉让所有访问路径都走最原始的总线访问稳字当头。关完MMU之后如果uboot版本比较新后面会在board_init阶段重新建立页表和使能Cache但4412常见的BSP版本一般倾向于用简单直接的物理地址访问这样做虽然性能不占优但移植和调试的复杂度低很多。2.3 时钟与DDR两个最容易出事的初始化start.S调用的下一批关键函数就是时钟初始化和内存初始化。4412外部接的晶振一般是24MHz但CPU运行频率可以达到1.4GHz。从24MHz到1.4GHz中间不是一步能跳过去的必须按PLL配置表一步步倍频经过ARMCLK、ACP等各级分频和锁相环锁定最后稳定到目标频率。这个过程里最麻烦的是不能随便乱配。PLL寄存器里包含了MDIV、PDIV、SDIV等分频因子配置错误轻则CPU跑在错误频率重则直接死机。厂商BSP里通常会带一套验证过的配置表比如在board_init里根据开发板型号选择对应配置或者直接在lowlevel_init里写死一套标准参数。我们做移植的时候尽量沿用这套表不要自己拍脑袋去改除非你确认掌握了PLL锁定时间的计算。DDR初始化的地位比时钟更关键。因为从BL1阶段开始所有代码就都要跑在DDR里了DDR不初始化后面所有步骤都无法进行。4412通常搭配DDR3颗粒初始化时要配置控制器的时序参数包括CAS延迟、写恢复时间、刷新周期、突发长度等。这些参数不是随便填的要和内存颗粒的规格对应。一个很常见的坑是厂商提供的BSP针对的是1GB内存配置你换了颗512MB的内存颗粒后如果不更新配置表uboot能启动但内存检测只能看到错误容量或者在高地址访问时随机死机。排查这类问题只能靠对照颗粒手册逐项核对时序配置没有捷径。DDR初始化完成后start.S会逐步切换栈指针指向DDR空间为调用C函数做准备。这时候bl跳转指令开始调用C语言函数标志着uboot第一阶段从“纯汇编”进入“C语言主导”的过渡。2.4 给C语言铺路栈和重定位汇编代码里调用C函数之前必须先设置栈指针sp。因为C语言在函数调用过程中参数传递、局部变量、返回地址都要压栈。uboot启动早期栈只能放在SRAM里等DDR初始化完成后再把栈挪到DDR的合适位置给后续C函数留足空间。栈地址的选择有讲究。不能选在uboot镜像本身的位置否则函数调用过程中压栈可能把代码覆盖掉。也不能选到内核将来要加载的地址区域否则后面tftp下载内核时会冲掉栈。常规做法是选择一个“uboot运行地址下方”或“内存高地址的保留区域”具体位置在链接脚本和板级配置头文件里定义。完成这些之后start.S会根据链接地址和当前运行地址的差异决定是否需要进行代码重定位。简单说代码在编译时有个链接地址比如0x43E00000但它实际被BL1加载到了别的地址那么所有使用绝对地址的指令都要修正。传统做法是把uboot整体搬运到链接地址处再跳转过去后面的代码就当成“运行地址等于链接地址”来跑省心。也有版本实现了位置无关的编译方式但如果厂商BSP还是老一套老老实实重定位更稳。重定位完成后代码跳转到board_init_f正式开始C语言第一批初始化。从这里开始启动流程就有串口日志“可见”了前面半段都是黑盒。3. uboot内部第二棒板级初始化和内核接棒3.1 board_init_f到board_init_r两段式初始化的用意到这里我们要区分两个同名函数board_init_f和board_init_r。它们的“f”是“early/forward”意义上的初始化“r”是“relocate”之后的重置初始化。这是uboot老版本里非常经典的两段式设计。board_init_f在代码重定位之前执行早早期阶段这时uboot对DRAM的完整布局还没有摸清所以只做有限的事初始化串口靠它打印日志、初始化DRAM信息、规划内存布局比如uboot镜像放哪、堆栈放哪、识别板级型号等。这一阶段结束后代码完成重定位然后跳转到board_init_r。board_init_r就是“UBoot已经搬到新家接下来可以撒开手脚初始化所有外设”的阶段。它负责初始化完整的板级设备集合包括eMMC/SD控制器、网络控制器、USB、LCD、按键、环境变量存储、命令解析等等。最后进入一个不会返回的主循环main_loop等待用户输入命令或执行自动启动流程。为什么要这么麻烦地分成两段原因还是那两个字空间。早期uboot只有几十KB可用空间时不可能把所有设备的驱动都加载进来只能先做最小系统确定内存布局后再扩展。放在4412的场景里BL1已经把内存弄好了但uboot自身还要在内存里重新安家所以两段式逻辑依然保留。3.2 串口、MMC、网卡等外设初始化的顺序在board_init_r里外设初始化是有先后顺序的不能颠倒。串口永远是第一个因为它承担了输出调试信息的职责——串口不工作后面初始化什么都看不到反馈。很多板子启动日志前几行看不到东西就是这个原因串口还没开日志无从谈起。串口初始化包括GPIO复用配置、UART控制器的波特率设置、收发FIFO使能等。4412的UART控制器支持不同的波特率默认串口日志通常用1152008N1。实际开发中为了和终端匹配发现乱码时先查波特率对不对别急着怀疑驱动。串口就绪后紧接着初始化的是存储设备比如SD/MMC控制器。因为uboot要将内核和设备树从存储介质里读出来存储设备不先初始化后面load mmc命令就无从执行。初始化顺序里还包括网络控制器tftp下载内核时要用。LED这类简单设备反而更早因为它是调试时最简单的指示手段。LCD一般放比较靠后的位置因为它不影响启动成功与否只影响显示。这种顺序并非固定的教学模板而是由“谁被依赖就谁先初始化”的原则决定的。比如板子上网卡用的是USB接口那么USB控制器必须在网卡之前初始化如果网卡挂在总线桥上总线桥也得提前就位。所以看启动日志时你会发现初始化顺序和硬件拓扑是强相关的。3.3 环境变量的实质作用uboot里环境变量看起来就是个字符串表格实际作用却直接决定启动行为。两个最重要的环境变量是bootcmd和bootargs。bootcmd是uboot在自动启动时执行的命令序列。开发板出厂时通常配置成从eMMC或SD卡读取内核镜像比如setenv bootcmd mmc read 0 0x41000000 0x2000 0x4000; bootm 0x41000000这里mmc read是把eMMC里某个偏移位置的数据读进内存bootm是从内存地址处启动内核。你看到的“Hit any key to stop autoboot”提示等的那几秒就是在等待用户按键如果按了键就进入交互命令行否则倒计时一结束就执行bootcmd。bootargs是传给内核的启动参数内容包含控制台、内存大小、根文件系统所在设备、根文件系统类型等。举个常见例子setenv bootargs consolettySAC2,115200 root/dev/mmcblk0p2 rootwait rwconsolettySAC2,115200告诉内核用哪个串口作为调试串口波特率是多少。root/dev/mmcblk0p2告诉内核根文件系统在eMMC的第二个分区上rootwait表示等待设备出现再挂载。这里很多新手栽过跟头bootargs里的根文件系统分区和实际分区没对上uboot启动内核没问题但内核启动到一半就panic了报的还特别委婉不仔细看都不知道是分区写错了。环境变量在出厂时会有一个默认值集合烧写在存储设备的固定区域。你修改环境变量后saveenv把它写回去下次启动继续生效。调试阶段发现反复改的配置没生效先确认是不是没执行saveenv——这是uboot玩家最常见的低级错误之一。3.4 bootm是怎么把内核交给Linux的到了bootm执行这一步uboot的使命基本快结束了。bootm命令要做的事情分三块准备内核镜像、准备内核启动参数、跳转执行。如果是传统uImage格式uboot会解析头部信息把真正的内核代码段解压到内存指定位置然后用寄存器传递机器类型编号和设备树指针。4412属于较新的平台常用方式是配合设备树DTB文件uboot把DTB也放入内存在跳转内核时把DTB物理地址告诉内核。内核启动早期再通过解析DTB来枚举平台信息、内存大小、串口配置等极大减少了内核代码对具体板卡的耦合。跳转内核不是简单地b一下就行uboot在跳之前要做好最后的收尾关闭中断、把MMU和Cache置成内核想要的状态、刷新Cache、确保r0、r1、r2三个寄存器分别存放正确的参数。其中r2存放设备树地址r1存放机器类型r0通常是0。内核从入口代码开始执行后uboot就再也没有控制权了。从代码逻辑上讲这里有个“唯一交接点”uboot把CPU放在一个确定的上下文里内核代码从链接地址入口开始跑。如果跳转后内核一片死寂往往是三个寄存器传参不对、镜像被解压到了错误地址或者DTB地址不合法。这类问题用串口看内核日志就能定位但如果内核首条指令就没跑起来串口上连一个字都不会有。4. 实操记录编译、烧写和串口日志解读4.1 编译4412 uboot的完整流程编译uboot看起来复杂实际上就是普通的交叉编译流程。先准备好交叉编译工具链比如arm-linux-gnueabihf-gcc版本选择要贴近厂商BSP的要求太新或太老都可能出现编译错误。打开uboot源码根目录通常第一步是查看板的配置项。厂商BSP的boards.cfg里一般有一行类似itop_4412的配置用make help能看到支持的目标板列表。配置完成后执行编译make itop_4412_config make最终会在根目录或特定子目录生成u-boot.bin。某些BSP还会额外产出BL1.bin或者提供一个mkuboot脚本把BL1和uboot.bin拼成一个完整的烧写镜像。如果没有特殊工具单纯一个u-boot.bin就已经是完整的BL2镜像了。编译环境里比较坑的是工具链路径和库依赖。如果编译报错缺少标准头文件先确认交叉编译器的sysroot是否指向正确的目标板库目录不要拿宿主机的头文件去顶。另外uboot源码里如果带了厂商补丁编译前最好先检查补丁是否已经应用否则很容易出现编译过了但跑不起来的诡异行为。4.2 烧写到SD卡的具体步骤编译出来只是第一步烧写才是动手的关键。针对SD卡启动假设你的SD卡是/dev/sdb首先清空卡上原来的内容确保没有残留分区干扰启动然后用厂商提供的烧写工具按扇区把BL1和uboot.bin写入指定位置。使用dd命令时要特别小心偏移量和输入文件类型。比如写入uboot.bin时如果它包含完整镜像头部就直接按BL1给你的偏移来写dd ifBL1.bin of/dev/sdb seek1 bs512 convfsync dd ifu-boot.bin of/dev/sdb seek17 bs512 convfsyncseek1表示从第1个扇区开始写bs512即以512字节为一个扇区。convfsync确保数据落盘防止写入缓存还没来得及刷就拔卡导致镜像不完整。这里必须再三提醒写错seek值是灾难性的。如果seek误写成了0等于从MBR开始覆盖后面分区表全部报废而且卡上原来的数据几乎无法恢复。操作前建议把原始卡镜像备份一下至少记下原来的分区布局。SD卡烧写完成后把卡插到开发板拨动OM引脚到SD卡启动上电看串口。4.3 串口日志告诉了我们什么接好串口线注意交叉连接信号线打开终端软件波特率设为115200开发板上电后正常启动大概能捕获到这样的日志节奏U-Boot 2010.12 (Jun 06 2013 - 15:20:26) CPU: EXYNOS4412 Board: itop-4412 DRAM: 1 GiB ... MMC: SAMSUNG SD/MMC: 0 ... Net: eth0 Hit any key to stop autoboot: 3看到响应字符串“U-Boot 2010.12”说明BL2已经完全接管并跑通了串口、CPU、DRAM这几件大事。DRAM: 1 GiB是内存检测结果如果这里显示的不是板子实际容量说明DDR配置有问题。MMC:后面列出检测到的存储设备Net:后面是网络控制器名称。从“Hit any key to stop autoboot”开始uboot就进入倒计时。如果你按下任意键会进入命令提示符可以手动执行help查看命令列表、printenv查看环境变量、load mmc读取内核、bootm启动内核。如果3秒倒计时结束则会自动执行bootcmd。串口日志的一个关键技巧是逐行对号日志出现的顺序、每行内容对应的初始化阶段基本能帮你判断问题卡在哪一步。比如日志显示DRAM: 1 GiB之后直接没有输出了那多半是MMC初始化或后续外设初始化卡住而不是DDR的问题。5. 启动排查板子不启动时怎么找问题5.1 上电无任何输出最常见也最让人绝望的故障就是上电后串口一个字符都没有。这时候先不要怀疑uboot从更底层排查供电是否正常核心板上的电源指示灯亮不亮晶振是否起振用示波器探一下24MHz晶振是否有波形OM引脚的启动介质配置是否和你烧写的镜像位置一致串口线是否接对了TX/RX有没有交叉。如果这些都没问题还有一种可能是BL1根本就没被执行。比如SD卡里BL1被某个工具意外覆盖或者写BL1时扇区偏移没对iROM按固定位置怎么读都读不到有效代码整个系统就停在黑屏状态。解决方法是重新烧写BL1并且烧完后用工具读一遍确认内容在正确的扇区上。5.2 卡在DRAM初始化如果串口上连“U-Boot”字样都没出现但板子有上电反应这一阶段大概率是DRAM初始化失败或时钟配置卡死。因为串口初始化是在DRAM可用之前打印不了任何东西。此时能用的是示波器量内存数据线上的波形观察是否有读写请求再对照BSP里的DDR配置逐项检查。有条件的话建议直连调试器在start.S里关键位置下断点观察PC运行到哪一步停了。没有调试器就用最笨的办法临时在start.S里增加串口早期的输出代码能把日志提前多少就提前多少一步步缩小范围。比如在调用lowlevel_init之前打印一行如果这行都没有说明时钟或栈有问题如果这行有但下一行没有说明DDR配置导致总线异常。5.3 能启动uboot但网络不通或内核挂不上根文件系统能进uboot命令行说明整体框架是好的。网络不通先看Net: eth0这行是否正常出现如果没有检查网卡芯片电源、复位引脚、MDIO总线连接以及uboot源码里对应网卡驱动的配置。如果有但ping不通再看环境变量里的ethaddr是否有效、网关和服务器地址是否配对。有些网卡芯片必须用Ethernet MAC地址地址全零会被驱动拒绝直接现象就是“link up但数据不通”。内核能起来但挂不上根文件系统这是更典型的应用层配置问题。kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,2)这类日志出现时先核对bootargs里root的分区号与实际分区号是否一致。SD卡设备名可能是/dev/mmcblk0p1、/dev/mmcblk0p2分配对即可。此外还要确认根文件系统镜像格式是否正确比如内核只支持ext4但你烧写的是ubifs格式那就是format mismatch跑不起来。5.4 调uboot的实用技巧调uboot最有效的工具永远是打印。加上printf和puts把关键函数的入口、寄存器值、状态字都打出来配合串口日志一点一点缩小问题范围。如果连基础打印都没有就查bd/board相关信息有没有初始化成功。其次是合理利用saveenv。在调试过程中把临时修改的bootcmd和bootargs存下来下次启动直接生效能大幅减少重复输入命令的麻烦。但记住最终的出厂环境变量要记得改回正规配置否则交付给人家的板子一上电就跑进你自己的调试命令现场会非常尴尬。最后一点是善用第二块启动介质。调试过程中如果SD卡启动不稳定先通过eMMC启动进入uboot再从uboot里用命令读取SD卡内容。这样即便SD卡镜像写坏了开发板仍有一块“保底系统”可以操作能省很多来回拔插的功夫。经过这几轮折腾你会发现自己对uboot的整个启动过程有了真正的感觉而不只是会敲烧写命令。4412的这套启动链在当今不少应用处理器上依然能看到类似思路只是命名和细节略有差异——搞懂它再换其他平台的开发板你会发现启动过程不过是大同小异。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑