[Android]基于AOSP源码为Pixel3编译boot.img(下)

本文档详细介绍了如何基于AOSP源码为Pixel3编译boot.img,包括编译环境、步骤及遇到的问题与解决方案。讨论了CodeAurora和LineageOS的不同实现,并提供了目录结构优化、编译速度提升的建议,旨在实现从源码到boot.img的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[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仓库验证 —— 编译正常

  1. 问题:链接阶段单线程进行,耗时较长
    方案:调试阶段不要使能如下宏:
    # CONFIG_LTO_CLANG is not set
    # CONFIG_CFI_CLANG is not set
    
    这两个宏会导致编译kernel时,在链接阶段进行优化,导致耗时明显增长;

其他方案

CodeAurora (CodeLinaro)

总所周知,CodeAurora (现改名:CodeLinaro)即是高通基线的开源部分,因此这里的实现就是高通基线的实现,具备一定的权威性;
利:

  1. 与高通基线一致;
  2. 支持各种定制化参数传递;

弊:

  1. 需要移植的文件比较分散,且依赖关系不明显,需要仔细梳理;
  2. 与kernel一同定义的,还有其他一些高通基线的其他分区编译规则,需要识别并摘除;(毕竟我们没有对应的源码,没法编译出诸如bootloader等产物);
  3. 编译没有覆盖PATH变量,依赖当前shell环境下的编译工具链(例如clang),若版本较低,则完成CONFIG_LTO_CLANG=y的内核编译;

LineageOS

LineageOS本质上也是AOSP + 设备闭源部分二进制文件 + 内核源码编译,与我们情况很相似,具备较高参考价值;
利:

  1. 移植较为简单,规则稳定、成熟;

弊:

  1. 目前来看不支持kernel module的编译(即编译出ko文件)

后记

到此为止,从源码编译boot.img的目标也算达成了,如无意外,后续将不再讨论此问题;

事实上,我也尝试过参考CodeAurora与LineageOS的实现,以为可以“抄作业”。但实际操作过程中,我发现,在不充分理解其编译规则编写背后的逻辑的前提下,要从别人完整的框架中甄别出自己需要的,并将其不多不少地精确剪切出来,恰如其分地移植到自己平台与之对应的位置,其难度并不比自己从头实现一遍来得快;

相反,由于自己这样从头操作一遍,不仅可以达到同样的效果,也可以加深自己对编译框架、规则编写等方面的认识,踩一些坑,积累一些相关经验;

若读者在操作过程中遇到任何问题,或者有什么疑问,可以添加评论,或留言讨论各位遇到的问题,我会在看到之后第一时间回复;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值