3、Boot分区详解:内核与ramdisk结构、dtb/dtbo集成方式、boot.img打包与解包工具
好,咱们今天来啃一块硬骨头——Boot分区。
说实话,Boot分区是AOSP定制里最容易翻车的地方之一。我早期做项目时,就因为在dtbo上少打了个补丁,导致设备死活起不来,折腾了两天才找到原因。嗯,咱们今天就把这块彻底讲透。
3.1 Boot分区整体结构
先看一张我手绘的结构图(你脑子里想象一下):
+-----------------------+
| Boot Header | <-- 固定大小,描述分区信息
+-----------------------+
| Kernel (zImage) | <-- 压缩后的Linux内核
+-----------------------+
| Ramdisk (initrd) | <-- 根文件系统,包含init进程
+-----------------------+
| Second Stage | <-- 可选,某些平台用
+-----------------------+
| DTB / DTBO | <-- 设备树,硬件描述信息
+-----------------------+
| Recovery DTBO | <-- 仅recovery模式使用
+-----------------------+
说白了,boot.img就是把上面这些东西打包成一个文件。你刷机时刷的就是它。
我个人习惯把Boot分区比作「手机的起跑线」。内核是发动机,ramdisk是驾驶员,dtb是地图。任何一个出问题,车都开不动。
3.2 内核与ramdisk:谁先谁后?
启动流程是这样的:
- Bootloader加载boot.img到内存
- 解析Boot Header,找到内核起始地址
- 解压内核,跳转到内核入口
- 内核挂载ramdisk作为根文件系统
- 执行init进程,启动用户空间
这里有个关键点:ramdisk是临时文件系统。内核启动后,会从ramdisk里加载驱动、挂载真正的system分区,然后切换到system分区继续启动。
重要:ramdisk里必须包含init程序,否则内核启动后会直接panic。我见过有人把ramdisk删空了,结果手机变砖。
ramdisk的典型内容:
ramdisk/
├── init
├── init.rc
├── system/
├── data/
├── proc/
├── sys/
├── dev/
└── sbin/
├── adbd
└── recovery
你想想看,init.rc里定义了服务启动顺序,比如zygote、surfaceflinger这些。如果ramdisk里少了某个关键服务,系统就卡在开机动画了。
3.3 DTB与DTBO:硬件描述的艺术
设备树(Device Tree)是Linux内核用来描述硬件信息的机制。说白了,就是告诉内核:「我这块板子上有哪些外设,地址是多少,中断号是多少」。
Android里分两种:
- DTB (Device Tree Blob):编译后的设备树,直接嵌入到boot.img里
- DTBO (Device Tree Blob Overlay):叠加层,用于支持多个硬件变体
为什么要搞DTBO?
举个例子:同一款手机有高配版和低配版,摄像头模组不同。如果每个版本都编译一个完整的boot.img,维护成本太高。DTBO允许你只修改叠加层,内核保持不变。
我的经验:在MTK平台上,DTBO经常用来区分不同屏幕面板。我曾经因为DTBO里少配了一个timing参数,导致屏幕闪烁。排查了三天才发现是设备树的问题。
DTB/DTBO的集成方式:
| 方式 | 位置 | 适用场景 |
|---|---|---|
| 内嵌到内核 | 内核镜像内部 | 单设备、调试阶段 |
| 附加到boot.img | 内核之后、ramdisk之前 | 通用方案,兼容性好 |
| 独立DTBO分区 | 单独分区(如dtbo_a、dtbo_b) | 多硬件变体、AB分区 |
我个人推荐第三种方式。为什么呢?因为独立分区方便OTA升级时只更新设备树,不用动内核。你想想看,如果只是换了个传感器,却要刷整个boot.img,多浪费流量。
3.4 boot.img打包与解包工具
工欲善其事,必先利其器。咱们来看看常用的工具。
3.4.1 mkbootimg:打包工具
这是AOSP自带的工具,源码在 system/tools/mkbootimg/ 下。
基本用法:
mkbootimg \
--kernel zImage \
--ramdisk ramdisk.img \
--dtb dtb.img \
--base 0x80000000 \
--pagesize 2048 \
--cmdline "console=ttyS0,115200" \
--output boot.img
参数说明:
--kernel:压缩后的内核镜像--ramdisk:ramdisk镜像(cpio格式压缩)--dtb:设备树文件--base:内存基地址,不同平台不一样--pagesize:页大小,通常是2048或4096--cmdline:内核启动参数
注意:base地址不能乱填。我见过有人把高通的base地址套用到MTK上,结果内核解压失败。每个SoC厂商都有自己的内存布局,一定要查datasheet。
3.4.2 unpackbootimg:解包工具
解包同样简单:
unpackbootimg -i boot.img -o output_dir/
解包后你会得到:
boot.img-zImage:内核boot.img-ramdisk.gz:压缩的ramdiskboot.img-dtb:设备树boot.img-cmdline:启动参数boot.img-base:基地址
ramdisk需要进一步解压:
gunzip boot.img-ramdisk.gz
mkdir ramdisk && cd ramdisk
cpio -idmv < ../boot.img-ramdisk
这样你就能看到ramdisk里的所有文件了。
3.4.3 第三方工具:AIK (Android Image Kitchen)
如果你觉得命令行太麻烦,可以用AIK。它提供了图形化界面,一键解包/打包。
但我个人还是推荐命令行。为什么呢?因为自动化脚本里你不可能点鼠标。CI/CD流水线里,一行命令搞定才是王道。
3.5 实战:定制一个boot.img
假设我们要修改ramdisk里的init.rc,添加一个自定义服务。
步骤:
- 解包:
unpackbootimg -i boot.img -o my_boot/ - 解压ramdisk:
cd my_boot && gunzip boot.img-ramdisk.gz && mkdir ramdisk && cd ramdisk && cpio -idmv < ../boot.img-ramdisk - 修改init.rc:在文件末尾添加你的服务定义
- 重新打包ramdisk:
find . | cpio -o -H newc | gzip > ../new_ramdisk.img - 重新打包boot.img:
mkbootimg --kernel boot.img-zImage --ramdisk new_ramdisk.img --dtb boot.img-dtb --base $(cat boot.img-base) --pagesize $(cat boot.img-pagesize) --cmdline "$(cat boot.img-cmdline)" --output new_boot.img
避坑指南:我曾经在重新打包时忘了指定pagesize,结果刷进去后内核直接崩溃。pagesize必须和原boot.img一致,否则内存对齐会出问题。
3.6 常见问题与排查
我整理了几个高频翻车点:
- 内核panic:检查cmdline里的console参数,确保串口输出能看到日志
- ramdisk挂载失败:检查ramdisk格式,必须是gzip压缩的cpio
- 设备树不匹配:用
dtc -I dtb -O dts dtb.img反编译,确认硬件信息正确 - 启动卡在logo:大概率是init.rc里某个服务挂了,用adb logcat看日志
嗯,Boot分区的内容就这些。说白了,它就是内核+ramdisk+设备树的组合体。掌握了打包解包工具,你就能随心所欲地定制启动流程。
下一章咱们聊System分区,那是Android系统的核心所在。