想象你是一支搬家队的队长。今天要服务的这位屋主,家具多到能装满一整个仓库——但房子是全新的毛坯房,水电没通、地板没铺、连门禁卡都还没激活。屋主的行李已经停在楼下卡车上,就等你一声令下。这支搬家队,就是计算机世界里的 Bootloader。CPU 就像一座刚通电的空房子,里面什么程序都没有。要让操作系统这位“屋主”顺利入住,必须先有人替他开门(找到第一条指令)、检查水电(初始化硬件)、铺好地板(为高级语言搭好运行环境)、把家具搬进屋(把系统镜像请进内存),最后把钥匙交到屋主手里(把控制权交给系统)。环环相扣,任何一环出错,屋主只能站在门外干瞪眼。这篇文章,就带你完整走一遍搬家队的作业流程:从刷开门禁卡的那一刻,到钥匙交出去为止。

岗位关联

在 AI 边缘设备与端侧推理大规模落地的今天,掌握 Bootloader 编写能让你在设备起不来、内核不跑、升级变砖时快速定位问题,是嵌入式 AI 工程师的底层看家本领。

目录
  1. 可视化演示
  2. 复位向量启动
  3. 硬件初始化
  4. C环境搭建
  5. 镜像搬运加载
  6. 内核交接控制
  7. U-Boot架构设计
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

复位向量启动

搬家队第一次到新公寓,手里只有一张物业发的门禁卡。刷卡刷哪一层、进哪扇门,决定了整个搬家流程的起点。CPU 上电后也面临同样的处境:内存空空如也,没有任何程序在运行,它怎么知道第一条指令在哪里?答案就是那张“门禁卡”——复位向量。

芯片设计者制造 CPU 时就约定好了:上电后,程序计数器 PC 被硬件强制指向一个固定地址,这个地址就是复位向量。不同架构地址不同,ARM 传统上从 0x00000000 启动,部分 SoC 则映射到 0xFFFF0000 或直接映射到内部 ROM。Bootloader 的第一段汇编代码必须被链接到这个地方,成为 CPU 打开的第一扇门。

复位向量不是孤零零的一条指令,它属于“异常向量表”的一部分。向量表按固定间隔排布各类异常的入口:第 0 项复位、第 1 项未定义指令、第 2 项软中断、后面的预取中止、数据中止、IRQ、FIQ 等。每个表项只占 4 字节,通常只放一条跳转指令(如 b reset_handler),真正的启动代码放在别处,跳转指令只负责指路。

entry_addr=vector_base+vector_index×4\displaystyle \text{entry\_addr} = \text{vector\_base} + \text{vector\_index} \times 4

1. vector_base(向量表基地址):异常向量表在内存中的起始位置,由芯片手册规定。

vector_base\text{vector\_base}

2. vector_index(异常序号):复位是第 0 号、未定义指令是第 1 号、软中断是第 2 号,硬件按编号排布。

vector_index∈{0,1,2,3,…}\text{vector\_index} \in \{0, 1, 2, 3, \ldots\}

3. 乘 4(表项步长):每条向量指令固定 4 字节,序号乘 4 才是真正的字节偏移。

×4\times 4

门禁卡刷开哪扇门,决定了搬家队从哪儿开始干活。写 Bootloader 的第一步,就是确保复位向量这个“门牌号”上的指令能准确把 CPU 带向真正干活的集合点。门牌错了,后面再多的计划都白搭。

硬件初始化

搬家队进门后傻眼了:水龙头不出水、电闸没合、楼道灯不亮——这屋子根本没法干活。上电瞬间的硬件也是这副模样:时钟源可能还不稳定、看门狗正倒计时准备重启系统、内存控制器还在睡觉。任何代码在这种环境下都跑不稳,所以第二站是“通水电气”。

硬件初始化的目标很朴素:让 CPU 依赖的基本设施先可用。最先要做的是“请保安下班”——关闭看门狗。看门狗是芯片内置的一位保安,需要程序定期“签到”(喂狗),一旦超时没签到,它就强制复位整个系统。启动阶段初始化耗时较长,稍不留神就会触发复位,所以 Bootloader 通常一上来就把狗关掉,等系统稳定后再按需启用。

接下来是时钟配置。SoC 一般外接低频晶振(比如 24 MHz),但 CPU 和外设需要更高频率,Bootloader 通过 PLL(锁相环)把低频倍频到目标主频,再分频给各路外设。最后初始化内存控制器(如 DDR 控制器),让外部内存可读可写——在此之前,代码只能龟缩在内部 SRAM 或 ROM 里。

