4. 内核源码与构建系统基础

要真正掌握 Android GKI 通用内核镜像工程,第一步不是急着敲 make,而是先弄清楚三件事:内核源码树长什么样、用什么工具构建、用什么方式管理代码。这三者分别对应 AOSP 内核源码树、Bazel/Kleaf 构建系统,以及 repo 与 git 的协作方式。理解它们之间的分工,才能在后续编译 GKI 镜像、打补丁、切换分支时做到心中有数。

一、AOSP 内核源码树:GKI 的物理载体

AOSP 的内核源码并不是一个孤立仓库,而是由多个 git 仓库组合而成的“超级工程”。在 GKI 体系下,最核心的是 common 仓库,它承载通用内核代码,是 GKI 镜像的源码基础。围绕它还有一系列按架构和平台划分的仓库,例如 common-modules/virtual-device、kernel/configs、prebuilts 等。它们通过 repo 清单统一拉取,最终在本地形成一个可编译的源码树。

典型目录结构如下:

kernel/
├── common/                 # GKI 通用内核源码
│   ├── arch/               # 各架构相关代码
│   ├── drivers/            # 驱动子系统
│   ├── kernel/             # 内核核心
│   ├── mm/                 # 内存管理
│   └── build.config.*      # 构建配置
├── common-modules/         # 外部模块
├── kernel/configs/         # 内核配置片段
└── prebuilts/              # 预编译工具链

其中 build.config.* 文件非常关键,它定义了编译目标、工具链路径、配置片段和输出目录。GKI 的构建入口通常不是直接调用 make,而是通过 build/build.sh 或 Kleaf 的 Bazel 目标来驱动,这些脚本会读取 build.config 并组织编译流程。

二、Bazel 与 Kleaf:现代构建系统

传统 Android 内核使用 make 加 shell 脚本构建,配置分散、依赖隐式、复现困难。GKI 引入了 Bazel 作为构建引擎,并在其上封装了 Kleaf(Kernel Build Abstraction Layer)。Kleaf 不是独立工具,而是 Bazel 规则和宏的集合,把内核编译抽象成可声明、可缓存、可并行的 Bazel 目标。

Bazel 的核心优势在于:

  • 声明式构建:通过 BUILD.bazel 描述目标、依赖和输出,构建过程可追踪。
  • 增量与缓存:只重建受影响的目标,大幅缩短重复编译时间。
  • 可复现性:工具链、配置、依赖版本被显式固定,减少“在我机器上能编”的问题。
  • 并行执行:多目标并行构建,充分利用多核 CPU。

Kleaf 中常见的构建目标包括 //common:kernel_aarch64、//common:kernel_x86_64 等。执行构建时通常使用:

tools/bazel build //common:kernel_aarch64
tools/bazel build //common:kernel_aarch64_dist

其中 _dist 目标会生成可分发的镜像和符号文件,是制作 GKI 镜像的常用入口。Kleaf 还支持 --config= 参数切换调试、LTO、KASAN 等构建变体,这些配置最终会映射到内核的 defconfig 和编译选项。

提示:Kleaf 构建前会检查工具链和仓库状态,若出现 “no such target” 或 “repository not found”,优先确认 repo 是否同步完整、Bazel 版本是否匹配。

三、repo 与 git:多仓库协作管理

AOSP 内核由几十个 git 仓库组成,逐个 clone 和切换分支不现实。repo 是 Google 开发的 git 多仓库管理工具,它通过一个 manifest 清单文件描述所有仓库的地址、分支和路径,然后用一条命令完成批量同步。

常用操作如下:

repo init -u https://android.googlesource.com/kernel/manifest -b common-android14-6.1
repo sync -j8
repo start my-gki-work --all
repo status

repo init 指定 manifest 仓库和分支,repo sync 拉取所有子仓库,repo start 为所有仓库创建本地工作分支,repo status 查看各仓库改动。这样就能在统一视图下管理整个内核源码树。

git 则负责单个仓库内的版本控制。在内核开发中,常见的 git 操作包括查看提交历史、创建补丁、rebase 到新基线、cherry-pick 上游修复等。例如:

git log --oneline -10
git format-patch -1 HEAD
git rebase origin/android14-6.1
git cherry-pick <commit>

理解 repo 与 git 的分工很重要:repo 管“多仓库整体”,git 管“单仓库细节”。GKI 的版本切换、补丁合入、基线升级,往往需要两者配合完成。

四、构建流程与常见误区

一个典型的 GKI 构建流程可以概括为:初始化 repo → 同步源码 → 创建本地分支 → 修改配置或打补丁 → 使用 Kleaf/Bazel 构建 → 获取 dist 输出 → 验证镜像。这个流程中,任何一步出错都会导致构建失败或镜像不可用。

阶段关键操作常见问题
源码同步repo init / repo sync网络中断、manifest 分支不匹配
分支管理repo start / git checkout本地改动未提交导致切换失败
配置修改编辑 build.config / defconfig配置片段冲突、选项未生效
构建tools/bazel build工具链缺失、Bazel 缓存损坏
输出验证检查 dist 目录、boot.img镜像未签名、符号缺失
误区一:把 Kleaf 当成 make 的替代品,直接手写 make 命令。GKI 的构建依赖 Bazel 规则和 build.config,绕过 Kleaf 容易导致配置不一致。误区二:在 repo 同步不完整时就开始构建,缺少 prebuilts 或 common-modules 会直接报错。误区三:随意切换分支而不清理 Bazel 缓存,旧缓存可能污染新构建结果。

复盘时建议养成三个习惯:每次构建前用 repo status 确认工作区干净;构建失败先看 Bazel 日志中的第一个错误,而不是最后一个;镜像生成后核对 dist 目录中的文件清单和符号表,确保与目标平台匹配。

GKI 内核源码与构建系统关系图 repo 清单层(多仓库统一管理) manifest 清单 repo init / sync repo start / status 统一工作区视图 AOSP 内核源码树(git 单仓库管理) common/ GKI 通用内核 common-modules/ 外部模块 kernel/configs/ 配置片段 prebuilts/ 工具链与预编译 Bazel + Kleaf 构建层 BUILD.bazel 声明式目标 build.config 编译配置 tools/bazel build 执行构建 dist 输出 镜像与符号 repo 管多仓库整体,git 管单仓库细节,Kleaf 管构建目标与输出

把这三层关系理清后,后续无论是编译 GKI 镜像、合入厂商补丁,还是升级内核基线,都能快速定位问题出在源码同步、分支管理还是构建配置上。这是进入 GKI 工程实践的基础,也是避免“编译玄学”的关键。