Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android系統鏡像文件的打包過程分析

Android系統鏡像文件的打包過程分析

編輯:關於Android編程

在前面一篇文章中,我們分析了Android模塊的編譯過程。當Android系統的所有模塊都編譯好之後,我們就可以對編譯出來的模塊文件進行打包了。打包結果是獲得一系列的鏡像文件,例如system.img、boot.img、ramdisk.img、userdata.img和recovery.img等。這些鏡像文件最終可以燒錄到手機上運行。在本文中,我們就詳細分析Android系統的鏡像文件的打包過程。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

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系統鏡像文件打包過程相關的內容如下所示:

......

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..OVERRIDES := 來說明。其中,表示用來替換的APK,而表示被替換的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文件中生成的,相關的內容如下所示:

# 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文件,它的依賴規則如下所示:

# 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 "Construct 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鏡像所包含的核心文件,它的依賴規則如下所示:

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的依賴規則如下所示:

# -----------------------------------------------------------------
# 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的定義如下所示:
FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)

INTERNAL_USERIMAGES_DEPS描述的是制作system.img鏡像所依賴的工具。例如,如果要制作的system.img使用的是yaffs2文件系統,那麼對應工具就是mkyaffs2image。

INTERNAL_SYSTEMIMAGE_FILES描述的是用來制作system.img鏡像的文件,它的定義如下所示:

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文件。

注意,ALL_DEFAULT_INSTALLED_MODULES描述的文件除了在build/core/main.mk文件中定義的模塊文件之外,還包含以下這些文件:

1. 通過PRODUCT_COPY_FILES定義的要拷貝到目標設備上去的文件

unique_product_copy_files_pairs :=
$(foreach cf,$(PRODUCT_COPY_FILES), \
    $(if $(filter $(unique_product_copy_files_pairs),$(cf)),,\
        $(eval unique_product_copy_files_pairs += $(cf))))
unique_product_copy_files_destinations :=
$(foreach cf,$(unique_product_copy_files_pairs), \
    $(eval _src := $(call word-colon,1,$(cf))) \
    $(eval _dest := $(call word-colon,2,$(cf))) \
    $(call check-product-copy-files,$(cf)) \
    $(if $(filter $(unique_product_copy_files_destinations),$(_dest)), \
        $(info PRODUCT_COPY_FILES $(cf) ignored.), \
        $(eval _fulldest := $(call append-path,$(PRODUCT_OUT),$(_dest))) \
        $(if $(filter %.xml,$(_dest)),\
            $(eval $(call copy-xml-file-checked,$(_src),$(_fulldest))),\
            $(eval $(call copy-one-file,$(_src),$(_fulldest)))) \
        $(eval ALL_DEFAULT_INSTALLED_MODULES += $(_fulldest)) \
        $(eval unique_product_copy_files_destinations += $(_dest))))
2. 由ADDITIONAL_DEFAULT_PROPERTIES定義的屬性所組成的default.prop文件

INSTALLED_DEFAULT_PROP_TARGET := $(TARGET_ROOT_OUT)/default.prop
ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_DEFAULT_PROP_TARGET)
ADDITIONAL_DEFAULT_PROPERTIES := \
    $(call collapse-pairs, $(ADDITIONAL_DEFAULT_PROPERTIES))
ADDITIONAL_DEFAULT_PROPERTIES += \
    $(call collapse-pairs, $(PRODUCT_DEFAULT_PROPERTY_OVERRIDES))
ADDITIONAL_DEFAULT_PROPERTIES := $(call uniq-pairs-by-first-component, \
    $(ADDITIONAL_DEFAULT_PROPERTIES),=)

$(INSTALLED_DEFAULT_PROP_TARGET):
    @echo Target buildinfo: $@
    @mkdir -p $(dir $@)
    $(hide) echo "#" > $@; \
            echo "# ADDITIONAL_DEFAULT_PROPERTIES" >> $@; \
            echo "#" >> $@;
    $(hide) $(foreach line,$(ADDITIONAL_DEFAULT_PROPERTIES), \
        echo "$(line)" >> $@;)
    build/tools/post_process_props.py $@
3. 由ADDITIONAL_BUILD_PROPERTIES等定義的屬性所組成的build.prop文件

