作为一名小白,要打开区区两个内核配置相关的编译选项,成功编译,打开并进入模拟器,看起来是一件事,其实是三件事,每一件事都花费了不少时间,这篇文章尝试总结一些方法论。
打开内核相关的编译选项
要打开内核相关的编译选项,我们首先得知道它究竟使用到了哪一个,因为整个Android代码里面,为各种架构,各种版本设计的编译选项一抓一大把,改哪里,是第一个问题。
Android内核代码使用了bazel这个编译工具进行编译,我们先进入到根目录的BUILD.bazel:
//common/BUILD.bazel
21:load("//build/kernel/kleaf:common_kernels.bzl", "define_common_kernels", "define_db845c")
这里加载了一个common_kernels.bzl文件
//build/kernel/kleaf/common_kernels.bzl:49
_ARCH_CONFIGS = {
"kernel_aarch64": {
"arch": "arm64",
"build_config": "build.config.gki.aarch64",
"outs": aarch64_outs,
},
"kernel_aarch64_interceptor": {
"arch": "arm64",
"build_config": "build.config.gki.aarch64",
"outs": aarch64_outs,
"enable_interceptor": True,
},
"kernel_aarch64_debug": {
"arch": "arm64",
"build_config": "build.config.gki-debug.aarch64",
"outs": aarch64_outs,
},
"kernel_x86_64": {
"arch": "x86_64",
"build_config": "build.config.gki.x86_64",
"outs": x86_64_outs,
},
"kernel_x86_64_debug": {
"arch": "x86_64",
"build_config": "build.config.gki-debug.x86_64",
"outs": x86_64_outs,
},
}
可以发现,kernel_x86_64使用的build_config是
"build.config.gki.x86_64"
找到这个文件:
//common/build.config.gki.x86_64
. ${ROOT_DIR}/${KERNEL_DIR}/build.config.common
. ${ROOT_DIR}/${KERNEL_DIR}/build.config.x86_64
. ${ROOT_DIR}/${KERNEL_DIR}/build.config.gki
BUILD_SYSTEM_DLKM=1
MODULES_LIST=${ROOT_DIR}/${KERNEL_DIR}/android/gki_system_dlkm_modules
BUILD_GKI_ARTIFACTS=1
BUILD_GKI_BOOT_IMG_SIZE=67108864
实际上它由三个部分组成。其中build.config.gki中:
DEFCONFIG=gki_defconfig
POST_DEFCONFIG_CMDS="check_defconfig"
if [ -n "${GKI_BUILD_CONFIG_FRAGMENT}" ]; then
source ${GKI_BUILD_CONFIG_FRAGMENT}
fi
所以我们知道了DEFCONFIG使用的是gki_defconfig。找到对应文件:
common/arch/x86/configs/gki_defconfig
里面全部是CONFIG的一些定义
在文件末尾添加上(或者修改已经存在的)我们想要的编译配置:
CONFIG_BLK_DEV_ZONED=y
CONFIG_BLK_DEV_NULL_BLK=y
好,到这里,完成了第一步。修改配置。
编译内核
打开了两个编译选项后,发现并编不过,很匪夷所思。
经过学长的寻找,最后发现,要在build.config.gki文件中,注释掉deconfig的检查的那一条语句:
//common/build.config.gki
DEFCONFIG=gki_defconfig
# POST_DEFCONFIG_CMDS="check_defconfig" 注释此行
if [ -n "${GKI_BUILD_CONFIG_FRAGMENT}" ]; then
source ${GKI_BUILD_CONFIG_FRAGMENT}
fi
取消检查后就能编过了。
运行模拟器
编译过后以为可以运行起来了,就直接尝试,发现并不能:
会报disagree错误,这个问题比较经典,因为之前切换内核的时候也遇到了,怀疑是版本错误,最后找到了解决方案:
应该是版本检查时CRC循环冗余检验不过,由于我们这些工作都还是在测试,考虑跳过这个检查,同样修改之前的内核配置文件,将CONFIG_MODVERSIONS这个选项注释掉不打开,确实解决了这个问题,然后出现下一个问题:
发现有模块还是无法成功加载,我发现,有一个version magic 'xx' should be 'xx'报错,经过查询,发现这也是一个版本校验的情况,内核与模块之间的版本不同不允许加载。
由于内核模块是通过预编译的方式,放置在AOSP代码中,内核版本与模块版本确实可能产生冲突,考虑更新模块版本,首先找到模块放置地址:
对于我的架构和Android系统版本,预编译的模块存放于:
AOSP/kernel/prebuilts/common-modules/virtual-device/5.15/x86-64
使用命令:
tools/bazel build //common-modules/virtual-device:virtual_device_x86_64_dist
编译内核模块。编译完成后在目录:
kernel_source/bazel-bin/common-modules/virtual-device/virtual_device_x86_64
把这些内核模块全部替换掉原来的内核模块,重新编译AOSP后,以上错误解决。
但是出现新错误,模拟器启动过程不会卡死,但是就是起不来。
经过逐行对比日志,发现surfaceflinger服务起不来,这个服务是Android里面图形化相关的服务,由于我们只修改了内核模块,会不会是某个模块被漏掉了?于是重新比较了一下,发现确实有若干模块并不在更新后的ko列表中。比如virtio-gpu.ko模块,很有可能是这个原因。
但是这个模块在哪呢?找到和模块编译相关的bazel文件:
common-modules/virtual-device/BUILD.bazel
搜索 virtio-gpu:
_virt_common_ext_modules = [
"goldfish_drivers/goldfish_address_space.ko",
"goldfish_drivers/goldfish_pipe.ko",
"goldfish_drivers/goldfish_sync.ko",
"virtio_gpu/virtio-gpu.ko",
]
发现它被定义为一种ext_modules,找到我们执行的命令对应条目:
kernel_build(
name = "virtual_device_x86_64",
srcs = [":virtual_device_x86_64_common_sources"],
outs = [],
base_kernel = "//common:kernel_x86_64",
build_config = "build.config.virtual_device.x86_64",
module_outs = _virt_common_modules + [
# keep sorted
"test_meminit.ko",
"test_stackinit.ko",
],
)
发现它确实没有编译ext_modules,那ext_modules该怎么编呢,我在这个文件中又发现了一个编译选项:
kernel_module(
name = "virtual_device_x86_64_external_modules",
srcs = [":virtual_device_x86_64_common_sources"],
outs = _virt_common_ext_modules,
kernel_build = ":virtual_device_x86_64",
)
所以,可以通过执行:
tools/bazel build //common-modules/virtual-device:virtual_device_x86_64_external_modules
来编译external_modules。
于是执行,编译完毕后,将
bazel-bin/common-modules/virtual-device/virtual_device_x86_64_external_modules
目录下的.ko文件移动到AOSP目录对应位置,然后重新编译AOSP。
终于成功启动模拟器: