在前面一篇文章中,我们分析了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文件。
|