3、Boot分区:内核与dtb打包、GKI 2.0对Boot分区的影响、ramdisk分离策略
Boot分区,说白了就是系统启动的第一道关卡。我刚开始接触Android底层时,总觉得这玩意儿不就是个内核加个文件系统嘛。后来踩过几次坑才明白,这里面的门道比想象中深得多。
3.1 内核与dtb打包:传统做法
先说说最基础的。传统的Boot分区里,主要塞了三样东西:内核(zImage/Image.gz)、设备树(dtb/dtbo)、以及ramdisk。这三者怎么组合,各家厂商都有自己的玩法。
我记得早期做MTK平台时,它们的打包方式就很特别。内核和dtb是分开的,启动时由bootloader去拼接。而高通则喜欢用mkbootimg工具,把内核、dtb、ramdisk一股脑打包成一个boot.img。
这里有个关键点:dtb必须和内核版本严格匹配。我在项目中遇到过,某次升级内核后忘了更新dtb,结果设备树里引用的GPIO号对不上,屏幕直接黑掉。嗯,那次排查花了我整整两天。
传统打包命令示例:
# 使用mkbootimg打包
mkbootimg --kernel Image.gz \
--dtb dtb.img \
--ramdisk ramdisk.img \
--cmdline "console=ttyMSM0,115200" \
--base 0x80000000 \
--pagesize 4096 \
-o boot.img
你想想看,这种打包方式有什么问题?每次内核升级,整个boot.img都得重新生成。厂商想改个设备树配置,也得重新刷boot分区。说白了,耦合太紧了。
3.2 GKI 2.0对Boot分区的影响
GKI 2.0(Generic Kernel Image)的出现,彻底改变了这个局面。Google搞这套东西的初衷很简单:让内核和厂商驱动解耦。
我个人习惯把GKI 2.0理解为「标准化内核」。以前每个厂商都有自己的内核分支,Google想统一升级内核?门儿都没有。现在好了,GKI 2.0规定:内核由Google维护,厂商只提供内核模块。
这对Boot分区的影响是巨大的:
- boot.img变小了:以前boot分区里塞满了厂商定制的东西,现在只剩Google的标准内核和通用ramdisk
- dtb被移到了vendor_boot分区:设备树不再和内核打包在一起,而是单独放在vendor_boot里
- 内核模块独立加载:厂商的驱动以.ko文件形式存在,启动时由init进程动态加载
我的经验:GKI 2.0刚推出来时,很多厂商不习惯。我参与的一个项目,第一次适配GKI 2.0时,发现原来的dtb引用方式全变了。以前直接在内核里写死的设备树节点,现在得通过overlay机制动态叠加。说白了,就是要把厂商的硬件差异和内核彻底隔离开。
为什么会这样?因为Google想让Android系统像Windows一样,内核可以独立升级。你想想看,Windows的补丁更新从来不需要重装驱动,对吧?GKI 2.0就是要达到这个效果。
3.3 ramdisk分离策略
ramdisk的分离,是GKI 2.0带来的另一个重要变化。以前ramdisk和内核打包在一起,现在被拆成了两部分:
| 分区 | 内容 | 说明 |
|---|---|---|
| boot | 内核 + 通用ramdisk | Google维护,包含init、adbd等基础组件 |
| vendor_boot | 厂商ramdisk + dtb/dtbo | 厂商维护,包含硬件初始化脚本、设备树 |
| init_boot | 恢复模式ramdisk | 用于OTA升级和系统恢复 |
这种分离策略的好处很明显:
- 升级更灵活:Google可以单独更新boot分区里的内核,不影响厂商的vendor_boot
- 分区更小:每个分区只放自己该放的东西,刷机时不用全量更新
- 安全性提升:内核和厂商代码的边界更清晰,攻击面变小了
避坑指南:我曾经在适配GKI 2.0时犯过一个低级错误。厂商的ramdisk里有个init.rc脚本,里面引用了/sys/class/xxx节点。结果GKI 2.0的内核把sysfs路径改了,导致脚本执行失败。排查了半天才发现,原来是内核版本升级后,驱动注册的sysfs路径变了。所以,厂商ramdisk里的路径引用一定要和内核版本对齐。
嗯,这里还要提一下ramdisk的加载顺序。bootloader会先加载boot分区里的通用ramdisk,然后加载vendor_boot里的厂商ramdisk。两个ramdisk会在内存中合并,形成一个完整的根文件系统。这个过程有点像搭积木,Google搭基础框架,厂商往上加自己的模块。
我个人觉得,ramdisk分离策略是GKI 2.0最精妙的设计之一。它既保证了内核的通用性,又保留了厂商的定制空间。你想想看,如果没有这个分离,每次Google更新内核,厂商都得跟着改ramdisk,那工作量得多大?
最后说一句,Boot分区的设计,本质上是在「通用性」和「定制化」之间找平衡。GKI 2.0把这个平衡点往前推了一大步,但代价是分区结构变得更复杂了。作为底层工程师,我们得理解这些变化背后的逻辑,才能在遇到问题时快速定位。