尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从单片机到u-boot:用QEMU跑通ARM64嵌入式Linux启动流程

发布时间:2026/9/29 20:49:34

资讯中心
01
ARTICLE

从单片机到u-boot:用QEMU跑通ARM64嵌入式Linux启动流程

从单片机到u-boot:用QEMU跑通ARM64嵌入式Linux启动流程
嵌入式这条路很多人是从51单片机、STM32一路摸爬滚打过来的。点个灯、驱动个数码管、接个DHT11读温湿度、用LCD1602显示两行字符这些确实是入门的经典项目也是很多人对嵌入式三个字的第一印象。但如果你只停在这一层会发现一个尴尬的现实面试的时候聊中断、聊寄存器、聊裸机时序你头头是道一旦对方问系统是怎么启动的内核镜像怎么被加载到内存的设备树是干嘛的就卡壳了。这不是能力问题是学习路径的问题——单片机让你理解了硬件但没有让你理解一个完整的计算机系统是怎么从加电到跑起操作系统的。u-boot就是补上这一环的关键。它是嵌入式Linux系统里最核心的引导程序负责从上电那一刻开始初始化DDR、加载内核、传递参数最后把控制权交给Linux。学u-boot本质上是在学一台ARM64机器是怎么活过来的。而且现在有个特别好的条件你不需要买开发板用QEMU就能在电脑上模拟一颗ARM64处理器把整个启动流程跑通。这篇文章就围绕u-boot、嵌入式、单片机、ARM64、QEMU这几个关键词把从单片机思维切换到系统级思维的完整路径讲清楚包括为什么要学、怎么搭环境、启动流程怎么拆、踩坑怎么排。适合已经玩过单片机、想往嵌入式Linux方向进阶的读者也适合想搞明白应用层开发到底算不算嵌入式这个争论的人。1. 从单片机到u-boot思维方式的根本切换1.1 单片机思维和系统级思维差在哪玩51或者STM32的时候你的世界是一个main函数打天下。上电之后芯片内部的Flash里存着你的程序复位向量跳到入口初始化时钟、配置GPIO、进while(1)循环整个系统的控制权从头到尾都在你手里。你清楚地知道每一行代码在干什么甚至能数出每条指令占几个机器周期。这种完全掌控的感觉很爽也是单片机让人上瘾的原因。但系统级开发完全是另一套逻辑。一台跑Linux的ARM64设备上电之后第一段执行的代码不是你写的是芯片厂商固化在ROM里的BootROM。BootROM会去读启动介质eMMC、SD卡、SPI Flash加载一个叫SPL或者TPL的小程序再由它去初始化DDR内存然后把完整的u-boot加载到内存里运行。u-boot跑起来之后才会去读内核镜像、设备树、根文件系统最后跳转到内核入口。整个过程像一场接力赛每一棒都只负责自己那一段然后把接力棒交出去。这个差异带来的直接后果是你不能再靠读一遍代码就懂了来理解系统。你需要理解启动链路、内存布局、镜像格式、参数传递协议这些概念。举个具体的例子单片机里你要点亮一个LED直接操作寄存器就行但在u-boot阶段你要让串口输出字符得先确认串口控制器的时钟有没有使能、引脚复用有没有配对、波特率分频怎么算而这些信息往往来自设备树而不是硬编码。思维方式从我控制一切变成了我在一个约定好的框架里做我该做的事。1.2 为什么u-boot是绕不过去的一课有人会问我直接学Linux驱动开发不行吗为什么要啃u-boot答案是你可以跳过u-boot的源码细节但你跳不过它的存在。只要你的设备是跑Linux的启动流程里就一定有引导程序这一环。当你的板子起不来、内核卡在Starting kernel...、或者串口一片空白的时候如果你不懂u-boot你连问题出在哪一段都判断不了。更现实的一点是嵌入式岗位的面试里u-boot相关的问题是区分会玩单片机和懂系统的分水岭。常见的问题包括u-boot的启动流程分几个阶段、SPL和u-boot proper的区别、bootargs是怎么传给内核的、设备树在启动过程中什么时候被解析。这些问题没有标准答案背必须真的动手跑过一遍才能答得踏实。而且u-boot本身是一个非常好的C语言工程学习样本。它代码量大、模块化清晰、跨平台支持广泛里面有大量关于内存管理、驱动模型、命令行解析的实战写法。你哪怕不为了启动流程单纯把它当成一个大型C项目来读收获也比啃那些玩具项目大得多。1.3 应用层开发到底算不算嵌入式这个话题在社区里吵了很多年。我的看法是算但只是嵌入式的一部分。嵌入式这个词描述的是嵌入到设备里的计算机系统它天然包含硬件、系统软件、应用软件三个层次。你在ARM64板子上用Qt写界面、用Python做数据处理这当然是嵌入式开发只不过你站在了最上层。问题在于如果你只做应用层你对下面两层的理解就是黑盒。系统起不来你只能等别人修性能上不去你不知道是驱动问题还是应用问题内存泄漏你分不清是内核还是自己的代码。而学u-boot的价值就是把这个黑盒打开一条缝让你知道下面发生了什么。你不需要成为内核专家但你需要有往下看一层的能力。这也是为什么很多团队招嵌入式应用开发时会问一些启动流程和系统层面的问题——他们想要的是能定位问题的人不是只会调API的人。2. 用QEMU搭一套ARM64的u-boot实验环境2.1 为什么选QEMU而不是买开发板学u-boot最传统的路径是买一块ARM开发板比如树莓派、全志的板子或者NXP的i.MX系列。但这条路对初学者有几个坑板子要钱、串口线要钱、烧录器可能要钱更麻烦的是一旦把板子搞成砖恢复起来很折腾。而且不同板子的启动细节差异很大你在这块板子上学到的经验换一块板子可能就不适用了。QEMU的好处是它把硬件变成了软件。你可以用一条命令模拟出一颗ARM64的CPU、一块内存、一个串口然后把你编译出来的u-boot直接喂给它运行。整个过程不需要任何物理硬件出问题了删掉重来就行成本几乎为零。更重要的是QEMU的硬件模型是固定的、可预测的你不需要去猜这块板子的DDR是怎么初始化的可以把精力集中在启动流程本身。当然QEMU也有局限。它模拟的是通用硬件很多真实板子上的外设它没有比如特定的PMIC、复杂的时钟树。但对于理解u-boot的启动流程、命令行操作、镜像加载这些核心概念来说QEMU完全够用。等你把流程跑通了再上真实板子会发现只是换了个硬件配置逻辑是一样的。2.2 工具链和依赖的安装细节在开始之前你需要准备几样东西一个能编译ARM64代码的交叉编译器、QEMU的ARM64模拟器、以及u-boot的源码。这里以常见的Linux开发环境为例说明如果你用的是其他系统思路类似只是包管理命令不同。交叉编译器推荐用aarch64-linux-gnu-这一套它是GNU工具链的ARM64版本。安装之后你的gcc、ld、objcopy这些工具都会带上aarch64-linux-gnu-前缀。为什么要用交叉编译而不是本机编译因为你的电脑大概率是x64架构编译出来的代码是给x64跑的而我们要的是ARM64的机器码必须用专门的编译器生成。QEMU需要装qemu-system-aarch64这个组件注意不是qemu-user后者是跑单个程序的前者才能模拟一整台机器。安装完之后可以用qemu-system-aarch64 --version确认一下。u-boot源码从官方仓库获取即可。这里有个经验不要一上来就追最新的master分支新代码可能引入你还不理解的改动。建议选一个稳定的release tag比如某个年份的稳定版本社区资料多遇到问题好搜。提示交叉编译器和QEMU的版本尽量选新一点的稳定版。太老的版本可能不支持某些ARM64特性导致编译或运行时报一些莫名其妙的错误排查起来很浪费时间。2.3 编译u-boot时的配置选择u-boot支持几百种板子每种板子对应一个配置文件放在configs/目录下。对于QEMU的ARM64虚拟平台官方提供了一个叫qemu_arm64_defconfig的配置。用make qemu_arm64_defconfig就能生成对应的.config文件。这里要解释一下为什么用defconfig而不是手动配置。u-boot的配置项有上千个手动配基本不可能。defconfig是维护者为特定平台预设好的一套配置包含了这个平台需要的驱动、命令、内存布局等。你基于它改比从零开始靠谱得多。编译的时候指定交叉编译器的前缀命令大概是make CROSS_COMPILEaarch64-linux-gnu-。编译完成后在源码根目录下会生成一个u-boot.bin这就是我们要喂给QEMU的镜像。如果配置里启用了SPL还会生成u-boot-spl.bin这个后面讲启动流程的时候会用到。编译过程中常见的报错是缺少依赖库比如libssl-dev、bison、flex、swig这些。u-boot的构建系统会用到它们来生成一些工具和头文件。遇到报错不要慌看错误信息里缺什么就装什么这是最直接的排查方式。2.4 启动QEMU并连上u-boot控制台编译出u-boot.bin之后用QEMU启动它的命令大致是这样的结构指定机器类型为virtCPU为cortex-a57或者max内存大小比如1G然后用-bios或者-kernel参数把u-boot镜像加载进去最后把串口重定向到标准输入输出。启动之后你应该能在终端里看到u-boot的启动日志最后停在一个提示符前面。这个提示符就是u-boot的命令行相当于u-boot的shell。你可以在这里敲命令比如help看支持哪些命令bdinfo看板级信息printenv看环境变量。如果启动之后什么都没输出最常见的原因是串口没配对。QEMU的virt机器默认的串口是ttyAMA0u-boot的配置里要对应上。另一个可能是镜像加载地址不对u-boot需要被加载到它预期的内存位置。这两个问题在第一次搭环境的时候几乎必然会遇到后面排错章节会详细讲。3. u-boot启动流程的逐段拆解3.1 上电之后的第一段代码BootROM与SPL要理解u-boot得先理解它前面还有什么。真实的ARM64芯片上电后CPU会从一个固定的地址开始执行这个地址里放的是芯片厂商固化好的BootROM代码。BootROM做的事情很有限它根据启动引脚或者熔丝位的配置去某个介质比如SD卡、eMMC的固定偏移处读取一小段代码加载到芯片内部的SRAM里执行。这段被加载的代码就是SPLSecondary Program Loader。为什么要有SPL因为DDR内存还没初始化而完整的u-boot太大放不进内部SRAM。SPL的体积被严格限制通常几十KB它的核心任务就一个把DDR初始化好。DDR初始化是个很精细的活涉及时序参数、电压配置通常由芯片厂商提供的代码完成。DDR初始化完成后SPL把完整的u-boot从存储介质读到DDR里然后跳过去执行。从这一刻起系统才有了足够的内存空间跑一个功能完整的引导程序。在QEMU里因为虚拟硬件简化了很多有时候可以跳过SPL直接跑u-boot proper但理解这个两阶段的设计对看懂真实板子的启动至关重要。3.2 u-boot proper都做了哪些初始化完整的u-boot跑起来之后会做一系列初始化顺序大致是设置异常向量表、初始化CPU关键寄存器、配置时钟和串口这样你才能看到输出、初始化内存管理、扫描并初始化各种驱动、最后进入命令行或者自动执行启动命令。这个顺序不是随便定的。串口必须尽早初始化否则后面的日志你什么都看不到出了问题就是两眼一抹黑。驱动初始化放在后面是因为很多驱动依赖内存管理和时钟系统就绪。u-boot采用了一种叫驱动模型Driver Model的框架来管理这些驱动每个驱动声明自己的依赖和初始化优先级框架按顺序调用。这里有个容易混淆的点u-boot的驱动模型和Linux的设备树驱动模型不是一回事虽然它们都叫driver model都用设备树。u-boot的驱动模型更轻量目的是在引导阶段快速把必要的硬件拉起来不追求完整的电源管理和热插拔支持。理解这个差异你就不会拿Linux驱动的思路去套u-boot。3.3 环境变量和启动命令的解析逻辑u-boot最实用的部分之一是环境变量系统。你可以用setenv设置变量用saveenv保存到存储介质用printenv查看。环境变量里最重要的两个是bootcmd和bootargs。bootcmd定义了u-boot启动后自动执行的命令序列bootargs则是要传给Linux内核的启动参数。bootcmd的典型内容是一串命令比如先把内核镜像从存储介质读到内存的某个地址再把设备树读到另一个地址最后用booti命令跳转到内核。这个地址不是随便选的必须符合内核的要求也要避开u-boot自己占用的内存区域。地址选错了内核加载进去就会覆盖u-boot的代码导致跳转之后直接崩溃。bootargs里常见的内容包括根文件系统的位置比如root/dev/mmcblk0p2、控制台设备consolettyAMA0,115200、内存大小等。这些参数被u-boot写到一个约定的内存位置内核启动时会去读。如果bootargs写错了典型症状是内核起来了但找不到根文件系统最后panic。3.4 从u-boot跳转到内核的那一瞬间当所有准备工作做完u-boot执行booti针对ARM64的Image格式或者bootm命令时它会做几件事关闭中断、清理缓存、把CPU寄存器设置成内核约定的状态、然后跳转到内核入口地址。这个跳转是不可逆的跳过去之后u-boot就死了控制权完全交给内核。内核入口处会检查一些约定比如CPU的某个寄存器里应该存着设备树在内存中的地址。如果这个约定没满足内核可能起不来或者起来之后找不到硬件。这也是为什么设备树必须被正确加载到内存并且地址被正确传递。理解这一瞬间的意义在于它解释了为什么u-boot和内核是解耦的。u-boot不需要知道内核内部怎么实现内核也不需要知道u-boot怎么加载它双方只通过内存里的数据和寄存器状态这个接口通信。这种设计让两者可以独立演进也让移植变得相对容易。4. 实操中那些文档不会告诉你的坑4.1 串口没输出时的排查链路第一次跑QEMU十有八九会遇到启动之后终端一片空白的情况。这时候不要瞎改配置按顺序排查。第一步确认QEMU命令里串口重定向参数写对了-serial mon:stdio或者-nographic这类参数决定了串口输出去哪。第二步确认u-boot配置里的控制台设备名和QEMU虚拟平台匹配virt机器一般是ttyAMA0。第三步确认波特率一致u-boot默认115200QEMU这边也要对应。如果这三步都没问题还是没输出那可能是u-boot根本没跑起来。这时候可以加QEMU的-d参数打开调试日志看看CPU到底执行到哪了。另一个技巧是用-S -s参数让QEMU启动后暂停并等待调试器连接然后用gdb连上去单步执行看第一条指令在哪。这个方法比较重但能定位到最底层的问题。我踩过的一个坑是编译出来的u-boot镜像格式不对。QEMU的-bios参数期望的是裸二进制而-kernel参数对镜像格式有特定要求。如果拿u-bootELF格式去喂-biosQEMU会拒绝加载或者加载后跑飞。正确的做法是用u-boot.bin这个经过objcopy处理的裸二进制。4.2 内存地址冲突导致的诡异崩溃u-boot、内核、设备树都要加载到内存里它们各自的地址必须规划好不能重叠。在QEMU里内存起始地址和大小是启动参数决定的u-boot自己会占用一部分剩下的才能给内核用。如果你把内核加载地址设得离u-boot太近加载过程中就会覆盖u-boot的代码症状是跳转之后系统直接挂掉没有任何日志。排查这类问题的思路是先用bdinfo命令看u-boot报告的内存布局确认可用内存范围。然后检查bootcmd里加载内核和设备的地址是否落在这个范围内并且彼此不重叠。一个稳妥的做法是让内核加载地址离u-boot的结束地址留出足够余量比如几MB的间隔避免边界情况。设备树的地址也要注意。设备树本身不大通常几十KB但它必须在内核能访问到的内存区域。有些平台要求设备树放在特定的对齐地址上这个要看具体平台的文档。QEMU的virt平台相对宽松但养成规划地址的习惯对以后上真实板子有好处。4.3 环境变量保存失败的原因在QEMU里用saveenv保存环境变量时可能会报错说找不到存储设备。这是正常的因为QEMU的virt平台默认没有挂载可写的持久化存储。u-boot的环境变量默认存在某个Flash或者eMMC的特定分区里虚拟平台如果没有模拟这个存储保存自然失败。解决办法有两个一是接受环境变量只在当前会话有效重启就丢二是给QEMU挂一个虚拟的存储设备比如用-drive参数挂一个镜像文件然后在u-boot里配置环境变量存储位置指向它。对于学习启动流程来说第一种就够了因为我们要验证的是启动逻辑不是持久化。但这里有个隐藏的坑如果你在bootcmd里依赖了某个环境变量而这个变量是手动setenv的、没保存那么下次重启它就没了启动行为会和你预期的不一样。调试阶段建议把关键配置直接写死在命令里或者每次启动都重新设置一遍避免被这个问题迷惑。4.4 交叉编译工具链版本不匹配的报错交叉编译工具链的版本和u-boot源码版本之间有时会有兼容性问题。典型症状是编译时报一些奇怪的语法错误或者链接时找不到某些符号。这通常是因为新版本的编译器对C标准更严格而老版本的u-boot代码里有一些不太规范的写法。遇到这种情况第一反应不要是去改u-boot源码而是先确认工具链版本。可以查一下u-boot官方文档里推荐的工具链版本范围或者看看社区里其他人用的是什么版本。如果实在要用新工具链可以在编译参数里加一些兼容性选项比如放宽某些警告。但更稳妥的做法是换一个和源码版本匹配的工具链省去一堆麻烦。还有一个常见问题是32位和64位的混淆。ARM64的工具链前缀是aarch64-如果你不小心用了arm-前缀的32位工具链编译出来的代码架构不对QEMU加载后会直接报非法指令。确认前缀是排查这类问题的第一步。5. 把u-boot学透之后能做什么5.1 读懂真实板子的启动日志学完u-boot的启动流程你再拿到一块真实开发板看它的串口启动日志就不会一头雾水了。日志里每一行对应启动的哪个阶段哪些是BootROM输出的哪些是SPL输出的哪些是u-boot proper输出的你能分得清清楚楚。当启动卡住的时候你能根据最后一行日志判断问题出在哪个阶段是DDR没初始化好还是内核镜像加载失败还是设备树有问题。这种能力在实际工作中非常值钱。很多嵌入式项目的调试时间一大半花在系统起不来上。能快速定位启动问题的人在团队里往往是救火队员的角色。而这个能力的门槛就是你真的跑通过一遍完整的启动流程知道每个阶段在干什么。5.2 为内核和驱动学习打地基u-boot是内核的前置知识。你理解了u-boot怎么加载内核、怎么传参数、怎么准备设备树再去学内核启动流程就会顺很多。内核的早期初始化、设备树的解析、驱动的probe顺序这些概念在u-boot阶段都有对应的影子。而且u-boot里的驱动模型和内核的驱动模型在思想上是相通的都是设备树描述硬件、驱动匹配设备、框架管理生命周期。你在u-boot里把这套思想理解了到内核里只是细节更多、机制更复杂核心逻辑是一样的。这比直接啃内核代码要友好得多。5.3 嵌入式学习路线的重新规划如果你现在还在单片机的阶段我的建议是不要急着丢掉它而是把它当成基础往上叠加系统级的知识。一条比较顺的路线是先用单片机把GPIO、中断、定时器、串口这些基础外设玩熟理解硬件是怎么被软件控制的然后学u-boot理解一个系统是怎么启动的接着学内核和驱动理解操作系统怎么管理硬件最后回到应用层这时候你写的应用代码对下面发生什么是有感知的。这条路线的好处是每一层都建立在下一层的基础上不会出现学了很多但串不起来的情况。而且每一层都有可以动手的实验单片机有点灯u-boot有QEMU启动内核有驱动模块应用层有Qt或者命令行工具。动手做过的东西才是真的学会了。回到标题那个问题还在玩单片机入门嵌入式要不要学u-boot我的答案是单片机是起点不是终点u-boot是你从会玩芯片走向懂系统的必经之路。而QEMU让这条路变得前所未有的好走——不用花钱买板子不用怕搞坏硬件一台电脑就能把ARM64的启动流程跑通。我自己的体会是第一次在QEMU里看到u-boot的提示符时那种我真的让一颗虚拟CPU活过来了的感觉比点亮一百个LED都来得实在。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。