本文介绍了通用内核映像 (GKI) 的版本控制方案。通用内核映像 (GKI) 具有一个称为内核版本的唯一标识符。内核版本由内核模块接口 (KMI) 版本和子级组成。内核版本特定于所发布的映像,而 KMI 版本表示构建版本所基于的接口。KMI 版本可以支持多个内核版本。 一个内核版本仅与一个 KMI 版本相关联。在极少数情况下,假如必须更改内核模块接口,KMI 代系会进行迭代,以反映 KMI 版本中的更改。
术语总结
下表总结了本文以及 GKI 更新中使用的重要术语。
名称 | 符号 | 示例 | 说明 |
---|---|---|---|
内核版本 | w.x.y-zzz-k-suffix | 5.4.42-android12-0-foo | GKI 版本的唯一标识符。这是由 uname 返回的值。 |
KMI 版本 | w.x-zzz-k | 5.4-android12-0 | 用于描述 GKI 和可动态加载的内核模块 (DLKM) 之间的内核模块接口 (KMI)。 |
子级 | y | 42 | 用于描述同一 KMI 版本中内核版本的发布顺序。 |
下表列出了其他相关术语,供您参考。
名称 | 符号 | 示例 | 说明 |
---|---|---|---|
w.x.y | w.x.y | 5.4.42 |
如需了解详情,请参阅 Linux 内核 Makefile(搜索“KERNELRELEASE”)。 本文档中使用了“w.x.y”。w.x.y此名称通常也称为“三段式版本号”。VINTF 中所用的术语“内核版本”可能会与其他术语(尤其是“w”)相混淆。 此变量在 libkver 中称为“kernel_version_tuple”。kernel_version_tuple 不得通过任何更新(包括 OTA 或 Mainline)减小此元组。 |
内核分支 | zzz-w.x | android12-5.4 | 这一术语在通用内核分支类型中使用。 |
版本 | w | 5 | 本文档中未使用此术语。此变量在 libkver 中称为“version”。 |
补丁程序级别 | x | 4 | 本文档中未使用此术语。此变量在 libkver 中称为“patch_level”。patch_level |
Android 版本 | zzz | android12 |
这是与内核关联的 Android(dessert)版本号。
比较 不得通过任何更新(包括 OTA 或 Mainline)减小 Android 版本号。 |
KMI 代系 | k | 0 |
这是为处理不太可能发生的事件而增加的额外编号。如果某个安全 bug 修复程序需要对同一 Android 版本中的 KMI 进行更改,系统会提高 KMI 代系。 KMI 代系编号以 0 开头。 |
版本控制设计
内核版本
定义
搭载 GKI 的设备的内核版本定义如下:
KernelRelease :=
Version.PatchLevel.SubLevel-AndroidRelease-KmiGeneration-suffix
w .x .y -zzz -k -something
如需了解详情,请参阅确定设备的内核版本。
以下是一个内核版本示例。
5.4.42-android12-0-00544-ged21d463f856
说明
内核版本是 GKI 版本的唯一 ID。如果两个 GKI 二进制文件具有相同的内核版本,那它们必须在字节上完全相同。
内核版本由一个 KMI 版本、一个子级和一个后缀组成。在本文档中,KMI 代系之后的后缀将被忽略。
KMI 版本
定义
KMI 版本的定义如下:
KmiVersion :=
Version.PatchLevel-AndroidRelease-KmiGeneration
w .x -zzz -k
请注意,子级 y
不是 KMI 版本的一部分。对于内核版本中的示例,KMI 版本为:
5.4-android12-0
说明
KMI 版本用于描述 GKI 和可动态加载的内核模块 (DLKM) 之间的内核模块接口 (KMI)。
如果两个内核版本具有相同的 KMI 版本,则它们会实现相同的内核模块接口。与其中一个兼容的 DLKM 也会与另一个兼容。
不得通过任何 OTA 更新降低 KMI 版本。
子级
子级 y
用于描述同一 KMI 版本中内核版本的发布顺序。
对于具有相同 KMI 版本但子级分别为 Y1 和 Y2 的两个内核版本:
- 如果 Y1 小于或等于 Y2,那么搭载 Y1 的设备可以接收 Y2 更新。
- 如果 Y1 大于 Y2,那么搭载 Y1 的设备无法更新到 Y2。
也就是说,如果 KMI 版本未更改,就无法通过任何 OTA 更新降低子级。
确定设备的内核版本
通过以下代码段执行 uname -r
或 uname(2)
,即可找到完整的内核版本:
std::string get_kernel_release() {
struct utsname buf;
return uname(&buf) == 0 ? buf.release : "";
}
示例输出如下:
5.4.42-android12-0-00544-ged21d463f856
在本文档中,系统在提取内核信息时会忽略 KMI 代系之后的任何内容。更正式地讲,uname -r
的输出将使用以下正则表达式进行解析(假设 zzz 始终以“android”开头):
^(?P<w>\d+)[.](?P<x>\d+)[.](?P<y>\d+)-(?P<z>android\d+)-(?P<k>\d+).*$
忽略的信息可以包括 ci.android.com build 号、基准内核之上的补丁数量,以及 Git 提交的 SHA 哈希值。
libkver
libkver 库提供了一个用于解析内核版本或 KMI 版本字符串的 C++ 接口。 如需查看 libkver 公开的 API 列表,请参阅 packages/modules/Gki/libkver/include/kver
。
VINTF 检查
对于 Android 11 或更低版本,KMI 版本的 Android 版本部分由设备制造商在设备清单中手动指定。如需了解详情,请参阅 VINTF 内核匹配规则。
从 Android S 开始,可以在构建时从内核提取 KMI 版本的 Android 版本部分并将其注入设备清单中。
由于内核配置要求通常不会更改,因此无需在兼容性矩阵中对 k
进行编码。不过,万一需要更改内核配置要求,请确保以下几点:
- 从兼容性矩阵中移除相应要求。
- 添加额外的 VTS 测试,用于检查 KMI 代系方面的新要求。
OTA 元数据中的启动映像版本
即使启动映像通过 OTA 执行更新,也必须以 OTA 载荷格式 payload.bin
进行封装。OTA 载荷会对每个分区的 version
字段进行编码。当 update_engine
处理 OTA 载荷时,它会比较此字段,确保相应分区不会被降级。
为避免混淆,OTA 元数据中 boot 分区的 version
字段称为 boot image version
。
由于 ramdisk 始终从头开始构建,因此,使用 ramdisk 时间戳就可以充分描述整个启动映像。无需将内核版本编码到启动映像版本,除非您将来要将旧启动映像拼接到新的内核二进制文件中。
在 OTA 更新之前,OTA 客户端会以与任何其他分区相同的方式检查启动映像版本。