INSTALLED_BUILD_PROP_TARGET := $(TARGET_OUT)/build.prop
ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_BUILD_PROP_TARGET)
ADDITIONAL_BUILD_PROPERTIES := \
    $(call collapse-pairs, $(ADDITIONAL_BUILD_PROPERTIES))
ADDITIONAL_BUILD_PROPERTIES := $(call uniq-pairs-by-first-component, \
    $(ADDITIONAL_BUILD_PROPERTIES),=)
......
BUILDINFO_SH := build/tools/buildinfo.sh
$(INSTALLED_BUILD_PROP_TARGET): $(BUILDINFO_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(wildcard $(TARGET_DEVICE_DIR)/system.prop)
    @echo Target buildinfo: $@
    @mkdir -p $(dir $@)
    $(hide) TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
            TARGET_DEVICE="$(TARGET_DEVICE)" \
            PRODUCT_NAME="$(TARGET_PRODUCT)" \
            PRODUCT_BRAND="$(PRODUCT_BRAND)" \
            PRODUCT_DEFAULT_LANGUAGE="$(call default-locale-language,$(PRODUCT_LOCALES))" \
            PRODUCT_DEFAULT_REGION="$(call default-locale-region,$(PRODUCT_LOCALES))" \
            PRODUCT_DEFAULT_WIFI_CHANNELS="$(PRODUCT_DEFAULT_WIFI_CHANNELS)" \
            PRODUCT_MODEL="$(PRODUCT_MODEL)" \
            PRODUCT_MANUFACTURER="$(PRODUCT_MANUFACTURER)" \
            PRIVATE_BUILD_DESC="$(PRIVATE_BUILD_DESC)" \
            BUILD_ID="$(BUILD_ID)" \
            BUILD_DISPLAY_ID="$(BUILD_DISPLAY_ID)" \
            BUILD_NUMBER="$(BUILD_NUMBER)" \
            PLATFORM_VERSION="$(PLATFORM_VERSION)" \
            PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
            PLATFORM_VERSION_CODENAME="$(PLATFORM_VERSION_CODENAME)" \
            BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
            TARGET_BOOTLOADER_BOARD_NAME="$(TARGET_BOOTLOADER_BOARD_NAME)" \
            BUILD_FINGERPRINT="$(BUILD_FINGERPRINT)" \
            TARGET_BOARD_PLATFORM="$(TARGET_BOARD_PLATFORM)" \
            TARGET_CPU_ABI="$(TARGET_CPU_ABI)" \
            TARGET_CPU_ABI2="$(TARGET_CPU_ABI2)" \
            TARGET_AAPT_CHARACTERISTICS="$(TARGET_AAPT_CHARACTERISTICS)" \
            bash $(BUILDINFO_SH) > $@
    $(hide) if [ -f $(TARGET_DEVICE_DIR)/system.prop ]; then \
              cat $(TARGET_DEVICE_DIR)/system.prop >> $@; \
            fi
    $(if $(ADDITIONAL_BUILD_PROPERTIES), \
        $(hide) echo >> $@; \
                echo "#" >> $@; \
                echo "# ADDITIONAL_BUILD_PROPERTIES" >> $@; \
                echo "#" >> $@; )
    $(hide) $(foreach line,$(ADDITIONAL_BUILD_PROPERTIES), \
        echo "$(line)" >> $@;)
    $(hide) build/tools/post_process_props.py $@
4. 用來描述event類型日志格式的event-log-tags文件

all_event_log_tags_file := $(TARGET_OUT_COMMON_INTERMEDIATES)/all-event-log-tags.txt

event_log_tags_file := $(TARGET_OUT)/etc/event-log-tags

# Include tags from all packages that we know about
all_event_log_tags_src := \
    $(sort $(foreach m, $(ALL_MODULES), $(ALL_MODULES.$(m).EVENT_LOG_TAGS)))

# PDK builds will already have a full list of tags that needs to get merged
# in with the ones from source
pdk_fusion_log_tags_file := $(patsubst $(PRODUCT_OUT)/%,$(_pdk_fusion_intermediates)/%,$(filter $(event_log_tags_file),$(ALL_PDK_FUSION_FILES)))

$(all_event_log_tags_file): PRIVATE_SRC_FILES := $(all_event_log_tags_src) $(pdk_fusion_log_tags_file)
    $(hide) mkdir -p $(dir $@)
    $(hide) build/tools/merge-event-log-tags.py -o $@ $(PRIVATE_SRC_FILES)

# Include tags from all packages included in this product, plus all
# tags that are part of the system (ie, not in a vendor/ or device/
# directory).
event_log_tags_src := \
    $(sort $(foreach m,\
      $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES) \
      $(call module-names-for-tag-list,user), \
      $(ALL_MODULES.$(m).EVENT_LOG_TAGS)) \
      $(filter-out vendor/% device/% out/%,$(all_event_log_tags_src)))