fcpu=fxtal×mp×2s\displaystyle f_{\text{cpu}} = \frac{f_{\text{xtal}} \times m}{p \times 2^s}

1. f_xtal(晶振基准频率):板子上那颗小晶体的固定频率,是一切时钟的源头。

fxtalf_{\text{xtal}}

2. m(倍频系数):PLL 的放大倍数,把低频“撑”到很高的频率。

mm

3. p × 2^s(分频系数):倍频后的频率往往过高,需要再除以分频因子,得到 CPU 与外设需要的频率。

p×2sp \times 2^s

4. f_cpu(CPU 主频):最终生效的工作频率,决定指令执行快慢。

fcpuf_{\text{cpu}}

水电通了,灯亮了,搬家队才能放心摆开架势。如果一个 Bootloader 跳过了时钟或内存初始化,代码就相当于在黑暗中摸索,跑着跑着就“断电重启”。这也是很多板子莫名反复重启的根源。

C环境搭建

水电通了,搬家队在客厅支起工作台:铺帆布、摆工具箱、在地上划分拣区。这些“场地准备”对应到软件世界,就是 C 环境搭建——在跳进 main 函数之前,汇编代码必须先为函数调用和数据访问安排好场地。

C 语言函数调用离不开“栈”:返回地址、局部变量、参数都要在栈上暂存。栈的本质是一块预留的内存区域,由栈指针 SP 指向栈顶,使用时向下增长。启动代码的第一件事,就是把 SP 设置到内存的合适位置,通常放在 RAM 顶部。

SP=stack_top & ∼0x07\displaystyle \text{SP} = \text{stack\_top} \ \& \ \sim 0x07

1. stack_top(栈顶地址):根据平台内存布局选定的栈起点,一般取 RAM 的最高可用地址。

stack_top\text{stack\_top}

2. & ~0x07(对齐掩码):把低 3 位强制清零,让地址对齐到 8 字节边界,这是 ARM 的栈对齐要求。

& ∼0x07\& \ \sim 0x07

3. SP(栈指针):赋值后 SP 指向合法栈区,函数调用与中断压栈才有落脚点。

SP\text{SP}

接着处理两类内存段。第一类叫 BSS 段,存放未初始化的全局变量,链接器只给出它的起止地址,Bootloader 需要把它们逐字清零;第二类叫数据段,存放已初始化的全局变量,它的初始值目前还烧在 Flash 里,必须被复制到 RAM 中。做完清零和搬运,C 源码里那些“看起来理所当然”的全局变量才是真实存在的。

n=bss_end−bss_start\displaystyle n = \text{bss\_end} - \text{bss\_start}

1. bss_start / bss_end(BSS 边界):链接脚本导出的两个符号,标记 BSS 段的起止位置。

bss_start,bss_end\text{bss\_start}, \quad \text{bss\_end}

2. n(清零长度):二者相减得到字节数,指针从 bss_start 一直走到 bss_end。

n=bss_end−bss_startn = \text{bss\_end} - \text{bss\_start}

地板铺好、工具摆齐,后面的 C 代码才有自由活动的空间。很多新手觉得“BSS 清零”多此一举,其实没有这块地板,全局变量捡到的全是历史遗留的旧数据,程序一进 main 就跑偏。

镜像搬运加载

场地收拾完毕,接下来是搬家的大头:把屋主的家具从卡车卸进屋里。在 Bootloader 的世界里,“家具”是操作系统内核镜像,“卡车”则是串口、USB 等传输通道。这一步有两个关键问题:用什么通道搬?搬到哪个位置?

开发极早期,目标板上只有 Bootloader、还没有稳定存储时,串口是最朴素的搬运通道。开发机通过串口把内核镜像逐字节发出去,Bootloader 在内存指定地址一直接收,收完再做校验。USB 则像大货车,带宽大得多,适合几十 MB 的系统镜像,但前提是 Bootloader 已经把 USB 控制器初始化好了。

搬运的目标地址不是随便选的。内核在编译时被链接到一个固定的“期望运行地址”,内部所有跳转和变量访问都基于这个地址。Bootloader 必须把镜像原封不动地搬到链接地址,否则内核一运行就找不到北。搬运完成后再做一次校验(如 CRC32),确认镜像在路上没有损坏。

