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把这个平衡点往前推了一大步,但代价是分区结构变得更复杂了。作为底层工程师,我们得理解这些变化背后的逻辑,才能在遇到问题时快速定位。