$(event_log_tags_file): PRIVATE_SRC_FILES := $(event_log_tags_src) $(pdk_fusion_log_tags_file)
$(event_log_tags_file): PRIVATE_MERGED_FILE := $(all_event_log_tags_file)
$(event_log_tags_file): $(event_log_tags_src) $(all_event_log_tags_file) $(pdk_fusion_log_tags_file)
    $(hide) mkdir -p $(dir $@)
    $(hide) build/tools/merge-event-log-tags.py -o $@ -m $(PRIVATE_MERGED_FILE) $(PRIVATE_SRC_FILES)

event-log-tags: $(event_log_tags_file)

ALL_DEFAULT_INSTALLED_MODULES += $(event_log_tags_file)
關於Android系統的event日志,可以參考Android日志系統驅動程序Logger源代碼分析、Android應用程序框架層和系統運行庫層日志系統源代碼分析和Android日志系統Logcat源代碼簡要分析這個系列的文章。

5. 由於使用了BSD、GPL和Apache許可的模塊而必須在發布時附帶的Notice文件

target_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE.txt
target_notice_file_html := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html
target_notice_file_html_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html.gz
tools_notice_file_txt := $(HOST_OUT_INTERMEDIATES)/NOTICE.txt
tools_notice_file_html := $(HOST_OUT_INTERMEDIATES)/NOTICE.html

kernel_notice_file := $(TARGET_OUT_NOTICE_FILES)/src/kernel.txt
pdk_fusion_notice_files := $(filter $(TARGET_OUT_NOTICE_FILES)/%, $(ALL_PDK_FUSION_FILES))
......
# Install the html file at /system/etc/NOTICE.html.gz.
# This is not ideal, but this is very late in the game, after a lot of
# the module processing has already been done -- in fact, we used the
# fact that all that has been done to get the list of modules that we
# need notice files for.
$(target_notice_file_html_gz): $(target_notice_file_html) | $(MINIGZIP)
    $(hide) $(MINIGZIP) -9 < $< > $@
installed_notice_html_gz := $(TARGET_OUT)/etc/NOTICE.html.gz
$(installed_notice_html_gz): $(target_notice_file_html_gz) | $(ACP)
    $(copy-file-to-target)

# if we've been run my mm, mmm, etc, don't reinstall this every time
ifeq ($(ONE_SHOT_MAKEFILE),)
ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_gz)
endif
6. 用來驗證OTA更新的證書文件

ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT_ETC)/security/otacerts.zip
$(TARGET_OUT_ETC)/security/otacerts.zip: KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
$(TARGET_OUT_ETC)/security/otacerts.zip: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR))
    $(hide) rm -f $@
    $(hide) mkdir -p $(dir $@)
    $(hide) zip -qj $@ $<

.PHONY: otacerts
otacerts: $(TARGET_OUT_ETC)/security/otacerts.zip
二. boot.img

從前面的分析可以知道,build/core/main.mk文件定義了boot.img鏡像文件的依賴規則,我們可以通過執行make bootimage命令來執行。其中,bootimage是一個偽目標,它依賴於INSTALLED_BOOTIMAGE_TARGET。

INSTALLED_BOOTIMAGE_TARGET定義在build/core/Makefile文件中,如下所示:

ifneq ($(strip $(TARGET_NO_KERNEL)),true)