T=SR\displaystyle T = \frac{S}{R}

1. S(镜像大小):内核镜像的字节数,决定搬运总量。

SS

2. R(传输速率):通道的实际有效速率,串口看波特率,USB 看协议带宽。

RR

3. T(搬运耗时):大小除以速率得到时间,直观解释“为什么 USB 比串口快这么多”。

T=SRT = \frac{S}{R}

家具一件件码放到位,还得对着清单核对有没有磕坏。搬运完成后,Bootloader 的任务就从“搬东西”晋级为“请屋主进门”——也就是下一站的控制交接。

内核交接控制

家具摆好了,搬家队最后一步是“交钥匙”:告诉屋主房子结构、水电开关,然后退到门外,把主场让出去。内核交接同样讲究一个“干净利落”——Bootloader 必须在跳转前把指挥权完整地交出去。

跳转前有三件收尾事。第一,构造启动参数:在 ARM Linux 的约定里,r0 填 0,r1 填机器类型 ID,r2 填设备树(DTB)在内存中的地址。如今设备树一统天下,核心是让内核能从 r2 找到硬件描述。第二,关闭 MMU 和缓存:Bootloader 建立的内存映射对内核没有意义,留着反而会引起一致性错误,必须把地址转换“打回原形”。

第三,真正跳转。汇编指令 b kernel_entry 或直接把 PC 赋值为内核入口地址,CPU 下一条执行的指令就是内核第一行代码。跳转时通常会关闭中断,确保内核以最干净的姿态自行初始化。整个交接过程不允许“脏状态”残留,所以这段代码通常是最精简的汇编。

PC←kernel_entry,R2←dtb_addr\displaystyle \text{PC} \leftarrow \text{kernel\_entry}, \quad \text{R}_2 \leftarrow \text{dtb\_addr}

1. R_2 ← dtb_addr(传设备树):把 DTB 地址写入 r2 寄存器,内核启动早期从该地址读取硬件信息。

R2←dtb_addr\text{R}_2 \leftarrow \text{dtb\_addr}

2. PC ← kernel_entry(跳转):内核入口地址写入程序计数器,下一条指令就是内核代码。

PC←kernel_entry\text{PC} \leftarrow \text{kernel\_entry}

门一交,搬家队就功成身退。Bootloader 从此不再有任何“存在感”,它的全部价值都凝聚在门打开的那一刻。交接够干净,内核才能顺顺利利地开始真正的系统生活。

U-Boot架构设计

小搬家队靠师傅个人经验,大搬家公司靠标准作业手册。嵌入式世界里,U-Boot 就是那本被全行业反复验证的手册:起步早、支持广、文档全,几乎所有主流 SoC 都有它的移植版本。看懂 U-Boot,就等于拿到了行业通用的“搬家标准”。

U-Boot 用多阶段设计控制体积。第一级 SPL(Secondary Program Loader)是运行在内部 SRAM 里的“先遣队”,代码极小,只负责把第二级完整的 U-Boot 从 Flash 或存储介质搬运到内存并跳转;第二级 U-Boot 才有完整的命令行、网络下载、文件系统支持。先遣队的存在,巧妙化解了“完整 U-Boot 放不进小 SRAM”的尴尬。

U-Boot 的另一个核心设计是设备树传递。U-Boot 加载内核的同时,也加载一份描述板级硬件信息的 DTB 文件,并通过寄存器约定把 DTB 地址交给内核,实现“同一份内核、不同板子”的灵活性。这一切行为由环境变量如 bootcmd 编排,开发者可以在命令行里手动干预,也可以把启动流程固化到脚本里。

Boot: ROM⟶SPL⟶U-Boot⟶Kernel\displaystyle \text{Boot:}\ \text{ROM} \longrightarrow \text{SPL} \longrightarrow \text{U-Boot} \longrightarrow \text{Kernel}

1. ROM(出厂固件):SoC 内部固化的一小段代码,负责上电后找到并加载 SPL。

ROM\text{ROM}

2. SPL(先遣队):运行在 SRAM 中的小体积加载器,把真正的 U-Boot 搬进内存。

SPL\text{SPL}

3. U-Boot(主加载器):功能完整的 Bootloader,负责加载内核与 DTB、解析启动命令。

U-Boot\text{U-Boot}

4. Kernel(最终目标):操作系统接管 CPU,整个启动流程到此圆满结束。

Kernel\text{Kernel}

