编译内核

本页详细介绍了为 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 映像构建中

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 start 命令。以下示例命令使用 common-android14-6.1 内核清单中的 arm64 目标的内核工件。

cvd start \
    -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 上开发内核

自定义内核 build

如需为 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