# -----------------------------------------------------------------
# the boot image, which is a collection of other images.
INTERNAL_BOOTIMAGE_ARGS := \
    $(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \
    --kernel $(INSTALLED_KERNEL_TARGET) \
    --ramdisk $(INSTALLED_RAMDISK_TARGET)

INTERNAL_BOOTIMAGE_FILES := $(filter-out --%,$(INTERNAL_BOOTIMAGE_ARGS))

BOARD_KERNEL_CMDLINE := $(strip $(BOARD_KERNEL_CMDLINE))
ifdef BOARD_KERNEL_CMDLINE
  INTERNAL_BOOTIMAGE_ARGS += --cmdline "$(BOARD_KERNEL_CMDLINE)"
endif

BOARD_KERNEL_BASE := $(strip $(BOARD_KERNEL_BASE))
ifdef BOARD_KERNEL_BASE
  INTERNAL_BOOTIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
endif

BOARD_KERNEL_PAGESIZE := $(strip $(BOARD_KERNEL_PAGESIZE))
ifdef BOARD_KERNEL_PAGESIZE
  INTERNAL_BOOTIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
endif

INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img

ifeq ($(TARGET_BOOTIMAGE_USE_EXT2),true)
tmp_dir_for_image := $(call intermediates-dir-for,EXECUTABLES,boot_img)/bootimg
INTERNAL_BOOTIMAGE_ARGS += --tmpdir $(tmp_dir_for_image)
INTERNAL_BOOTIMAGE_ARGS += --genext2fs $(MKEXT2IMG)
$(INSTALLED_BOOTIMAGE_TARGET): $(MKEXT2IMG) $(INTERNAL_BOOTIMAGE_FILES)
    $(call pretty,"Target boot image: $@")
    $(hide) $(MKEXT2BOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) --output $@

else # TARGET_BOOTIMAGE_USE_EXT2 != true

$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES)
    $(call pretty,"Target boot image: $@")
    $(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@
    $(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE),raw)
endif # TARGET_BOOTIMAGE_USE_EXT2

else    # TARGET_NO_KERNEL
# HACK: The top-level targets depend on the bootimage.  Not all targets
# can produce a bootimage, though, and emulator targets need the ramdisk
# instead.  Fake it out by calling the ramdisk the bootimage.
# TODO: make the emulator use bootimages, and make mkbootimg accept
#       kernel-less inputs.
INSTALLED_BOOTIMAGE_TARGET := $(INSTALLED_RAMDISK_TARGET)
endif

在介紹boot.img之前,我們首先要介紹BootLoader。我們都知道,PC啟動的時候,首先執行的是在BIOS上的代碼,然後再由BIOS負責將Kernel加載起來執行。在嵌入式世界裡,BootLoader的作用就相當於PC的BIOS。

BootLoader的啟動通常分為兩個階段。第一階段在固態存儲中(Flash)執行,負責初始化硬件,例如設置CPU工作模式、時鐘頻率以及初始化內存等,並且將第二階段拷貝到RAM去准備執行。第二階段就是在RAM中執行,因此速度會更快,主要負責建立內存映像,以及加載Kernel鏡像和Ramdisk鏡像。

BootLoader的第二階段執行完成後,就開始啟動Kernel了。Kernel負責啟動各個子系統,例如CPU調度子系統和內存管理子系統等等。Kernel啟動完成之後,就會將Ramdisk鏡像安裝為根系統,並且在其中找到一個init文件,將其啟動為第一個進程。init進程啟動就意味著系統進入到用戶空間執行了,這時候各種用戶空間運行時以及守護進程就會被加載起來。最終完成整個系統的啟動過程。

BootLoader的第一階段是固化在硬件中的,而boot.img的存在就是為BootLoader的第一階段提供第二階段、Kernel鏡像、Ramdisk鏡像,以及相應的啟動參數等等。也就是說,boot.img主要包含有BootLoader的第二階段、Kernel鏡像和Ramdisk鏡像。當然,BootLoader的第二階段是可選的。當不存在BootLoader的第二階段的時候,BootLoader的第一階段啟動完成後,就直接進入到kernel啟動階段。

boot.img鏡像的文件格式定義在system/core/mkbootimg/bootimg.h中,如下所示:

/*
** +-----------------+ 
** | boot header     | 1 page
** +-----------------+
** | kernel          | n pages  
** +-----------------+
** | ramdisk         | m pages  
** +-----------------+
** | second stage    | o pages
** +-----------------+
**
** n = (kernel_size + page_size - 1) / page_size
** m = (ramdisk_size + page_size - 1) / page_size
** o = (second_size + page_size - 1) / page_size
**
** 0. all entities are page_size aligned in flash
** 1. kernel and ramdisk are required (size != 0)
** 2. second is optional (second_size == 0 -> no second)
** 3. load each element (kernel, ramdisk, second) at
**    the specified physical address (kernel_addr, etc)
** 4. prepare tags at tag_addr.  kernel_args[] is
**    appended to the kernel commandline in the tags.
** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
** 6. if second_size != 0: jump to second_addr
**    else: jump to kernel_addr
*/
它由4部分組成:boot header、kernel、ramdisk和second state。每一個部分的大小都是以頁為單位的,其中,boot header描述了kernel、ramdisk、sencond stage的加載地址、大小,以及kernel啟動參數等等信息。