标准手册之所以被选中,是因为它在无数块板子上验证过同一套流程。当你从“按师傅经验搬家”进阶到“读得懂手册、能优化流程”,你就真正算是一名合格的嵌入式启动工程师了。

---

AI 应用场景

场景一:边缘 AI 盒子的“起不来”排障

阶段:模型部署与设备量产

解决的问题:一批 AI 摄像头量产时偶发上电黑屏,传统做法是反复换料、碰运气。理解 Bootloader 各阶段职责后,工程师把搬家队每一站单独“打卡”——通过串口日志确认故障停在哪一站:是没刷开门禁卡(复位向量),还是没通上电(硬件初始化),或是家具搬到一半摔了(镜像校验失败)。

具体案例:某智能摄像头产线 3% 设备启动失败,串口日志停在硬件初始化阶段。对比正常设备后发现是 PLL 倍频系数对部分芯片余量不足,微调参数后不良率直接归零。

场景二:无人零售设备的 OTA 回滚保险

阶段:模型与固件远程更新

解决的问题:AI 识别模型需要频繁 OTA 升级,一旦新镜像有问题,设备可能直接变砖。U-Boot 的 A/B 分区机制像搬家队的“双保险”:先验货(校验 CRC),验不过就退回旧屋(备份分区)。

具体案例:无人零售柜的识别模组推送新模型后,2% 设备重启失败。U-Boot 检测到主分区校验错误,自动加载备份版本,设备 30 秒内恢复服务,运营人员全程无感知。

场景三:车载域控的“冷启动倒计时”

阶段:性能优化与上车量产

解决的问题:自动驾驶域控要求上电后尽快进入系统,Bootloader 每多花一秒,用户就多等一秒,安全冗余也少一秒。通过裁剪 SPL 初始化、并行内存训练、用 DMA 搬运镜像,可以把启动时间显著压缩。

具体案例:某域控原冷启动 8 秒,工程师把硬件初始化里非必要外设移到系统启动后再做,并改用 DMA 搬运内核,最终压进 2 秒,满足车规要求。

Python 演示

python70 行
class FakeMemory:    """用字节数组模拟一块 RAM,Bootloader 在上面读写"""     def __init__(self, size):        self.data = bytearray(size)     def write(self, addr, value):        self.data[addr] = value     def read(self, addr):        return self.data[addr]  class BootLoader:    """用 Python 演示搬家队(Bootloader)的完整启动流程"""     def __init__(self, ram_size=0x0400):        self.ram = FakeMemory(ram_size)        self.sp = 0        self.kernel_entry = 0x0200        self.stage_list = []     def reset(self, vector_base):        """第 0 站:从复位向量取第一条指令"""        first = self.ram.read(vector_base)        self.stage_list.append("reset")        return first     def hardware_init(self):        """第 1 站:关看门狗、配置时钟,这里用标志表示"""        self.stage_list.append("hw_init")        return True     def c_environment(self, stack_top):        """第 2 站:设置栈指针并清零 BSS 段"""        self.sp = stack_top        for addr in range(0x0100, 0x0180):            self.ram.write(addr, 0)        self.stage_list.append("c_env")     def load_image(self, src, dst, size):        """第 3 站:把内核镜像搬运到目标地址"""        for i in range(size):            self.ram.write(dst + i, src[i])        self.stage_list.append("load")     def checksum(self, start, size):        """校验搬运后的镜像完整性"""        return sum(self.ram.read(start + i) for i in range(size))     def boot(self, dtb_addr):        """第 4 站:传参并跳转内核入口"""        r2 = dtb_addr        pc = self.kernel_entry        self.stage_list.append("boot:" + hex(pc))        return pc, r2  if __name__ == "__main__":    loader = BootLoader()    loader.ram.write(0x0000, 0xEA)          # 复位向量放入第一条指令    kernel = bytes(range(256))               # 模拟 256 字节的内核镜像    loader.reset(0x0000)    loader.hardware_init()    loader.c_environment(stack_top=0x03F0)    loader.load_image(kernel, 0x0200, len(kernel))    chk = loader.checksum(0x0200, len(kernel))    pc, r2 = loader.boot(dtb_addr=0x0300)    print("stages:", " -> ".join(loader.stage_list))    print("checksum:", chk, "| pc:", hex(pc), "| dtb r2:", hex(r2))
目的:用 50 行代码把复位向量、硬件初始化、C 环境、镜像搬运、校验与交接完整演一遍,运行后即可看到整条启动链路。
  1. loader.ram.write(0x0000, 0xEA)在复位向量地址放一条指令,相当于把门禁卡插进门锁。
  2. loader.c_environment(stack_top=0x03F0)设栈顶并清 BSS,给 C 语言铺好地板。
  3. loader.load_image(kernel, 0x0200, len(kernel))把内核镜像搬到链接地址 0x0200。
  4. chk = loader.checksum(0x0200, len(kernel))验货:确认镜像没有在搬运中损坏。
  5. pc, r2 = loader.boot(dtb_addr=0x0300)交钥匙:传设备树地址并跳转内核。

