搜索
 找回密码
 立即注册

简单一步 , 微信登陆

Android系统镜像文件的打包|过程分析

作者:liuwei | 时间:2016-9-28 13:12:10 | 阅读:4533| 只看该作者

  在前面一篇文章中,我们分析了Android模块的编译过程。当Android系统的所有模块都编译好之后,我们就可以对编译出来的模块文件进行打包了。打包结果是获得一系列的镜像文件,例如system.img、boot.img、ramdisk.img、userdata.img和recovery.img等。这些镜像文件最终可以烧录到手机上运行。在本文中,我们就详细分析Android系统的镜像文件的打**程。

       Android系统镜像文件的打包工作同样是由Android编译系统来完成的,如图1所示:

图1 Android系统镜像文件的打**程

       从前面Android编译系统环境初始化过程分析和Android源代码编译命令m/mm/mmm/make分析这两篇文章可以知道,Android编译系统在初始化的过程中,会通过根目录下的Makefile脚本加载build/core/main.mk脚本,接着build/core/main.mk脚本又会加载build/core/Makefile脚本,而Android系统镜像文件就是由build/core/Makefile脚本负责打包生成的。

       在build/core/main.mk文件中,与Android系统镜像文件打**程相关的内容如下所示:


[plain] view plain copy


  • ......  
  •   
  • ifdef FULL_BUILD  
  •   # The base list of modules to build for this product is specified  
  •   # by the appropriate product definition file, which was included  
  •   # by product_config.make.  
  •   product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES)  
  •   # Filter out the overridden packages before doing expansion  
  •   product_MODULES := $(filter-out $(foreach p, $(product_MODULES), \  
  •       $(PACKAGES.$(p).OVERRIDES)), $(product_MODULES))  
  •   $(call expand-required-modules,product_MODULES,$(product_MODULES))  
  •   product_FILES := $(call module-installed-files, $(product_MODULES))  
  •   ......  
  • else  
  •   # We're not doing a full build, and are probably only including  
  •   # a subset of the module makefiles.  Don't try to build any modules  
  •   # requested by the product, because we probably won't have rules  
  •   # to build them.  
  •   product_FILES :=  
  • endif  
  • ......  
  •   
  • modules_to_install := $(sort \  
  •     $(ALL_DEFAULT_INSTALLED_MODULES) \  
  •     $(product_FILES) \  
  •     $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \  
  •     $(call get-tagged-modules, shell_$(TARGET_SHELL)) \  
  •     $(CUSTOM_MODULES) \  
  •   )  
  •   
  • # Some packages may override others using LOCAL_OVERRIDES_PACKAGES.  
  • # Filter out (do not install) any overridden packages.  
  • overridden_packages := $(call get-package-overrides,$(modules_to_install))  
  • ifdef overridden_packages  
  • #  old_modules_to_install := $(modules_to_install)  
  •   modules_to_install := \  
  •       $(filter-out $(foreach p,$(overridden_packages),$(p) %/$(p).apk), \  
  •           $(modules_to_install))  
  • endif  
  • ......  
  •   
  • # Install all of the host modules  
  • modules_to_install += $(sort $(modules_to_install) $(ALL_HOST_INSTALLED_FILES))  
  •   
  • # build/core/Makefile contains extra stuff that we don't want to pollute this  
  • # top-level makefile with.  It expects that ALL_DEFAULT_INSTALLED_MODULES  
  • # contains everything that's built during the current make, but it also further  
  • # extends ALL_DEFAULT_INSTALLED_MODULES.  
  • ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)  
  • include $(BUILD_SYSTEM)/Makefile  
  • modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))  
  • ALL_DEFAULT_INSTALLED_MODULES :=  
  • ......  
  •   
  • .PHONY: ramdisk  
  • ramdisk: $(INSTALLED_RAMDISK_TARGET)  
  • ......  
  •   
  • .PHONY: userdataimage  
  • userdataimage: $(INSTALLED_USERDATAIMAGE_TARGET)  
  • ......  
  •   
  • .PHONY: bootimage  
  • bootimage: $(INSTALLED_BOOTIMAGE_TARGET)  
  •   
  • ......  

       如果定义在FULL_BUILD这个变量,就意味着我们是要对整个系统进行编译,并且编译完成之后 ,需要将编译得到的文件进行打包,以便可以得到相应的镜像文件,否则的话,就仅仅是对某些模块进行编译。

       在前面Android编译系统环境初始化过程分析一篇文章中,我们提到,变量INTERNAL_PRODUCT描述的是执行lunch命令时所选择的产品所对应的产品Makefile文件,而变量PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES描述的是在该产品Makefile文件中通过变量PRODUCT_PACKAGES所定义的模块名称列表。因此,我们得到的变量product_MODULES描述的就是要安装的模块名称列表。

       我们知道,Android源码中自带了很多默认的APK模块。如果我们想用自己编写的一个APK来代替某一个系统自带的APK,那么就可以通过变量PACKAGES.<new>.OVERRIDES := <old>来说明。其中,<new>表示用来替换的APK,而<old>表示被替换的APK。在这种情况下,被替换的APK是不应该被打包到系统镜像中去的。因此,我们需要从上一步得到的模块名称列表中剔除那些被替换的APK,这是通过Makefile脚本提供的filter-out函数来完成的。

       一个模块可以通过LOCAL_REQUIRED_MODULES变量来指定它所依赖的其它模块,因此,当一个模块被安装的时候,它所依赖的其它模块也会一起被安装。调用函数expand-required-modules获得的就是所有要安装的模块所依赖的其它模块,并且将这些被依赖的模块也一起保存在变量product_MODULES中。

       注意,这时候我们得到的product_MODULES描述的仅仅是要安装的模块的名称,但是我们实际需要的这些模块对应的具体文件,因此,需要进一步调用函数module-installed-files来获得要安装的模块所对应的文件,也就是要安装的模块经过编译之后生成的文件,这些文件就保存在变量product_FILES中。

       最终需要安装的文件除了变量product_FILES所描述的文件之后, 还包含以下四类模块文件:

       1. 变量ALL_DEFAULT_INSTALLED_MODULES描述的文件。

       2. 变量CUSTOM_MODULES描述的文件。

       3. 与当前编译类型对应的模块文件。例如,如果当前设定的编译类型为debug,那么所有通过将变量LOCAL_MODULE_TAGS将自己设置为debug的模块也会被打包到系统镜像文件中去。

       4. 与当前shell名称对应的模块文件。例如,如果当前使用的shell是mksh,那么所有通过将变量LOCAL_MODULE_TAGS将自己设置为shell_mksh的模块也会被打包到系统镜像文件中去。

       最后,modules_to_install就描述当前需要打包到系统镜像中去的模块文件。实际上,我们除了可以通过PACKAGES.$(p).OVERRIDES来描述要替换的APK之后,还可以在一个模块中通过LOCAL_OVERRIDES_PACKAGES来描述它所替换的模块。因此,我们需要通过函数get-package-overrides来获得此类被替换的模块文件,并且将它们从modules_to_install中剔除,这样得到的模块文件才是最终需要安装的。

       确定了要安装的所有模块文件之后,就可以将build/core/Makefile文件加载进来了。注意,文件build/core/Makefile是通过变量ALL_DEFAULT_INSTALLED_MODULES来获得当前所有要打包到系统镜像文件中去的模块文件的。

       文件build/core/Makefile主要就是用来打包各种Android系统镜像文件的,当然它也是通过make规则来执行各种Android系统镜像文件打包命令的。每一个Android镜像文件都对应有一个make伪目标。例如,在build/core/main.mk文件中,就定义了三个make伪目标ramdisk、userdataimage和bootimage,它们分别依赖于变量INSTALLED_USERDATAIMAGE_TARGET、INSTALLED_USERDATAIMAGE_TARGET和INSTALLED_BOOTIMAGE_TARGET所描述的文件,并且它们分别表示的就是ramdisk.img、userdata.img和boot.img文件。

        变量INSTALLED_USERDATAIMAGE_TARGET、INSTALLED_USERDATAIMAGE_TARGET和INSTALLED_BOOTIMAGE_TARGET都是在build/core/Makefile文件中定义的。此外,build/core/Makefile文件还定义了另外两个镜像文件system.img和recovery.img的生成规则。接下来,我们就分别对这些镜像文件的打**程进行分析。

        一. system.img

        system.img镜像文件描述的是设备上的system分区,即/system目录,它是在build/core/Makefile文件中生成的,相关的内容如下所示:


[plain] view plain copy


  • # Rules that need to be present for the all targets, even  
  • # if they don't do anything.  
  • .PHONY: systemimage  
  • systemimage:  
  • ......  
  •   
  • INSTALLED_SYSTEMIMAGE := $(PRODUCT_OUT)/system.img  
  • ......  
  •   
  • $(INSTALLED_SYSTEMIMAGE): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH) | $(ACP)  
  •     @echo "Install system fs image: $@"  
  •     $(copy-file-to-target)  
  •     $(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE),yaffs)  
  •   
  • systemimage: $(INSTALLED_SYSTEMIMAGE)  
  •   
  • .PHONY: systemimage-nodeps snod  
  • systemimage-nodeps snod: $(filter-out systemimage-nodeps snod,$(MAKECMDGOALS)) \  
  •                 | $(INTERNAL_USERIMAGES_DEPS)  
  •     @echo "make $@: ignoring dependencies"  
  •     $(call build-systemimage-target,$(INSTALLED_SYSTEMIMAGE))  
  •     $(hide) $(call assert-max-image-size,$(INSTALLED_SYSTEMIMAGE),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE),yaffs)  


       从这里就可以看出,build/core/Makefile文件定义了两个伪目标来生成system.img:1. systemimg;2. systemimg-nodeps或者snod。伪目标systemimg表示在打包system.img之前,要根据依赖规则重新生成所有要进行打包的文件,而伪目标systemimg-nodeps则不需要根据依赖规则重新生成所有需要打包的文件而直接打包system.img文件。因此,执行systemimg-nodep比执行systemimg要快很多。通常,如果我们在Android源码中修改了某一个模块,并且这个模块不被其它模块依赖,那么对这个模块进行编译之后,就可以简单地执行make systemimg-nodeps来重新打包system.img。但是,如果我们修改的模块会被其它模块引用,例如,我们修改了Android系统的核心模块framework.jar和services.jar,那么就需要执行make systemimg来更新所有依赖于framework.jar和services.jar的模块,那么最后得到的system.img才是正确的镜像。否则的话,会导致Android系统启动失败。

       接下来,我们就主要关注伪目标systemimg生成system.img镜像文件的过程。伪目标systemimg依赖于INSTALLED_SYSTEMIMAGE,也就是最终生成的$(PRODUCT_OUT)/system.img文件。INSTALLED_SYSTEMIMAGE又依赖于BUILT_SYSTEMIMAGE、RECOVERY_FROM_BOOT_PATCH和ACP。注意,BUILT_SYSTEMIMAGE、RECOVERY_FROM_BOOT_PATCH和ACP之间有一个管道符号相隔。在一个make规则之中,一个目标的依赖文件可以划分为两类。一个类是普通依赖文件,它们位于管道符号的左则,另一类称为“order-only”依赖文件,它们位于管理符号的右侧。每一个普通依赖文件发生修改后,目标都会被更新。但是"order-only"依赖文件发生修改时,却不一定会导致目标更新。只有当目标文件不存在的情况下,"order-only"依赖文件的修改才会更新目标文件。也就是说,只有在目标文件不存在的情况下,“order-only”依赖文件才会参与到规则的执行过程中去。

        ACP描述的是一个Android专用的cp命令,在生成system.img镜像文件的过程中是需要用到的。普通的cp命令在不同的平台(Mac OS X、MinGW/Cygwin和Linux)的实现略有差异,并且可能会导致一些问题,于是Android编译系统就重写了自己的cp命令,使得它在不同平台下执行具有统一的行为,并且解决普通cp命令可能会出现的问题。例如,在Linux平台上,当我们把一个文件从NFS文件系统拷贝到本地文件系统时,普通的cp命令总是会认为在NFS文件系统上的文件比在本地文件系统上的文件要新,因为前者的时间戳精度是微秒,而后者的时间戳精度不是微秒。Android专用的cp命令源码可以参考build/tools/acp目录。

        RECOVERY_FROM_BOOT_PATCH描述的是一个patch文件,它的依赖规则如下所示:


[plain] view plain copy


  • # The system partition needs room for the recovery image as well.  We  
  • # now store the recovery image as a binary patch using the boot image  
  • # as the source (since they are very similar).  Generate the patch so  
  • # we can see how big it's going to be, and include that in the system  
  • # image size check calculation.  
  • ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)  
  • intermediates := $(call intermediates-dir-for,PACKAGING,recovery_patch)  
  • RECOVERY_FROM_BOOT_PATCH := $(intermediates)/recovery_from_boot.p  
  • $(RECOVERY_FROM_BOOT_PATCH): $(INSTALLED_RECOVERYIMAGE_TARGET) \  
  •                              $(INSTALLED_BOOTIMAGE_TARGET) \  
  •                  $(HOST_OUT_EXECUTABLES)/imgdiff \  
  •                          $(HOST_OUT_EXECUTABLES)/bsdiff  
  •     @echo "C**truct recovery from boot"  
  •     mkdir -p $(dir $@)  
  •     PATH=$(HOST_OUT_EXECUTABLES)$PATH $(HOST_OUT_EXECUTABLES)/imgdiff $(INSTALLED_BOOTIMAGE_TARGET) $(INSTALLED_RECOVERYIMAGE_TARGET) $@  
  • endif  

       这个patch文件的名称为recovery_from_boot.p,保存在设备上system分区中,描述的是recovery.img与boot.img之间的差异。也就是说,在设备上,我们可以通过boot.img和recovery_from_boot.p文件生成一个recovery.img文件,使得设备可以进入recovery模式。

       INSTALLED_SYSTEMIMAGE描述的是system.img镜像所包含的核心文件,它的依赖规则如下所示:


[plain] view plain copy


  • systemimage_intermediates := \  
  •     $(call intermediates-dir-for,PACKAGING,systemimage)  
  • BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img  
  •   
  • # $(1): output file  
  • define build-systemimage-target  
  •   @echo "Target system fs image: $(1)"  
  •   @mkdir -p $(dir $(1)) $(systemimage_intermediates) && rm -rf $(systemimage_intermediates)/system_image_info.txt  
  •   $(call generate-userimage-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt)  
  •   $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p)$$PATH \  
  •       ./build/tools/releasetools/build_image.py \  
  •       $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1)  
  • endef  
  •   
  • $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)  
  •     $(call build-systemimage-target,$@)  


        INSTALLED_SYSTEMIMAGE描述的是$(systemimage_intermediates)/system.img文件,它依赖于FULL_SYSTEMIMAGE_DEPS和INSTALLED_FILES_FILE,并且是通过调用函数build-systemimage-target来生成,而函数build-systemimage-target实际上又是通过调用python脚本build_image.py来生成system.img文件的。

        INSTALLED_FILES_FILE的依赖规则如下所示:


[plain] view plain copy


  • # -----------------------------------------------------------------  
  • # installed file list  
  • # Depending on anything that $(BUILT_SYSTEMIMAGE) depends on.  
  • # We put installed-files.txt ahead of image itself in the dependency graph  
  • # so that we can get the size stat even if the build fails due to too large  
  • # system image.  
  • INSTALLED_FILES_FILE := $(PRODUCT_OUT)/installed-files.txt  
  • $(INSTALLED_FILES_FILE): $(FULL_SYSTEMIMAGE_DEPS)  
  •     @echo Installed file list: $@  
  •     @mkdir -p $(dir $@)  
  •     @rm -f $@  
  •     $(hide) build/tools/fileslist.py $(TARGET_OUT) > $@  

        INSTALLED_FILES_FILE描述的是$(PRODUCT_OUT)/installed-files.txt文件,该文件描述了要打包在system.img镜像中去的文件列表,同时它与INSTALLED_SYSTEMIMAGE一样,也依赖于FULL_SYSTEMIMAGE_DEPS。        
        FULL_SYSTEMIMAGE_DEPS的定义如下所示:[plain] view plain copy


  • FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)  

        INTERNAL_USERIMAGES_DEPS描述的是制作system.img镜像所依赖的工具。例如,如果要制作的system.img使用的是yaffs2文件系统,那么对应工具就是mkyaffs2image。

        INTERNAL_SYSTEMIMAGE_FILES描述的是用来制作system.img镜像的文件,它的定义如下所示:


[plain] view plain copy


  • INTERNAL_SYSTEMIMAGE_FILES := $(filter $(TARGET_OUT)/%, \  
  •     $(ALL_PREBUILT) \  
  •     $(ALL_COPIED_HEADERS) \  
  •     $(ALL_GENERATED_SOURCES) \  
  •     $(ALL_DEFAULT_INSTALLED_MODULES) \  
  •     $(PDK_FUSION_SYSIMG_FILES) \  
  •     $(RECOVERY_RESOURCE_ZIP))  

        从这里就可以看出,INTERNAL_SYSTEMIMAGE_FILES描述的就是从ALL_PREBUILT、ALL_COPIED_HEADERS、ALL_GENERATED_SOURCES、ALL_DEFAULT_INSTALLED_MODULES、PDK_FUSION_SYSIMG_FILES和RECOVERY_RESOURCE_ZIP中过滤出来的存放在TARGET_OUT目录下的那些文件,即在目标产品输出目录中的system子目录下那些文件。

        ALL_PREBUILT是一个过时的变量,用来描述要拷贝到目标设备上去的文件,现在建议使用PRODUCT_COPY_FILES来描述要拷贝到目标设备上去的文件。

        ALL_COPIED_HEADERS描述的是要拷贝到目标设备上去的头文件。

        ALL_GENERATED_SOURCES描述的是要拷贝到目标设备上去的由工具自动生成的源代码文件。

        ALL_DEFAULT_INSTALLED_MODULES描述的就是要安装要目标设备上的模块文件,这些模块文件是在build/core/main.mk文件中设置好并且传递给build/core/Makefile文件使用的。

        PDK_FUSION_SYSIMG_FILES是从PDK(Platform Development Kit)提取出来的相关文件。PDK是Google为了解决Android碎片化问题而为手机厂商提供的一个新版本的、还未发布的Android开发包,目的是为了让手机厂商跟上官方新版Android的开发节奏。具体可以参考这篇文章:http://www.xinwengao.net/release/af360/67079.shtml

        RECOVERY_RESOURCE_ZIP描述的是Android的recovery系统要使用的资源文件,对应于/system/etc目录下的recovery-resource.dat文件。



收藏
收藏0
分享
分享
点赞
点赞0
反对
反对0
回复

使用道具 举报

大神点评1

沙发#
liuwei 发表于:2016-9-28 14:09:58
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册
手机版