boot header的結構同樣是定義在system/core/mkbootimg/bootimg.h中,如下所示:

struct boot_img_hdr
{
    unsigned char magic[BOOT_MAGIC_SIZE];

    unsigned kernel_size;  /* size in bytes */
    unsigned kernel_addr;  /* physical load addr */

    unsigned ramdisk_size; /* size in bytes */
    unsigned ramdisk_addr; /* physical load addr */

    unsigned second_size;  /* size in bytes */
    unsigned second_addr;  /* physical load addr */

    unsigned tags_addr;    /* physical addr for kernel tags */
    unsigned page_size;    /* flash page size we assume */
    unsigned unused[2];    /* future expansion: should be 0 */

    unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */

    unsigned char cmdline[BOOT_ARGS_SIZE];

    unsigned id[8]; /* timestamp / checksum / sha1 / etc */
};
各個成員變量的含義如下所示:

magic:魔數,等於“ANDROID!”。

kernel_size:Kernel大小,以字節為單位。

kernel_addr:Kernel加載的物理地址。

ramdisk_size:Ramdisk大小,以字節為單位。

ramdisk_addr:Ramdisk加載的物理地址。

second_size:BootLoader第二階段的大小,以字節為單位。

second_addr:BootLoader第二階段加載的物理地址。

tags_addr:Kernel啟動之前,它所需要的啟動參數都會填入到由tags_addr所描述的一個物理地址中去。

unused:保留以後使用。

page_size:頁大小。

name:產品名稱。

id:時間戳、校驗碼等等。

理解了BootLoader的啟動過程,以及boot.img的文件結構之後,就不難理解boot.img文件的生成過程了。

首先檢查變量TARGET_NO_KERNEL的值是否等於true。如果等於true的話,那麼就表示boot.img不包含有BootLoader第二階段和Kernel,即它等同於Ramdisk鏡像。如果不等於true的話,那麼就通過INSTALLED_2NDBOOTLOADER_TARGET、INSTALLED_KERNEL_TARGET和INSTALLED_RAMDISK_TARGET獲得BootLoader第二階段、Kernel和Ramdisk對應的鏡像文件,以及通過BOARD_KERNEL_CMDLINE、BOARD_KERNEL_BASE和BOARD_KERNEL_PAGESIZE獲得Kernel啟動命令行參數、內核基地址和頁大小等參數。最後根據TARGET_BOOTIMAGE_USE_EXT2的值來決定是使用genext2fs還是mkbootimg工具來生成boot.img。

三. ramdisk.img

從前面的分析可以知道,build/core/main.mk文件定義了ramdisk.img鏡像文件的依賴規則,我們可以通過執行make ramdisk命令來執行。其中,ramdisk是一個偽目標,它依賴於INSTALLED_RAMDISK_TARGET。

INSTALLED_RAMDISK_TARGET定義在build/core/Makefile文件中,如下所示:

INTERNAL_RAMDISK_FILES := $(filter $(TARGET_ROOT_OUT)/%, \
    $(ALL_PREBUILT) \
    $(ALL_COPIED_HEADERS) \
    $(ALL_GENERATED_SOURCES) \
    $(ALL_DEFAULT_INSTALLED_MODULES))

BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img

# We just build this directly to the install location.
INSTALLED_RAMDISK_TARGET := $(BUILT_RAMDISK_TARGET)
$(INSTALLED_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_RAMDISK_FILES) | $(MINIGZIP)
    $(call pretty,"Target ram disk: $@")
    $(hide) $(MKBOOTFS) $(TARGET_ROOT_OUT) | $(MINIGZIP) > $@

ALL_PREBUILT、ALL_COPIED_HEADERS、ALL_GENERATED_SOURCES和ALL_DEFAULT_INSTALLED_MODULES這幾個變量的含義前面分析system.img的生成過程時已經介紹過了。因此,這裡我們就很容易知道,ramdisk.img鏡像實際上就是由這幾個變量所描述的、保存在TARGET_ROOT_OUT目錄中的文件所組成。與此相對應的是,system.img由保存在TARGET_OUT目錄中的文件組成。

