ARTICLE DETAIL

资讯详情

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

从单片机到嵌入式Linux:为什么u-boot是必须跨过的第一道门槛

从单片机到嵌入式Linux:为什么u-boot是必须跨过的第一道门槛 刷了几年单片机从51折腾到STM32点灯、传感器、小车、示波器逻辑分析仪玩了一堆回头发现真正做产品、找工作时市场要的“嵌入式”和我在实验室玩的“单片机”根本不是一回事。别人聊的嵌入式是LinuxQt、是驱动、是设备树、是u-boot我聊的嵌入式是GPIO翻转速度、是中断优先级、是Keil里怎么下载程序。这就是单片机和嵌入式Linux之间那条巨大的认知鸿沟。想跨过去u-boot是绕不开的第一道门。这篇文章就聊聊为什么建议你从单片机往u-boot走以及具体怎么学、怎么实操、会遇到哪些坑。很多玩单片机的人对u-boot的第一反应是“这玩意儿跟我有什么关系”实际上只要你碰的是带MMU的SoC——比如i.MX6ULL、全志V3s、瑞芯微RK系列上电之后第一段可编程代码就是u-boot或者类似的bootloader。它决定了你怎么引导内核、怎么调试硬件、怎么从裸机思维切换到系统思维。我从一个纯单片机背景一路摸到嵌入式Linux驱动回头看u-boot是性价比最高的一个学习节点值得单独拿出来啃。1. 从单片机到嵌入式Linux差的不只是操作系统1.1 裸机开发的直觉在Linux面前会失效先说说我自己走过的弯路。最开始接触嵌入式Linux的时候我带着单片机那一套思维去理解上电后直接写个main函数初始化时钟、配置GPIO、轮询外设。这种思路在STM32上完全成立因为有启动文件帮你把堆栈、中断向量表、时钟都弄好了你只需要关心业务逻辑。但到了Linux系统的开发板上这套直觉直接失灵——你找不到一个“main函数”也不知道第一个被执行的指令在哪。原因很简单Linux内核不是一个裸机程序它依赖复杂的内存管理、进程调度、驱动框架需要外部先帮它把硬件环境准备到“可以运行”的状态。比如DDR控制器要初始化否则内核代码放哪跑串口要初始化否则打印信息往哪输出定时器要初始化否则内核起来没法调度。这些事如果都让内核自己干它就得为成百上千种开发板写初始化代码内核会变成一个巨大的、不可维护的怪物。于是行业里约定了一个分工bootloader负责“把硬件粗略初始化好把内核镜像找到并加载进内存然后跳转执行”。u-boot就是Linux生态里最主流、支持最广泛的bootloader。说白了单片机里的启动文件是一个汇编写的“小u-boot”而u-boot本身是一个功能更丰富、可交互、可调试的“大启动文件”。这里补充一个关键认知u-boot不是操作系统它本身也是一个“裸机程序”。它没有进程、没有调度、没有虚拟内存它跑起来之后就是一个死循环等你敲命令。但这个裸机程序比你平时写的单片机程序复杂得多它要处理不同SoC的寄存器差异、不同存储介质SD卡、eMMC、NAND、SPI Flash的驱动、网络协议栈TFTP、NFS、文件系统FAT、ext4等等。这些东西恰恰是单片机开发里很少碰到的也是嵌入式Linux岗位面试时最爱问的知识点。1.2 上电之后第一行代码到底是谁在跑很多人把“单片机入门嵌入式”理解成“会用STM32CubeMX生成代码就算嵌入式”这是一个极大的误解。真正的嵌入式系统从按下电源到应用跑起来中间经历了好几个阶段每个阶段都有专门的软件在承担职责。搞清楚这条链对你理解整个嵌入式系统的运行逻辑非常有帮助。以一颗常见的Cortex-A系列SoC为例完整的启动链路是这样的SoC内部的BootROM片上ROM出厂固化的代码先执行它根据启动引脚的电平状态决定从SD卡、eMMC、NAND还是USB/串口去加载下一段代码。BootROM加载的是u-boot的SPLSecondary Program Loader二级程序加载器有时还有TPL。SPL非常小主要作用是初始化DDR内存然后从存储介质加载完整的u-boot主体。完整的u-boot运行起来后会进一步初始化串口、网口、Flash控制器等外设然后根据环境变量bootcmd里定义的命令去加载Linux内核镜像zImage/uImage和设备树文件dtb最后跳转到内核入口。Linux内核接管后才会初始化完整的驱动框架、挂载根文件系统rootfs、启动init进程最终进入用户态。这里每一步都不能跳而且每一段代码都有自己的入口地址和运行地址。单片机玩惯了的人会特别不适应——怎么这么麻烦原因其实可以这样理解SoC内部的SRAM太小装不下一个完整u-boot更装不下Linux内核所以必须“接力跑”。BootROM先拉一把SPLSPL搞定大内存再把完整u-boot拉上来u-boot再拉内核。就像你自己做饭锅太小只能一锅一锅炒总不能把整桌菜倒进去一起炒吧。理解这条启动链路之后“学u-boot”这件事就变得非常具体了你要搞懂程序是怎么从Flash/SD卡被搬进内存的、内存是怎么被初始化成可用的、内核镜像是怎么被解析和跳转的、启动参数DTS、bootargs是怎么传给内核的。这一套学明白嵌入式Linux的大门基本就推开一半了。2. u-boot到底是个什么东西2.1 本质一个比单片机程序更复杂的裸机程序u-boot的全称是“Universal Boot Loader”由德国DENX软件工程公司的Wolfgang Denk主导开发支持几十种CPU架构、上千种开发板是嵌入式Linux事实上的标准bootloader。它的代码结构长期从Linux内核借鉴——注意这不是单向的Linux内核的很多早期代码也受u-boot影响。两者的目录布局、Makefile风格、配置体系Kconfig/defconfig高度相似所以如果你先学u-boot再看Linux内核源码会有一种“哎这个套路我见过”的熟悉感学习曲线能平缓不少。从运行形态上看u-boot就是一个跑在开发板上的命令行程序。上电后它会打印一屏启动日志然后停留在一个提示符通常是等待你输入命令。你可以把它理解成一个迷你的“BIOSshell”混合体它不像BIOS那么简单BIOS只负责启动固件也不像Linux shell那么强大没有进程概念。它做的事情是读写内存、操作Flash/SD卡、配置网络、加载内核。最常用的几条命令比如printenv查看环境变量setenv设置环境变量saveenv保存环境变量tftp通过网口下载文件mmc操作SD/eMMCfatls查看FAT分区文件bootz/bootm启动内核。这些命令在实际调试中会反复用熟练之后你甚至在板子起不来内核时不慌——因为你能用u-boot命令手动定位是内核镜像坏了、设备树不对还是启动参数错了。我用一个比喻来帮单片机背景的朋友理解u-boot的分量你平时写STM32的main函数是当“餐厅老板兼厨师”自己蒸饭、自己炒菜、自己端盘。而u-boot是“餐厅前台领位员”客人来了先带到座位初始化硬件再把菜单递过去加载内核镜像客人坐定开始点菜后领位员的工作就完成了。你见过哪家餐厅让领位员去后厨炒菜的吗同理u-boot不会也不该负责Linux内核该干的活。2.2 为什么说这是嵌入式Linux的“必修第一课”市面上的嵌入式Linux学习路线普遍把u-boot放在内核驱动之前我觉得这个安排非常有道理。原因有三点。第一u-boot让你以“最小成本”接触ARM底层。很多单片机玩家觉得ARM很熟但你熟的是Cortex-M内核嵌入式Linux用的多是Cortex-A内核两者的异常模型、内存映射、启动流程差异很大。u-boot代码里包含了ARM架构下很底层的初始化——异常向量表、MMU页表、DDR控制器时序配置、中断控制器GIC等。通过读u-boot源码你能把这些底子补上而且因为u-boot比Linux内核简单得多没有进程调度没有复杂驱动模型读起来压力小。第二u-boot能快速检验你的“硬件认知”。玩单片机时器件地址、寄存器偏移基本是数据手册写好的你照着配就行。但u-boot要面对的是DDR颗粒的时序参数刷新率、CAS延迟、SD卡的分区布局、网络PHY的寄存器配置。你必须在真实硬件上验证这些配置是否正确这种“对着原理图和数据手册去驱动一块真实硬件”的经验在后续写驱动时直接复用。第三u-boot是招聘面试的高频考点。“说说ARM的启动流程”“u-boot的SPL是什么”“设备树和在u-boot里怎么被处理的”“怎么给内核传启动参数”……这些问题在网络上的嵌入式八股文里反复出现。如果你只是背答案很容易被追问穿但你要是真的编译过、烧录过、手动敲命令把内核引导起来过回答起来完全是另一个层次。我把单片机开发和u-boot需要的知识做了一个对比方便你评估自己在哪能力维度单片机开发Cortex-Mu-boot/嵌入式LinuxCortex-A启动流程启动文件向量表基本不用管多级加载BootROM→SPL→u-boot→内核内存直接操作Flash/SRAM无需初始化DDR必须配置DDR控制器时序还要建MMU页表存储介质内部FlashKeil一键下载SD/eMMC/NAND需要处理分区和镜像偏移调试手段仿真器在线调试断点单步串口打印为主网络加载镜像极少用JTAG外设认知GPIO/I2C/SPI/UART裸驱动时钟树、PHY、MAC、DMA、中断控制器源码阅读库函数HAL几百页手册KconfigMakefile设备树几万行代码这张表看下来你应该明白了u-boot是把你从“单片机舒适区”拽出来推向“系统级开发”的过渡层。它的难度不在某一个知识点而在于思维方式的切换——从“代码直接操作硬件”变成“代码需要按框架组织、按配置裁剪、按约定传递信息”。3. 学习路线与实操步骤3.1 别急着买开发板先在QEMU上跑通第一个u-boot我见过不少朋友买了块几百块的开发板拿到手就先烧官方镜像看到Linux桌面出来了兴奋得不行然后呢没有然后了。因为官方镜像已经把u-boot、内核、根文件系统全配好了你根本没有机会接触u-boot。正确做法是先在一个可以“随便折腾”的环境里跑起u-boot把它的命令行、配置、编译流程玩熟再上真实硬件。QEMU就是最好的折腾环境。QEMU是一个纯软件模拟器可以模拟ARM开发板而且u-boot官方源码里就带了QEMU模拟的板级配置。以vexpress-a9为例这是ARM官方的一个虚拟开发板模型常见于各种嵌入式教学操作步骤如下# Ubuntu/Debian系统先安装QEMU和交叉编译工具链 sudo apt install qemu-system-arm gcc-arm-linux-gnueabihf # 下载u-boot源码 git clone https://github.com/u-boot/u-boot.git cd u-boot # 查看支持QEMU的板级配置 ls configs/ | grep vexpress你能看到vexpress_ca9x4_defconfig这样的文件。接下来编译并运行make vexpress_ca9x4_defconfig make -j4 CROSS_COMPILEarm-linux-gnueabihf- qemu-system-arm -M vexpress-a9 -nographic -m 256M -kernel u-boot如果一切顺利QEMU窗口终端里会出现u-boot的启动日志最终停在提示符。这时候你就可以敲help、version、printenv等命令了。我第一次在QEMU里跑起u-boot的时候虽然只是一串字符但那种“我亲手编译的bootloader跑起来了”的感觉比看任何教程都来得踏实。这里提醒一点QEMU模拟的vexpress板子比较老旧u-boot新版本对它的支持时好时坏。如果你用的u-boot版本编译后跑不起来可以适当调整内核版本或者直接用老牌的u-boot-2020.04这种稳定版本。虚拟平台图省事不必追求版本最新。另外QEMU里没有真实串口硬件-nographic参数把串口重定向到了当前终端所以你能直接看到u-boot的打印信息。3.2 从虚拟平台到真实板卡编译和烧录QEMU只是热身真正涨经验的是烧到真实板卡上。我先拿最常见的SD卡启动流程来拆解。绝大多数Cortex-A开发板比如i.MX6ULL、全志V3s、瑞芯微RV1126出厂都是支持SD卡启动的。先把工具链准备好。注意不同的SoC厂商给的交叉编译工具链版本不一样别混用。比如NXP的i.MX系列官方推荐gcc-arm-none-eabi老版本或者Linaro的arm-linux-gnueabihf-gcc全志的BSP可能自带了专门的工具链。编译前先搞清楚目标板子的defconfig名称比如make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- distclean make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- mx6ull_14x14_evk_defconfig make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j4编译产物里你会看到u-boot.bin不同厂商还会生成专属格式的镜像比如NXP会打包出u-boot.imx在u-boot.imx尾部加了IVT头部和校验信息BootROM才能识别全志则生成u-boot-sunxi-with-spl.bin。这些细节差别恰恰是厂商把你和硬件隔开的那道墙——你只有深入进去才明白为什么同样是u-boot烧法却不一样。以SD卡烧写为例NXP的i.MX6ULL要求u-boot.imx从SD卡的偏移1KB处开始写入前1KB是分区表/引导扇区保留区# 假设SD卡设备节点是/dev/sdb务必先确认别写错盘 sudo dd ifu-boot.imx of/dev/sdb bs1024 seek1 convfsync烧完插卡上电串口接好USB转TTL波特率设115200理论上就能看到u-boot的打印了。这一步是很多人的“劝退点”因为串口完全没有输出。别慌大概率是这几个原因SD卡没插到位、启动引脚拨码不对、串口TX/RX接反了、电源功率不够。这些坑我在后面第五节专门展开。3.3 手动引导内核体验“开盲盒”的快乐u-boot能跑只是第一步。真正让你理解u-boot价值的是你亲手用命令把内核“拉起来”。这里我用一个典型场景来说明开发板只有一个串口没有屏幕也没插网线你要把编译好的Linux内核镜像通过SD卡加载进去。先是准备素材把一张SD卡分成两个分区第一个分区格式化为FAT32放zImage内核镜像和board.dtb设备树文件第二个分区可以是ext4放根文件系统。然后把卡插到板子上上电停在u-boot命令行执行以下操作# 先看看SD卡设备编号一般是mmc 0 mmc list # 查看FAT分区里的文件 fatls mmc 0:1 # 把内核镜像和设备树加载到内存 fatload mmc 0:1 0x82000000 zImage fatload mmc 0:1 0x83000000 board.dtb # 设置内核启动参数 setenv bootargs consolettyS0,115200 root/dev/mmcblk0p2 rw rootwait # 启动内核 bootz 0x82000000 - 0x83000000看到这里你可能会有疑问0x82000000这个地址是怎么来的其实这不是随便填的它必须在DDR内存有效范围内并且不能和u-boot自身占用的内存区域冲突。大多数开发板的内存映射都差不多0x80000000是内存起始地址u-boot自己通常运行在0x80000000附近所以把内核加载地址放在0x82000000是约定俗成的方式。但如果你板子内存只有256MB0x800000000x10000000就已经到顶了再往上写就会越界这种情况要降低地址。bootz是启动“非压缩内核镜像”zImagebootm是启动带头部信息的uImage。现在主流平台都用bootz旧平台和某些厂商BSP还在用bootm。两个命令接受的参数格式有差别用前help bootz查一下避免参数传错。当你敲下bootz回车看到Linux内核一串串的启动日志刷出来最后出现Login:的时候那种成就感是不言而喻的。你已经手动完成了“bootloader引导内核”这一整套流程接下来再去学设备树、驱动、系统移植每一步都有真实场景兜底不会觉得飘。4. 需要吃透的几个核心机制4.1 配置体系defconfig、Kconfig和menuconfig玩单片机的朋友对工程文件里的.c和.h很熟但对Linux世界这套“配置树”会很陌生。u-boot从Linux内核学来了Kconfig配置体系每个驱动、每个功能都有对应的Kconfig文件定义选项板级配置用defconfig文件描述图形化配置工具是menuconfig。三者关系可以这样理解Kconfig是“菜单内容”defconfig是“店员推荐的默认搭配”menuconfig是“你自己去菜单里勾选”。你执行make xxx_defconfig时其实是应用了一套默认配置执行make menuconfig可以在此基础上修改最终配置保存在隐藏文件.config里。在u-boot里我强烈建议你自己走一遍“新增板级配置”的流程哪怕是在QEMU的vexpress上修改几个配置项再编译也能帮你建立整体认知。具体来说去configs/目录下找对应的defconfig去arch/arm/mach-xxx/目录下找对应的板级C文件去include/configs/下找头文件老式配置风格再去dts/目录下找对应的设备树源文件。这四个位置基本就是u-boot板级适配的全部阵地了。一个小技巧如果你要适配一块全新的板子最简单的方式不是从零写而是找到一颗“配置最接近的现有板子”复制它的defconfig和头文件然后逐项改内存大小、串口地址、网卡芯片型号。我自己第一次给一块陌生板子移植u-boot时就是拿着一块官方EVK的配置改了三天几乎是照着数据手册一项一项核对寄存器。这个过程很枯燥但收获比看十遍教程都大。4.2 SPL和TPL为什么一次启动要拆成两段前面提到过SPL这里展开说说。u-boot本身不小编译出来轻松超过几百KB而很多SoC内部的SRAM片上静态内存只有几十KB到一两百KB。这就产生了一个矛盾BootROM先要把u-boot从SD卡读进内存那么读进来的这段代码放在哪运行放不下。于是就有了“接力方案”BootROM先把一个裁剪到极小的u-bootSPL读进SRAMSPL只做一件事——配置DDR控制器让外部大内存可用然后再从SD卡把完整u-boot读进DDR跳转运行。SPL这个名字的完整含义是Secondary Program Loader二级程序加载器。如果SoC内存实在太小还能再拆出一级叫TPLTertiary Program Loader三级程序加载器形成BootROM→TPL→SPL→u-boot的链条。第一次看这段启动日志时你可能会有一种“俄罗斯套娃”的感觉这是正常的。SPL的编译方式和u-boot主体是分开的。以NXP i.MX6ULL为例编译SPL需要单独指定make mx6ull_14x14_evk_defconfig make -j4 CROSS_COMPILEarm-linux-gnueabihf- spl编译产物是u-boot-spl.bin。厂商通常把它和u-boot主体打包在一起形成u-boot.imx或u-boot-sunxi-with-spl.bin这样用户只需要烧一个文件BootROM会依次自动加载。理解这一层后你就不会疑惑“为什么u-boot有这么多bin文件”了。这里顺手说一个调试技巧如果u-boot卡在SPL阶段不往后走通常是DDR初始化失败。DDR时序参数调不对代码很容易跑飞而且这种问题很难用打印信息排查因为打印本身依赖串口初始化而串口初始化可能在DDR之后。这种情况下的常规做法是逐项核对DDR颗粒的数据手册参数或者直接借用厂商给的参考配置不要自己乱调。4.3 设备树u-boot也在用不只是内核的专利很多单片机转嵌入式的人第一次听到“设备树Device Tree”这个名词时会觉得很高深。实际上它很简单就是把“板子上有哪些硬件、地址是多少、中断是几号”这些描述信息从C代码里剥离出来写成一种树形结构的文本文件。这样同一个内核可以配合不同的dtb文件适配不同的板子而不用为每块板子编译一遍内核。u-boot从2014年之后开始全面引入设备树现在几乎所有新板子的u-boot配置里都有一份u-boot.dts。它的作用是u-boot运行时从dtb里读取内存大小、串口寄存器地址、GPIO定义、启动参数bootargs可能从设备树的chosen节点里获取等信息。也就是说设备树不仅仅是给内核看的u-boot在加载内核之前就已经在用了。实际操作中你会接触两种设备树源文件格式.dts设备树源文件和.dtsi公共头文件。一个SoC平台有一个.dtsi描述芯片内部资源每块具体板子再写一个.dts引用对应的.dtsi并描述板级差异。这种“继承覆盖”的思路在Linux驱动开发里也非常常见。对于初学者我建议不要把精力放在“研究设备树语法”上——语法文档随时可以查。更重要的是理解设备树在整个启动流程里的位置u-boot编译时附带一份dtb启动内核时再把dtb地址传给内核就是你上面命令里的0x83000000那个参数内核从dtb中读取硬件描述并初始化驱动。所以设备树是连接u-boot和内核的“信息契约”两边对同一份dtb的理解必须一致否则内核对硬件的初始化就会错乱。5. 常见问题与排错实录5.1 串口完全没有输出——先怀疑硬件再怀疑配置这个问题遇到过太多次了。你辛辛苦苦编译完u-bootdd烧进SD卡上电一看串口终端空空如也。这时候容易陷入一个恶性循环反复检查源码、反复重新编译其实问题根本不在代码。正确的排查顺序是这样先确认串口调试助手或minicom的波特率、数据位、停止位设置正确波特率不匹配时屏幕上会出现乱码或完全不显示乱码和黑屏的含义完全不同再测量TX/RX引脚电平是否正常用手碰一下串口芯片是否发热然后用万用表确认板子供电是否稳定很多开发板用USB口供电电流不够时SoC启动到一半就挂了表现就是串口只有几行打印甚至完全没有。软件层面需要确认的依次是编译用的defconfig是否匹配你的板子型号u-boot镜像写入SD卡的偏移是否对应厂商规定的启动头位置有的写1KB有的写8KB写错了BootROM根本找不到启动拨码开关是否切到了SD卡启动模式。一个很多人忽略的细节是SD卡插入时接触不良也会导致完全没有输出因为BootROM读不到代码整个系统就只能“沉默”。我自己的习惯是拿到一块陌生板子先烧厂商官方的uboot镜像确认硬件链路是通的然后再烧自己编译的镜像一步步替换验证。不要一上来就拿自己的“半成品”镜像去烧那样出了问题你根本不知道是硬件问题还是自己改代码引入的问题。5.2 能进u-boot但内核死活起不来串口有输出、u-boot能进命令行了这算是一个小里程碑。但紧接着就会遇到下一个高频坑执行bootz或者boot后内核进度条走了一半就死机或者打印几行乱码后重启。这种问题大多数出在三个方面。第一加载地址不对。内核镜像加载的内存地址必须与实际运行地址一致或者说必须在u-boot能正确重定位的范围内。如果你把zImage加载到了0x60000000但你的板子内存起始地址是0x80000000那内核镜像根本不在有效内存里u-boot一跳转就飞了。这种问题用md命令读一下内存地址就能快速判断“md 0x82000000 10”看看那一段是否真的有内核镜像的数据应该能看到ARM内核的魔数。第二设备树不匹配。内核启动早期会解析dtb来确定硬件信息如果dtb和内核版本不匹配或者dtb描述的内存大小与实际不符内很常见。内核可能在启动早期就panic打印里有“Unflattening device tree”相关的报错。解决方法是确保dtb来源于你编译内核时同版本的arch/arm/boot/dts目录。第三启动参数bootargs不对。root参数指向的设备节点不存在内核挂载根文件系统就会失败报错类似“VFS: Unable to mount root fs”。这个时候检查两个点一是根文件系统分区是否存在fatls mmc 0:2或者ext4ls mmc 0:2看看二是bootargs里的root设备名是否与内核实际识别的设备名一致。很多SoC的内核里MMC设备的编号是变化的/dev/mmcblk0p2和/dev/mmcblk1p2就差一个数字结果却截然不同。5.3 环境变量的那些暗坑u-boot的环境变量是你与u-boot交互的主要媒介但它的“默认值”常常骗人。printenv打印出来的内容是当前生效的配置它可能来自编译时内置的默认值也可能来自存储介质里保存的旧值。如果你改了代码重新编译烧录却发现u-boot行为没变先别怀疑代码——大概率是环境变量里保存了旧值覆盖了新配置。解决方法是执行env default -a恢复默认环境变量然后saveenv保存最后reset重启。如果这样还不行可以考虑把环境变量存储分区擦掉mmc erase相关命令让u-boot彻底“失忆”。这里注意不同板子的环境变量存储位置不一样有的在SD卡固定扇区有的在专用eMMC分区有的在SPI Flash务必查阅板子手册确认。还有一个很隐蔽的坑环境变量值里的特殊字符。比如你要在bootcmd里写多条命令用分号分隔但分号在u-boot命令行里本身也是命令分隔符直接写会被提前拆开。正确的做法是用引号包住整个字符串比如setenv bootcmd fatload mmc 0:1 0x82000000 zImage; fatload mmc 0:1 0x83000000 board.dtb; bootz 0x82000000 - 0x83000000如果忘了加引号u-boot执行到第一个分号就会停下后面的命令不会执行但你知道这种问题不仔细看根本发现不了因为它不会报错只会导致内核“莫名其妙”无法启动。最后提醒一个安全习惯在调试板上随意改环境变量之前先把当前环境变量完整备份下来。方法很简单执行printenv后把输出全部复制保存。我曾经在某块板子上把bootdelay设成0导致每次上电都直接启动内核自己怎么都进不了命令行最后愣是靠着按住某个按键强制进入u-boot才救回来。这种操作失误在嵌入式调试里不算罕见。写在最后说点个人的体会吧。当年我从STM32转到嵌入式Linux最痛苦的不是学不会某个具体技术点而是不知道“学了之后能干嘛”。u-boot是第一个让我产生“原来如此”感觉的东西——它把启动流程、内存管理、设备树、内核引导这些知识点全部串成了一条线每学一个点都能在上电日志里看到它真的在起作用。这种感觉和单片机时代“改一个寄存器然后看LED变化”是完全不同的体验但它同样上头。给准备入坑的朋友一个具体建议不要一上来就看u-boot的源码先跑起来、先会敲命令、先会烧录然后再回到源码里去理解为什么。当你有一天能对着启动日志把从BootROM到Linux内核的每一步讲清楚你就真正跨过了单片机到嵌入式Linux之间的那道门槛。u-boot这个起点选对了后面的内核驱动、系统移植、应用开发都会顺很多。
返回列表