本页详细介绍了为 Android 设备构建自定义内核的流程。以下说明会逐步指导您如何选择正确的源代码,构建内核,以及将结果嵌入到根据 Android 开源项目 (AOSP) 构建的系统映像中。
您可以使用 Repo 获取最新的内核源代码;通过在源代码检出的根目录下运行 build/build.sh
可构建这些内核源代码,而无需更多配置。
下载源代码和构建工具
对于最新的内核,可以使用 repo
下载源代码、工具链和构建脚本。一些内核(例如 Pixel 3 内核)需要从多个 git 仓库获取源代码,而其他内核(如通用内核)只需要一份源代码。使用 repo
方法可确保源目录设置正确。
下载相应分支的源代码:
mkdir android-kernel && cd android-kernel
repo init -u https://android.googlesource.com/kernel/manifest -b BRANCH
repo sync
如需查看可与之前的“repo init”命令搭配使用的 repo 分支 (BRANCH) 列表,请参阅内核分支及其构建系统。
如需详细了解如何为 Pixel 设备下载和编译内核,请参阅构建 Pixel 内核。
构建内核
使用 Bazel (Kleaf) 构建
Android 13 引入了使用 Bazel 构建内核的功能。
如需为 aarch64 架构构建 GKI 内核,请查看不低于 Android 13 的 Android 通用内核分支,然后运行以下命令:
tools/bazel build //common:kernel_aarch64_dist
如需创建分发版本,请运行以下命令:
tools/bazel run //common:kernel_aarch64_dist -- --dist_dir=$DIST_DIR
之后,内核二进制文件、模块和相应的映像会置于 $DIST_DIR
目录中。如果未指定 --dist_dir
,请参阅该命令的输出以了解工件的位置。如需了解详情,请参阅 AOSP 文档。
使用 build.sh(旧版)构建
对于 Android 12 或更低版本的分支,或者不使用 Kleaf 的分支:
build/build.sh
内核二进制文件、模块和相应的映像位于 out/BRANCH/dist
目录下。
为虚拟设备构建供应商模块
Android 13 引入了使用 Bazel (Kleaf) 构建内核的功能,以取代 build.sh
。
如需构建 virtual_device
的模块,请运行以下命令:
tools/bazel build //common-modules/virtual-device:virtual_device_x86_64_dist
如需创建分发版本,请运行以下命令:
tools/bazel run //common-modules/virtual-device:virtual_device_x86_64_dist -- --dist_dir=$DIST_DIR
如需详细了解如何使用 Bazel 构建 Android 内核,请参阅:Kleaf - 使用 Bazel 构建 Android 内核。
如需详细了解对各个架构的 Kleaf 支持,请参阅对设备和内核的 Kleaf 支持。
使用 build.sh(旧版)为虚拟设备构建供应商模块
在 Android 12 中,Cuttlefish 和 Goldfish 融合,因此它们共享同一个内核:virtual_device
。如需构建该内核的模块,请使用以下 build 配置:
BUILD_CONFIG=common-modules/virtual-device/build.config.virtual_device.x86_64 build/build.sh
Android 11 引入了 GKI,用于将内核拆分为由 Google 维护的内核映像和由供应商维护的模块,二者分别单独构建。
以下示例展示了内核映像配置:
BUILD_CONFIG=common/build.config.gki.x86_64 build/build.sh
以下示例展示了模块配置(Cuttlefish 和模拟器):
BUILD_CONFIG=common-modules/virtual-device/build.config.cuttlefish.x86_64 build/build.sh
运行内核
您可以通过多种方式运行以自定义方式构建的内核。下面介绍了几种适合各种开发场景的已知方法。
嵌入到 Android 映像 build 中
将 Image.lz4-dtb
复制到 AOSP 树中相应的内核二进制文件位置,然后重新构建启动映像。
或者,您也可以在使用 make bootimage
(或用于构建启动映像的任何其他 make
命令行)时定义 TARGET_PREBUILT_KERNEL
变量。所有设备均支持该变量,因为它是通过 device/common/populate-new-device.sh
进行设置的。例如:
export TARGET_PREBUILT_KERNEL=DIST_DIR/Image.lz4-dtb
使用 fastboot 刷写和启动内核
最新的设备具有引导加载程序扩展,可以简化生成和启动启动映像的过程。
要启动内核而不刷新,请运行以下命令:
adb reboot bootloader
fastboot boot Image.lz4-dtb
使用此方法时,内核实际上并未刷新,因此不会在重新启动时保留。
在 Cuttlefish 上运行内核
您可以在 Cuttlefish 设备上以所选的架构运行内核。
如需启动包含一组特定内核工件的 Cuttlefish 设备,请使用目标内核工件作为参数运行 cvd create
命令。以下示例命令使用 common-android14-6.1
内核清单中的 arm64 目标的内核工件。
cvd create \
-kernel_path=/$PATH/$TO/common-android14-6.1/out/android14-6.1/dist/Image \
-initramfs_path=/$PATH/$TO/common-android14-6.1/out/android14-6.1/dist/initramfs.img
如需了解详情,请参阅在 Cuttlefish 上开发内核。
自定义内核编译系统
如需为 Kleaf build 自定义内核 build,请参阅 Kleaf 文档。
使用 build.sh(旧版)自定义内核 build
对于 build/build.sh
,构建流程和结果可能会受环境变量的影响。它们中的大多数是可选的,并且每个内核分支都应该具有适当的默认配置。此处列出了最常用的变量。如需完整(且最新)的列表,请参阅 build/build.sh
。
环境变量 | 说明 | 示例 |
---|---|---|
BUILD_CONFIG |
用于初始化构建环境的 build 配置文件。必须相对于 Repo 根目录定义其具体位置。默认为 build.config 。必须为通用内核指定此变量。 |
BUILD_CONFIG=common/build.config.gki.aarch64 |
CC |
替换要使用的编译器。回退至 build.config 定义的默认编译器。 |
CC=clang |
DIST_DIR |
内核分发版本的基本输出目录。 | DIST_DIR=/path/to/my/dist |
OUT_DIR |
内核 build 的基本输出目录。 | OUT_DIR=/path/to/my/out |
SKIP_DEFCONFIG |
跳过 make defconfig |
SKIP_DEFCONFIG=1 |
SKIP_MRPROPER |
跳过 make mrproper |
SKIP_MRPROPER=1 |
本地构建的自定义内核配置
在 Android 14 及更高版本中,您或许可使用 defconfig fragment 自定义内核配置。请参阅关于 defconfig fragment 的 Kleaf 文档。
具有 build 配置(旧版)的本地构建的自定义内核配置
在 Android 13 及更低版本中,请参阅以下内容。
如果您需要定期切换内核配置选项(例如在开发某项功能时),或者需要设置一个用于开发的选项,可以通过维护 build 配置的本地修改或副本来实现这种灵活性。
将 POST_DEFCONFIG_CMDS 变量设为一个在常规 make defconfig
步骤完成后立即接受评估的语句。由于 build.config
文件源于构建环境,因此 build.config
中定义的函数可以作为 post-defconfig 命令的一部分进行调用。
一个常见示例是在开发期间针对 crosshatch 内核停用链接时优化 (LTO)。虽然 LTO 对已发布的内核有益,但构建时产生的开销可能巨大。添加到本地 build.config
的以下代码段将在使用 build/build.sh
时永久停用 LTO。
POST_DEFCONFIG_CMDS="check_defconfig && update_debug_config"
function update_debug_config() {
${KERNEL_DIR}/scripts/config --file ${OUT_DIR}/.config \
-d LTO \
-d LTO_CLANG \
-d CFI \
-d CFI_PERMISSIVE \
-d CFI_CLANG
(cd ${OUT_DIR} && \
make O=${OUT_DIR} $archsubarch CC=${CC} CROSS_COMPILE=${CROSS_COMPILE} olddefconfig)
}
确定内核版本
您可以通过以下两个来源确定要构建的正确版本:AOSP 树和系统映像。
AOSP 树中的内核版本
AOSP 树包含预构建的内核版本。git 日志会在提交消息中显示正确的版本:
cd $AOSP/device/VENDOR/NAME
git log --max-count=1
如果内核版本未在 git 日志中列出,请从系统映像中获取,如下所述。
系统映像中的内核版本
如需确定系统映像中使用的内核版本,请对内核文件运行以下命令:
file kernel
对于 Image.lz4-dtb
文件,请运行以下命令:
grep -a 'Linux version' Image.lz4-dtb
构建启动映像
可以使用内核构建环境构建启动映像。
使用 init_boot 为设备构建启动映像
对于具有 init_boot
分区的设备,启动映像会与内核一起构建。initramfs
映像未嵌入到启动映像中。
例如,使用 Kleaf,您或许可使用以下代码构建 GKI 启动映像:
tools/bazel run //common:kernel_aarch64_dist -- --dist_dir=$DIST_DIR
借助 build/build.sh
(旧版),您或许可使用以下代码构建 GKI 启动映像:
BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
GKI 启动映像位于 $DIST_DIR 中。
在不使用 init_boot(旧版)的情况下为设备构建启动映像
对于没有 init_boot
分区的设备来说,您需要一个 ramdisk 二进制文件,该二进制文件可以通过下载 GKI 启动映像后解压缩来获取。关联的 Android 版本中的任何 GKI 启动映像都可以使用。
tools/mkbootimg/unpack_bootimg.py --boot_img=boot-5.4-gz.img
mv $KERNEL_ROOT/out/ramdisk gki-ramdisk.lz4
目标文件夹是内核树的顶级目录(当前的工作目录)。
如果您使用 AOSP main 进行开发,则可以从 ci.android.com 上的 aosp_arm64 build 中下载 ramdisk-recovery.img
build 工件,并将其用作 ramdisk 二进制文件。
当您拥有 ramdisk 二进制文件并将其复制到内核 build 的根目录中的 gki-ramdisk.lz4
时,可以通过执行以下命令来生成启动映像:
BUILD_BOOT_IMG=1 SKIP_VENDOR_BOOT=1 KERNEL_BINARY=Image GKI_RAMDISK_PREBUILT_BINARY=gki-ramdisk.lz4 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
如果您使用的是基于 x86 的架构,请将 Image
替换为 bzImage
,将 aarch64
替换为 x86_64
:
BUILD_BOOT_IMG=1 SKIP_VENDOR_BOOT=1 KERNEL_BINARY=bzImage GKI_RAMDISK_PREBUILT_BINARY=gki-ramdisk.lz4 BUILD_CONFIG=common/build.config.gki.x86_64 build/build.sh
该文件位于工件目录 $KERNEL_ROOT/out/$KERNEL_VERSION/dist
中。
启动映像位于 out/<kernel branch>/dist/boot.img
。