TARGET_ROOT_OUT和TARGET_OUT又分別是指向什麼目錄呢?假設我們的編譯目標產品是模擬器,那麼TARGET_ROOT_OUT和TARGET_OUT對應的目錄就分別為out/target/product/generic/root和out/target/product/generic/system。

收集好對應的文件之後,就可以通過MKBOOTFS和MINIGZIP這兩個變量描述的mkbootfs和minigzip工具來生成一個格式為cpio的ramdisk.img了。mkbootfs和minigzip這兩個工具對應的源碼分別位於system/core/cpio和external/zlib目錄中。

四. userdata.img

userdata.img鏡像描述的是Android系統的data分區,即/data目錄,裡面包含了用戶安裝的APP以及數據等等。

從前面的分析可以知道,build/core/main.mk文件定義了userdata.img鏡像文件的依賴規則,我們可以通過執行make userdataimage命令來執行。其中,userdataimage是一個偽目標,它依賴於INSTALLED_USERDATAIMAGE_TARGET。

INSTALLED_USERDATAIMAGE_TARGET定義在build/core/Makefile文件中,如下所示:

INTERNAL_USERDATAIMAGE_FILES := \
    $(filter $(TARGET_OUT_DATA)/%,$(ALL_DEFAULT_INSTALLED_MODULES))

$(info $(TARGET_OUT_DATA))

# If we build "tests" at the same time, make sure $(tests_MODULES) get covered.
ifdef is_tests_build
INTERNAL_USERDATAIMAGE_FILES += \
    $(filter $(TARGET_OUT_DATA)/%,$(tests_MODULES))
endif

userdataimage_intermediates := \
    $(call intermediates-dir-for,PACKAGING,userdata)
BUILT_USERDATAIMAGE_TARGET := $(PRODUCT_OUT)/userdata.img

define build-userdataimage-target
  $(call pretty,"Target userdata fs image: $(INSTALLED_USERDATAIMAGE_TARGET)")
  @mkdir -p $(TARGET_OUT_DATA)
  @mkdir -p $(userdataimage_intermediates) && rm -rf $(userdataimage_intermediates)/userdata_image_info.txt
  $(call generate-userimage-prop-dictionary, $(userdataimage_intermediates)/userdata_image_info.txt)
  $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \
      ./build/tools/releasetools/build_image.py \
      $(TARGET_OUT_DATA) $(userdataimage_intermediates)/userdata_image_info.txt $(INSTALLED_USERDATAIMAGE_TARGET)
  $(hide) $(call assert-max-image-size,$(INSTALLED_USERDATAIMAGE_TARGET),$(BOARD_USERDATAIMAGE_PARTITION_SIZE),yaffs)
endef

# We just build this directly to the install location.
INSTALLED_USERDATAIMAGE_TARGET := $(BUILT_USERDATAIMAGE_TARGET)
$(INSTALLED_USERDATAIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) \
                                   $(INTERNAL_USERDATAIMAGE_FILES)
    $(build-userdataimage-target)
INSTALLED_USERDATAIMAGE_TARGET的值等於BUILT_USERDATAIMAGE_TARGET,後者指向的就是userdata.img文件,它依賴於INTERNAL_USERDATAIMAGE_FILES描述的文件,即那些由ALL_DEFAULT_INSTALLED_MODULES描述的、並且位於TARGET_OUT_DATA目錄下的文件。假設我們的編譯目標產品是模擬器,那麼TARGET_OUT_DATA對應的目錄就為out/target/product/generic/data。此外,如果我們在make userdataimage的時候,還帶有一個額外的tests目標,那麼那些將自己的tag設置為tests的模塊也會被打包到userdata.img鏡像中。

INSTALLED_USERDATAIMAGE_TARGET還依賴於INTERNAL_USERIMAGES_DEPS。前面在分析system.img鏡像的生成過程時提到,INTERNAL_USERIMAGES_DEPS描述的是制作userdata.img鏡像所依賴的工具。例如,如果要制作的userdata.img使用的是yaffs2文件系統,那麼對應工具就是mkyaffs2image。

INSTALLED_USERDATAIMAGE_TARGET規則由函數build-userdataimage-target來執行,該函數通過build_image.py腳本來生成userdata.img鏡像文件。

與system.img鏡像類似,userdata.img鏡像的生成也可以通過一個沒有任何依賴的偽目標userdataimage-nodeps生成,如下所示:

.PHONY: userdataimage-nodeps
userdataimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)
    $(build-userdataimage-target)
當我們執行make userdataimage-nodeps的時候,函數build-userdataimage-target就會被調用直接生成userdata.img文件。

五. recovery.img

recovery.img是設備進入recovery模式時所加載的鏡像。recovery模式就是用來更新系統的,我們可以認為我們的設備具有兩個系統。一個是正常的系統,它由boot.img、system.img、ramdisk.img和userdata.img等組成,另外一個就是recovery系統,由recovery.img組成。平時我們進入的都是正常的系統,只有當我們需要更新這個正常的系統時,才會進入到recovery系統。因此,我們可以將recovery系統理解為在Linux Kernel之上運行的一個小小的用戶空間運行時。這個用戶空間運行時可以訪問我們平時經常使用的那個系統的文件,從而實現對它的更新。

在build/core/Makefile文件中,定義了一個偽目標recoveryimage,用來生成recovery.img,如下所示:

.PHONY: recoveryimage
recoveryimage: $(INSTALLED_RECOVERYIMAGE_TARGET) $(RECOVERY_RESOURCE_ZIP)
INSTALLED_RECOVERYIMAGE_TARGET描述的是組成recovery系統的模塊文件,而RECOVERY_RESOURCE_ZIP描述的是recovery系統使用的資源包,它的定義如下所示:

INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

recovery_initrc := $(call include-path-for, recovery)/etc/init.rc
recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system
recovery_ramdisk := $(PRODUCT_OUT)/ramdisk-recovery.img
recovery_build_prop := $(INSTALLED_BUILD_PROP_TARGET)
recovery_binary := $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery
recovery_resources_common := $(call include-path-for, recovery)/res
recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res))
recovery_resource_deps := $(shell find $(recovery_resources_common) \
  $(recovery_resources_private) -type f)
recovery_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery.fstab))
# Named '.dat' so we don't attempt to use imgdiff for patching it.
RECOVERY_RESOURCE_ZIP := $(TARGET_OUT)/etc/recovery-resource.dat

......

INTERNAL_RECOVERYIMAGE_ARGS := \
    $(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \
    --kernel $(recovery_kernel) \
    --ramdisk $(recovery_ramdisk)

# Assumes this has already been stripped
ifdef BOARD_KERNEL_CMDLINE
  INTERNAL_RECOVERYIMAGE_ARGS += --cmdline "$(BOARD_KERNEL_CMDLINE)"
endif
ifdef BOARD_KERNEL_BASE
  INTERNAL_RECOVERYIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
endif
BOARD_KERNEL_PAGESIZE := $(strip $(BOARD_KERNEL_PAGESIZE))
ifdef BOARD_KERNEL_PAGESIZE
  INTERNAL_RECOVERYIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
endif

# Keys authorized to sign OTA packages this build will accept.  The
# build always uses dev-keys for this; release packaging tools will
# substitute other keys for this one.
OTA_PUBLIC_KEYS := $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem

# Generate a file containing the keys that will be read by the
# recovery binary.
RECOVERY_INSTALL_OTA_KEYS := \
    $(call intermediates-dir-for,PACKAGING,ota_keys)/keys
DUMPKEY_JAR := $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar
$(RECOVERY_INSTALL_OTA_KEYS): PRIVATE_OTA_PUBLIC_KEYS := $(OTA_PUBLIC_KEYS)
$(RECOVERY_INSTALL_OTA_KEYS): extra_keys := $(patsubst %,%.x509.pem,$(PRODUCT_EXTRA_RECOVERY_KEYS))
$(RECOVERY_INSTALL_OTA_KEYS): $(OTA_PUBLIC_KEYS) $(DUMPKEY_JAR) $(extra_keys)
    @echo "DumpPublicKey: $@ <= $(PRIVATE_OTA_PUBLIC_KEYS) $(extra_keys)"
    @rm -rf $@
    @mkdir -p $(dir $@)
    java -jar $(DUMPKEY_JAR) $(PRIVATE_OTA_PUBLIC_KEYS) $(extra_keys) > $@

$(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
        $(INSTALLED_RAMDISK_TARGET) \
        $(INSTALLED_BOOTIMAGE_TARGET) \
        $(recovery_binary) \
        $(recovery_initrc) $(recovery_kernel) \
        $(INSTALLED_2NDBOOTLOADER_TARGET) \
        $(recovery_build_prop) $(recovery_resource_deps) \
        $(recovery_fstab) \
        $(RECOVERY_INSTALL_OTA_KEYS)
    @echo ----- Making recovery image ------
    $(hide) rm -rf $(TARGET_RECOVERY_OUT)
    $(hide) mkdir -p $(TARGET_RECOVERY_OUT)
    $(hide) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc $(TARGET_RECOVERY_ROOT_OUT)/tmp
    @echo Copying baseline ramdisk...
    $(hide) cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT)
    @echo Modifying ramdisk contents...
    $(hide) rm -f $(TARGET_RECOVERY_ROOT_OUT)/init*.rc
    $(hide) cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/
    $(hide) -cp $(TARGET_ROOT_OUT)/init.recovery.*.rc $(TARGET_RECOVERY_ROOT_OUT)/
    $(hide) cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/
    $(hide) cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/
    $(hide) $(foreach item,$(recovery_resources_private), \
      cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)
    $(hide) $(foreach item,$(recovery_fstab), \
      cp -f $(item) $(TARGET_RECOVERY_ROOT_OUT)/etc/recovery.fstab)
    $(hide) cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys
    $(hide) cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) \
            > $(TARGET_RECOVERY_ROOT_OUT)/default.prop
    $(hide) $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk)
    $(hide) $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@
    $(hide) $(call assert-max-image-size,$@,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw)
    @echo ----- Made recovery image: $@ --------

