【全栈第13课】内核编译 + menuconfig + GKI(阶段三开篇)
Android 全栈工程师进阶教程 第13课。内核编译 + menuconfig + GKI(阶段三开篇)。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。
> 本文是《Android 全栈工程师进阶教程》系列第 13 课。完整 26 课见 GitHub 仓库。
【全栈第13课】内核编译 + menuconfig + GKI(阶段三开篇)
0. 这节课你将搞懂什么
现代 Android 内核(GKI)是怎么组织和编译的,menuconfig 的三态,以及你写的驱动(L14-L20)将来怎么编进系统。
学完你能回答:为什么 某真实机型 主树里 kernel 只有 prebuilt 没源码?GKI 是什么?我写的 .ko 驱动放哪、开机怎么加载?menuconfig 里 <*>/<M>/< > 啥区别?
1. 背景:Android 内核的碎片化与 GKI
Android 内核就是 Linux 内核 + Android 的一些增量(binder 驱动、lmkd 等,见 L19)。
历史问题:每个厂商各自魔改内核,导致:
- Google 发了内核安全补丁,要等几十个厂商各自合入,慢
- 同一个 Android 版本,内核五花八门,碎片化严重
Android 11+ 引入 GKI(Generic Kernel Image,通用内核镜像):
- GKI 核心:Google 编译、所有设备统一的内核镜像(boot.img 里的 kernel)
- vendor modules:厂商的驱动编成独立的
.ko模块,放 vendor 分区,开机加载 - 好处:Google 直接推内核更新到 GKI,不用等厂商;厂商驱动和内核解耦
这就是为什么 某真实机型 主树里 kernel 只有 prebuilt(预编译的 GKI Image),没有完整源码树——内核在独立的 kernel 仓编。
2. 全局图:GKI 架构
boot.img
└─ GKI 内核 Image(Google 编译,统一)
│ 提供稳定的 KMI(Kernel Module Interface)
▼ 加载
vendor_boot.img / vendor 分区
└─ vendor modules(*.ko,厂商编)
├─ 触控驱动.ko / 传感器驱动.ko / ... ← L14-L20 你写的驱动
└─ modules.load(列出开机要加载哪些 .ko)
你写的驱动 = 一个 .ko,放进 vendor,开机由 modules.load 加载。GKI 核心不动。
3. 某真实机型 真实印证
# 某真实机型 主树 kernel/ 只有这些(没源码):
kernel/prebuilts/5.10/arm64/ ← 多个版本的 GKI 预编译 Image
kernel/prebuilts/5.15/arm64/
kernel/prebuilts/6.1/arm64/
kernel/prebuilts/6.12/arm64/
kernel/prebuilts/approved-ogki-builds.xml ← GKI 版本管理(OGKI=OEM GKI)
kernel/prebuilts/kernel-lifetimes.xml
有多个版本(5.10~6.12),按机型选。新版本 要求新芯片用 kernel ≥ 6.18(见 新版本 总结)。
4. 前置准备
- 本课偏概念(因为主树编不了 kernel)。
- 真要编内核,需拉独立 kernel 仓(下面给方法)。
- 为 L14-L20 写驱动做认知铺垫。
5. 动手:理解内核编译流程
5.1 内核源码树结构(拉下来后长这样)
kernel/
├── arch/arm64/ 架构相关(boot/dts 设备树在这,L15)
├── drivers/ 所有驱动(你写的驱动归这或编成外部 .ko)
├── kernel/ 核心(调度/进程)
├── mm/ 内存管理
├── fs/ 文件系统
├── include/ 头文件(写 .ko 要 include 这里的)
├── Documentation/ 内核文档
└── Makefile / Kconfig 构建系统
5.2 配置:defconfig + menuconfig
make ARCH=arm64 gki_defconfig # GKI 用 gki_defconfig(精简的默认配置)
make ARCH=arm64 menuconfig # 图形界面调配置,结果存进 .config
menuconfig 三态(核心,写驱动必懂):
| 标记 | 含义 | 用途 |
|---|---|---|
<*> | 编进内核(built-in) | 开机就在内核里,核心功能 |
<M> | 编成模块(.ko) | 运行时 insmod 加载,vendor 驱动多用这个 |
< > | 不编 | 不要这个功能 |
你的驱动一般选 <M>,编出 .ko,装进 vendor,GKI 核心不动。
5.3 编译(现代用 bazel/kleaf)
# 现代 GKI 用 bazel(kleaf):
tools/bazel build //common:kernel_aarch64
# 或老的 build.sh:
BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
# 编外部模块(你的驱动):
make -C <kernel_src> M=$(pwd) modules # 出你的 .ko(L14 会用)
5.4 编译产物
| 产物 | 是什么 |
|---|---|
Image / Image.gz | 内核镜像(进 boot.img) |
*.dtb | 设备树二进制(L15,描述硬件) |
*.ko | 内核模块(你的驱动,进 vendor) |
6. 看底层:vendor 驱动开机怎么加载
GKI 核心不认识你的驱动。你的 .ko 进 vendor 分区后:
modules.load(或modules.load.recovery等)文件列出开机要加载的 .ko 顺序- 开机时
init/vendor_modprobe按这个列表insmod你的 .ko - .ko 的
module_init函数被调用(L14 会写)
所以你写完驱动 .ko,还要把它加进 modules.load,否则开机不加载。
7. 编译 & 运行(验证状态如实)
# 看设备当前内核版本
adb shell uname -r
# 看加载了哪些模块
adb shell lsmod
# 看 GKI 信息
adb shell cat /proc/version
> 验证状态(诚实):某真实机型 主树是 GKI prebuilt 无源码,编不了 .ko(本课在标准 Linux 环境验证驱动,见 L14 及验证报告)。本课为概念课,讲清 GKI 机制和编译方法。
8. 踩坑提醒
- 主树找不到 kernel 源码很正常:GKI 架构下内核在独立仓,主树只放 prebuilt。
- KMI 兼容:vendor 模块依赖的内核符号必须在 GKI 的 KMI 白名单里,用了白名单外的符号,GKI 升级后你的 .ko 加载失败。
- modules.load 漏加:.ko 编出来但没加进 modules.load,开机不加载,驱动等于没装。
- 内核版本要匹配:.ko 是给特定内核版本编的,版本不匹配
insmod报version magic错。 - 新版本 内核要求:新芯片要 6.18+,旧 defconfig 可能不适用。
9. 常见问题分析与定位(内核/模块实战)
9.1 常见问题清单
insmod报version magic mismatch.ko加载报Unknown symbol- 驱动编出来了但开机没加载
- GKI 升级后 vendor 模块失效
- 内核编译失败
9.2 分析与定位
① version magic mismatch
- 现象:
insmod xxx.ko报version magic 'x.y.z' should be 'a.b.c'。 - 原因:.ko 是给别的内核版本编的,和当前设备内核不匹配。
- 定位:
adb shell uname -r看设备内核版本;modinfo xxx.ko看 .ko 编译时的内核版本。对齐重编。
② Unknown symbol
- 现象:
insmod报Unknown symbol xxx (err -2)。 - 原因:.ko 用了内核没导出的符号,或依赖的别的 .ko 没先加载,或用了 KMI 白名单外符号。
- 定位:
dmesg | grep "Unknown symbol"看缺哪个符号;检查依赖的模块加载顺序(modules.load 里顺序)。
③ 驱动没加载
- 现象:
lsmod里没有你的模块。 - 定位:
adb shell cat /vendor/lib/modules/modules.load | grep yourmod(在列表里吗);dmesg | grep yourmod(加载时报错了吗)。 - 修复:加进 modules.load;若加载报错按 ①② 查。
④ GKI 升级后模块失效
- 原因:用了 KMI 白名单外的内核符号,GKI 内核改了那个符号。
- 定位:
dmesg看 Unknown symbol;对比 KMI 白名单(abi_symbollist)。 - 修复:只用 KMI 白名单内符号,或申请加白名单。
⑤ 内核编译失败
- 定位:看 make 报错;常见 defconfig 不对、工具链版本、缺依赖。
9.3 定位决策树
内核/模块出问题
├─ version magic → uname -r vs modinfo → 对齐内核版本重编
├─ Unknown symbol → dmesg 看缺啥符号 → 依赖模块顺序 / KMI 白名单
├─ 没加载 → cat modules.load 有没有 + dmesg 加载报错 → 加进列表/按①②查
├─ GKI 升级失效 → dmesg Unknown symbol → 用 KMI 白名单内符号
└─ 编译失败 → make 报错 → defconfig/工具链/依赖
10. 小结
- 现代 Android = GKI:Google 统一的内核核心 + 厂商的 .ko 模块。
- 某真实机型 主树只有 prebuilt 内核,真编内核要拉独立 kernel 仓。
- menuconfig 三态:
<*>编进内核 /<M>编成 .ko(vendor 驱动多用)/< >不编。 - 你的驱动 = .ko,进 vendor,加进 modules.load 开机加载。
- 排查口诀:version magic 对齐版本;Unknown symbol 查符号+顺序+KMI;没加载查 modules.load。
下节预告
L14:字符设备驱动 /dev/hello —— 写你的第一个真实内核驱动(file_operations/open/read/write/ioctl),编成 .ko(本课已真编译验证)。