[Android]基于AOSP源码为Pixel3编译boot.img(下)
[Android]基于AOSP源码为Pixel3编译boot.img(下)
前言
本系列主要是要实现将Android Kernel源码并入AOSP源码,使后者的编译框架可以直接从源码编译boot.img这一目标,而在上一节我们已经基本完成了编译框架的整合;
本篇会讨论一些基于上一节的改进、优化。同时,会讨论下CAF(CodeAuroraForum, 现更改为:CodeLinaro) 中SDM845(Pixel3 SoC) + Android 9.0的基线,以及LineageOS 16(Android 9.0)上Pixel3(blueline)对应基线,两者对从源码编译boot.img
这一目标,在实现上有何差异,以及各自的优劣如何;
环境
台式机
Ubuntu16.04
AOSP版本
9.0(android-9.0.0_r46)
内核版本
4.9(android-msm-crosshatch-4.9-pie-qpr2)
Pixel型号
Pixel3 (4GB + 64GB)
正文
编译规则改进
代码结构优化
由于接下来我们会调整Android kernel编译产物存放目录,因此之前硬编码写的kernel/out
目录部分,建议提取一个宏来控制:
# BoardConfig.mk或等效路径下添加:
KERNEL_OUT := kernel/out
然后修改上一篇中定义kernel伪目标的kernel.mk
文件中(或等效目录):
# 对使用kernel/out目录的部分进行替换
$(PRODUCT_OUT)/kernel: $(SOONG_ZIP)
@echo "Making kernel"
cd kernel && bash build/build.sh
cp $(KERNEL_OUT)/dist/*.ko $(TARGET_OUT_VENDOR)/lib/modules/
cp $(KERNEL_OUT)/dist/dtbo.img $(PRODUCT_OUT)/dtbo.img
cp $(KERNEL_OUT)/dist/Image.lz4-dtb $(PRODUCT_OUT)/kernel
.PHONY: kernel
kernel: $(PRODUCT_OUT)/kernel
# 这里也修改以下:kernelclean删除目录时,细化到每个子目录,而不是整个$(KERNEL_OUT)直接删除
.PHONY: kernelclean
kernelclean:
if [[ ! -z `find $(TARGET_OUT_VENDOR)/lib/modules -name "*.ko"` ]]; then rm $(TARGET_OUT_VENDOR)/lib/modules/*.ko; fi
if [ -f $(PRODUCT_OUT)/dtbo.img ]; then rm $(PRODUCT_OUT)/dtbo.img; fi
if [ -f $(PRODUCT_OUT)/kernel ]; then rm $(PRODUCT_OUT)/kernel; fi
if [ -d $(KERNEL_OUT)/dist ]; then rm -rf $(KERNEL_OUT)/dist; fi
if [ -d $(KERNEL_OUT)/kernel_uapi_headers ]; then rm -rf $(KERNEL_OUT)/kernel_uapi_headers; fi
if [ -d $(KERNEL_OUT)/private ]; then rm -rf $(KERNEL_OUT)/private; fi
if [ -d $(KERNEL_OUT)/staging ]; then rm -rf $(KERNEL_OUT)/staging; fi
@echo "kernelclean done"
应对问题
-
问题:vendor.img打包早于kernel编译完成,导致kernel modules没有打包进vendor.img;
方案:
思路很简单,为vendor.img打包规则添加kernel modules的依赖;
这里提供一种实现方向(非唯一,可自行评估、实现):# BoardConfig.mk或等效路径下添加(注意需要在上述KERNEL_OUT定义后添加): BOARD_VENDOR_KERNEL_MODULES += \ $(KERNEL_OUT)/dist/ftm5.ko \ $(KERNEL_OUT)/dist/sec_touch.ko \ $(KERNEL_OUT)/dist/snd-soc-sdm845.ko \ $(KERNEL_OUT)/dist/snd-soc-wcd934x.ko \ $(KERNEL_OUT)/dist/snd-soc-wcd-spi.ko \ $(KERNEL_OUT)/dist/wcd-dsp-glink.ko \ $(KERNEL_OUT)/dist/pinctrl-wcd.ko \ $(KERNEL_OUT)/dist/snd-soc-cs35l36.ko \ $(KERNEL_OUT)/dist/snd-soc-sdm845-max98927.ko \ $(KERNEL_OUT)/dist/snd-soc-wcd9xxx.ko \ $(KERNEL_OUT)/dist/wcd-core.ko \ $(KERNEL_OUT)/dist/wlan.ko
然后修改上一篇中定义kernel伪目标的
kernel.mk
文件中(或等效目录):#逐一定义对kernel的依赖,并执行拷贝文件的操作 define copy-one-kernel-module-file $(1): $(PRODUCT_OUT)/kernel @echo Copying module:$(1) find $(KERNEL_OUT)/staging -type f -name $$(notdir $(1)) | xargs -I {} cp -f {} $(1) endef #调用上方定义的函数 $(foreach target,$(BOARD_VENDOR_KERNEL_MODULES),\ $(eval $(call copy-one-kernel-module-file,$(target)))) # 去除重复的cp命令 $(PRODUCT_OUT)/kernel: $(SOONG_ZIP) @echo "Making kernel" cd kernel && bash build/build.sh cp $(KERNEL_OUT)/dist/dtbo.img $(PRODUCT_OUT)/dtbo.img cp $(KERNEL_OUT)/dist/Image.lz4-dtb $(PRODUCT_OUT)/kernel
-
问题:Android Kernel编译产物在kernel/out下,而其余源码编译产物在out下,管理不方便;
方案:
基础版:
参考这一篇可以实现目录统一;但是需要注意,上面提到的KERNEL_OUT
也需要同步修改:# 一劳永逸 KERNEL_OUT := $(OUT_DIR)
进阶版:
将编译kernel
时的bash build/build.sh
转义合入kernel.mk
然后废弃kernel/build
目录下的规则;
具体来看步骤如下:-
拆解
kernel/build/build.sh
通过梳理,可以得到整个编译阶段包含如下几个步骤:
- 配置环境变量
- 编译defconfig——生成
.config
- 编译linux kernel——生成
vmlinux/dtbo.img/Image.lz4-dtb
等产物; - 编译modules——生成ko文件;
- 产物拷贝到特定目录下(当前为
kernel/out/dist
);——这一步其实是不需要的,因为我们可以用Makefile
来拷贝我们需要的产物;
-
转录为
Makefile
语法- 配置环境变量
#指定统一的绝对输出路径 KERNEL_OUT := $(PWD)/$(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ #指定内核源码存放的绝对路径 KERNEL_DIR := $(PWD)/kernel/private/msm-google #需要编译的外部模块的绝对路径 EXT_MODULES := $(PWD)/kernel/private/msm-google-modules/wlan/qcacld-3.0 #编译参考的defconfig文件名 DEFCONFIG := b1c1_defconfig #编译参数指定为clang CC_ARG := "CC=clang" #环境变量PATH添加 KERNEL_BUILD_PATH := $(strip $(PWD)/prebuilts/clang/host/linux-x86/clang-4393122/bin) KERNEL_BUILD_PATH := $(strip $(KERNEL_BUILD_PATH):$(PWD)/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin) KERNEL_BUILD_PATH := $(strip $(KERNEL_BUILD_PATH):$(PWD)/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin) #将需要的变量、参数,统一添加到KERNEL_BUILD_ENV中 KERNEL_BUILD_ENV := \ PATH=$(KERNEL_BUILD_PATH):$$PATH \ OUT_DIR=$(KERNEL_OUT) \ ANDROID_SOURCE_ROOT=$(PWD) \ ROOT_DIR=$(PWD)/kernel \ KERNEL_DIR=$(KERNEL_DIR) \ UNSTRIPPED_MODULES="wlan.ko" \ ARCH=arm64 \ CROSS_COMPILE=aarch64-linux-android- \ CROSS_COMPILE_ARM32=arm-linux-androideabi- \ LD_LIBRARY_PATH=$(PWD)/prebuilts/clang/host/linux-x86/clang-4393122/lib64:$(LD_LIBRARY_PATH) \ IN_KERNEL_MODULES=1 \ STOP_SHIP_TRACEPRINTK=1 \ CLANG_TRIPLE=aarch64-linux-gnu-
- 编译defconfig
build-defconfig: echo "========================================================" echo " Setting up for build" mkdir -p $(KERNEL_OUT) @$(MAKE) -C ${KERNEL_DIR} O=$(KERNEL_OUT) ${DEFCONFIG} $(KERNEL_BUILD_ENV)
- 编译linux kernel
build-kernel: build-defconfig echo "========================================================" echo " Building kernel" @$(KERNEL_BUILD_ENV) \ $(MAKE) -C $(KERNEL_OUT) O=$(KERNEL_OUT) ${CC_ARG} -j$(shell nproc)
- 编译modules
#内部模块(源码在KERNEL_DIR中) build-internal-kernel-modules: build-kernel rm -rf $(KERNEL_OUT)/staging mkdir -p $(KERNEL_OUT)/staging echo "========================================================" echo " Installing kernel modules into staging directory" @$(KERNEL_BUILD_ENV) \ $(MAKE) -C $(KERNEL_OUT) O=$(KERNEL_OUT) ${CC_ARG} INSTALL_MOD_STRIP=1 \ INSTALL_MOD_PATH=$(KERNEL_OUT)/staging modules_install #外部模块(源码不在KERNEL_DIR中) define build-external-kernel-module-internal mkdir -p $(1); @$(KERNEL_BUILD_ENV) \ $(MAKE) -C $(1) M=$(1) KERNEL_SRC=$(KERNEL_DIR) \ O=$(KERNEL_OUT) $(CC_ARG) -j$(shell nproc); @$(KERNEL_BUILD_ENV) \ $(MAKE) -C $(1) M=$(1) KERNEL_SRC=$(KERNEL_DIR) \ O=$(KERNEL_OUT) $(CC_ARG) INSTALL_MOD_STRIP=1 \ INSTALL_MOD_PATH=$(KERNEL_OUT)/staging modules_install; endef build-external-kernel-module: build-kernel echo "========================================================" echo " Building external modules and installing them into staging directory" $(foreach ext_mod,$(EXT_MODULES), \ $(call build-external-kernel-module-internal,$(ext_mod)))
-
移除
kernel/build
仓库验证 —— 编译正常
-
- 问题:链接阶段单线程进行,耗时较长
方案:调试阶段不要使能如下宏:
这两个宏会导致编译kernel时,在链接阶段进行优化,导致耗时明显增长;# CONFIG_LTO_CLANG is not set # CONFIG_CFI_CLANG is not set
其他方案
CodeAurora (CodeLinaro)
总所周知,CodeAurora (现改名:CodeLinaro)即是高通基线的开源部分,因此这里的实现就是高通基线的实现,具备一定的权威性;
利:
- 与高通基线一致;
- 支持各种定制化参数传递;
弊:
- 需要移植的文件比较分散,且依赖关系不明显,需要仔细梳理;
- 与kernel一同定义的,还有其他一些高通基线的其他分区编译规则,需要识别并摘除;(毕竟我们没有对应的源码,没法编译出诸如bootloader等产物);
- 编译没有覆盖
PATH
变量,依赖当前shell环境下的编译工具链(例如clang
),若版本较低,则完成CONFIG_LTO_CLANG=y
的内核编译;
LineageOS
LineageOS本质上也是AOSP + 设备闭源部分二进制文件 + 内核源码编译,与我们情况很相似,具备较高参考价值;
利:
- 移植较为简单,规则稳定、成熟;
弊:
- 目前来看不支持kernel module的编译(即编译出ko文件)
后记
到此为止,从源码编译boot.img
的目标也算达成了,如无意外,后续将不再讨论此问题;
事实上,我也尝试过参考CodeAurora与LineageOS的实现,以为可以“抄作业”。但实际操作过程中,我发现,在不充分理解其编译规则编写背后的逻辑的前提下,要从别人完整的框架中甄别出自己需要的,并将其不多不少地精确剪切出来,恰如其分地移植到自己平台与之对应的位置,其难度并不比自己从头实现一遍来得快;
相反,由于自己这样从头操作一遍,不仅可以达到同样的效果,也可以加深自己对编译框架、规则编写等方面的认识,踩一些坑,积累一些相关经验;
若读者在操作过程中遇到任何问题,或者有什么疑问,可以添加评论,或留言讨论各位遇到的问题,我会在看到之后第一时间回复;