常见误区

误区我听别人说 Bootloader 就是把内核从 Flash 搬到内存再跳过去,感觉就是个“拷贝加跳转”的简单程序,剩下的都是内核自己的事。

正解拷贝和跳转只是最后两步。在搬运之前,Bootloader 必须先让自己“活过来”:从复位向量取指令、关看门狗、配时钟、初始化内存、搭好 C 环境,这些前置步骤往往占启动代码的七八成。就像搬家队进门之前先要刷门禁卡、通水电气、铺地板,家具才搬得动。

为何常见:人们习惯从“Bootloader 的最终职责”反推它的全部工作,忽略了过程性步骤,倒果为因。

误区我一直以为程序上电就执行 C 语言的 main 函数,写裸机程序时直接从 main 开始写也能跑,说明 main 就是起点。

正解main 是 C 运行时环境准备好之后才会被调用的。上电瞬间 CPU 只认复位向量地址,从那里取第一条汇编指令,依次完成硬件初始化和 C 环境搭建后,才有 main 的登场。直接写 main 能跑,是因为开发工具链悄悄替你做了这些事,而不是硬件本身就认识 main。

为何常见:高级语言和集成开发环境把启动细节“藏”起来了,初学者看不到汇编启动文件就觉得不存在。

误区看门狗能防程序跑飞,Bootloader 一上来就关狗,这不是破坏系统安全吗?

正解看门狗防的是“运行中”的系统卡死,需要程序定期喂狗。启动阶段初始化耗时很长,如果狗还在跑,没来得及喂就会被强制复位,导致系统不断重启。所以 Bootloader 通常先关狗或周期性喂狗,等系统稳定后再重新启用。搬家队刚进门搬东西时,保安不停拉警报只会坏事。

为何常见:把“看门狗防死机”的最终目的直接套到启动场景,忽略了喂狗时序这个前提。

误区只要内核镜像在内存里,从哪个地址运行不都一样吗?我把它加载到任意空闲内存应该没问题。

正解内核编译时按固定的链接地址生成,内部所有跳转、变量访问都是绝对地址。搬运目标如果与链接地址不一致,内核第一条指令就开始“迷路”,访问到错误位置直接崩溃。这就好比家具按户型图标注了摆放位置,搬错房间整个家就乱了套。

为何常见:日常开发中“文件拷贝到哪都能用”的直觉被错误地迁移到了地址相关的底层镜像上。

误区串口一秒钟才十几 KB,USB 快上百倍,Bootloader 真没必要再用串口。

正解串口只有两根线、协议极简、任何 SoC 都自带 UART,是调试和早期启动的“保底通道”;USB 速度快,但需要复杂的控制器初始化和协议栈,Bootloader 早期未必来得及准备好。实际工程里两者分工:调试用串口,量产下载用 USB 或网络。

为何常见:单看速率指标做判断,忽略了可用性、初始化复杂度和启动时序的现实约束。

面试题

Q1. CPU 上电后第一条指令从哪里取?

单选题 · easy

  • A.操作系统的 main 函数
  • B.复位向量指定的地址← 正确答案
  • C.用户程序的起始入口
  • D.内存里的随机位置

答案:B。搬家队的“门禁卡”就是复位向量。CPU 上电后 PC 被硬件强制指向复位向量地址,而不是高级语言的 main,也不是随机位置。A 是 C 语言运行时的入口,硬件不认识它;C 是用户程序的入口,同样不是硬件起点;D 说随机地址与“固定约定”完全矛盾。

Q2. ARM 异常向量表中每个表项占多少字节?

单选题 · easy

  • A.一条指令占 1 字节
  • B.一条指令占 2 字节
  • C.一条指令占 4 字节← 正确答案
  • D.一条指令占 8 字节

答案:C。ARM 指令固定编码为 32 位,正好 4 字节,所以异常向量表每个表项占 4 字节,通常只放一条跳转指令。A、B、D 的 1/2/8 字节都不符合 ARM 指令宽度,选 C。

Q3. Bootloader 进 C 环境前为什么要设置栈指针?

单选题 · mid

  • A.让 CPU 运行得更快
  • B.为函数调用提供栈空间← 正确答案
  • C.给全局变量初始赋值
  • D.把内核搬运到内存中

答案:B。C 函数调用的返回地址、局部变量都要压在栈上,SP 不设置好,函数一调用就没地方落脚。A 的“更快”与栈无关;C 是数据段和 BSS 的职责;D 是镜像搬运阶段的事,与栈指针无直接关系。

Q4. BSS 段清零的目的是什么?

单选题 · mid

  • A.把未初始化全局变量清零← 正确答案
  • B.压缩内核镜像的体积
  • C.提高串口波特率的上限
  • D.让看门狗提前重复工作

答案:A。BSS 段约定存放未初始化全局变量且初值为 0,上电后 RAM 里是随机值,必须逐字清零。压缩体积、提高波特率、调整看门狗都与 BSS 无关,故选 A。

Q5. 下面哪个是 Bootloader 跳转内核前必须做的交接动作?

单选题 · hard

  • A.先开启 MMU 再做地址映射
  • B.传参并把 PC 指向内核入口← 正确答案
  • C.重新初始化一遍 USB 控制器
  • D.把完整启动日志打印完毕

答案:B。跳转内核前必须“交钥匙”:把 DTB 地址放进 r2,把内核入口写进 PC。A 方向反了,交接前要关闭 MMU 而非开启;C 是多余的重复动作;D 打印日志不是交接的必要条件。

Q6. 内核镜像的搬运目标地址取决于什么?

单选题 · mid

  • A.串口波特率的大小决定
  • B.内核链接时约定的地址← 正确答案
  • C.Flash 存储芯片的容量
  • D.电池剩余电量的多少

答案:B。内核按链接地址编译,所有地址引用都在该地址上成立,所以搬运目标必须等于链接地址。波特率只影响传输速度,Flash 容量决定存储空间,电池电量更是毫不相干。

Q7. U-Boot 的 SPL 主要解决什么问题?

单选题 · easy

  • A.解决内核镜像体积过大的问题
  • B.完整 U-Boot 放不进小 SRAM← 正确答案
  • C.串口传输速率太低的问题
  • D.设备树文件缺失导致的问题

答案:B。SPL 的价值在于“先遣队”:完整 U-Boot 太大放不进内部 SRAM,先用小体积 SPL 把它从存储介质搬到内存再接力。A 把问题归给内核,实际上内核由 U-Boot 加载;C、D 都不是 SPL 诞生的原因。

Q8. 下面哪些是硬件初始化的典型任务?

多选题 · mid

  • A.关闭看门狗定时器← 正确答案
  • B.配置 PLL 系统时钟← 正确答案
  • C.初始化内存控制器← 正确答案
  • D.编译设备树二进制

答案:ABC。硬件初始化三件套是关看门狗、配时钟、初始化内存控制器,分别对应“请保安下班”“接通水电”“打开仓库门”。D 编译设备树发生在开发机上,不属于目标板硬件初始化。

Q9. 跳转内核前的“交接工作”包括哪些?

多选题 · hard

  • A.构造并传递内核启动参数← 正确答案
  • B.关闭 MMU 和缓存一致性← 正确答案
  • C.对 BSS 段进行逐字清零
  • D.校准串口的数据帧格式

答案:AB。交接工作 = 构造启动参数 + 关闭 MMU/缓存 + 跳转入口。C 的 BSS 清零属于 C 环境搭建阶段,发生在交接之前;D 校准串口帧格式与交接无关。故选 A、B。

Q10. 关于 U-Boot 设备树传递的说法,正确的是?

多选题 · extreme

  • A.DTB 描述板级硬件信息← 正确答案
  • B.通过寄存器传递 DTB 地址← 正确答案
  • C.同一内核适配不同板卡← 正确答案
  • D.DTB 是内核唯一的存储格式

答案:ABC。DTB 描述板级硬件信息,U-Boot 通过寄存器约定把 DTB 地址传给内核,让同一份内核适配不同板卡。D 说 DTB 是内核唯一存储格式是错的,内核还依赖分区表、文件系统等,故排除 D。