$(RECOVERY_RESOURCE_ZIP): $(INSTALLED_RECOVERYIMAGE_TARGET)
    $(hide) mkdir -p $(dir $@)
    $(hide) find $(TARGET_RECOVERY_ROOT_OUT)/res -type f | sort | zip -0qrj $@ -@

由於recovery.img和boot.img都是用來啟動系統的,因此,它們的內容是比較類似,例如都包含有Kernel及其啟動參數、Ramdisk,以及可選的BootLoader第二階段。此外,recovery.img還包含有以下的主要內容:

1. 用來設置系統屬性的文件build.prop和default.prop;

2. 進入recovery界面時用到的資源文件recovery_resources_common和recovery_resources_private;

3. 描述進入recovery模式時要安裝的文件系統腳本recovery.fstab;

4. 在recovery模式執行OTA更新要用到的證書OTA_PUBLIC_KEYS;

5. 負責升級系統的recovery程序,它的源代碼位於bootable/recovery目錄中。

通過對比Android正常使用時的系統和進行升級時使用的Recovery系統,我們就可以對運行Linux系統的移動設備(或者說嵌入式設備)世界窺一斑而知全豹,實際上我們只需要有一個BootLoader、一個Linux Kernel,以及一個Ramdisk,就可以將它們運行起來。Ramdisk屬於用戶空間的范疇,它裡面主要包含了Linux Kernel啟動完成時所要加載的根文件系統。這個根文件系統的作用就是為整個系統提供一個init程序,以便Linux Kernel可以將控制權從內核空間轉移到用戶空間。系統提供什麼樣的功能給用戶使用主要取決於在用戶空間層運行了哪些服務和守護進程。這些服務守護進程都是直接或者間接地由init進程啟動的。例如,對於recovery系統來說,它最主要提供的功能就是讓用戶升級系統,也就是它的主要用戶空間服務就是負責執行長級任務的recovery程序。又如,我們正常使用的Android系統,它的主要用戶空間服務和守護進程都是由system.img鏡像提供的,此外,我們還可以通過userdata.img鏡像來安裝第三方應用來豐富手機的功能。因此,我們也可以說,init進程啟動的服務決定了系統的功能。當然 ,這些功能是建立是硬件的基礎之上的。

至此,我們就分析完成Android系統的主要鏡像文件system.img、boot.img、ramdisk.img、userdata.img和recovery.img的制作過程了,希望通過這些鏡像文件的制作過程以及它們的作用的介紹,使得小伙伴後對Android系統有更進一步的認識。同時,我們也結束了對Android編譯系統的學習了。重新學習請參考Android編譯系統簡要介紹和學習計劃一文。想了解更多Android相關技術,歡迎關注老羅的新浪微博:http://weibo.com/shengyangluo。


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved