安卓系统中有system.img,boot.img,userdata.img等镜像文件,那么这些镜像文件是怎么形成的呢?下面我们以system.img为例来描述系统镜像文件的编译打包过程。
(一)system.img的编译生成过程build/core/Makefile中相关变量的定义:
INSTALLED_SYSTEMIMAGE := $(PRODUCT_OUT)/system.img // system.img实际产生的位置,即out/target/product/~/system.img编译的target :
$(INSTALLED_SYSTEMIMAGE): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH) | $(ACP) $(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)) $(copy-file-to-target) @echo "Install system fs image: $@" systemimage: $(INSTALLED_SYSTEMIMAGE)
安卓系统编译时,会including 相关的MK文件,因此可以通过make systemimage来单独编译system.img。当$(INSTALLED_SYSTEMIMAGE)执行时,会先进行$(BUILT_SYSTEMIMAGE)的编译,即:
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE) $(call build-systemimage-target,$@) (1)FULL_SYSTEMIMAGE_DEPS FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS) INTERNAL_SYSTEMIMAGE_FILES := $(filter $(TARGET_OUT)/%, \ $(ALL_DEFAULT_INSTALLED_MODULES) \ $(RECOVERY_RESOURCE_ZIP)) $(PDK_FUSION_SYSIMG_FILES) \ $(ALL_PREBUILT) \ $(ALL_GENERATED_SOURCES) \这部分基本就是system所包含的内容了,有兴趣的可以根据TARGET跟下去。
编译过程中在Linux的终端上可以看到"Install system fs image:" 的输出,在上面两个TARGET执行后,会调用build-systemimage-target方法,如下:
define build-systemimage-target @echo "Target system fs image: $(1)" $(call create-system-vendor-symlink) @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, \ skip_fsck=true) $(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) $(TARGET_OUT) \ || ( echo "Out of space? the tree size of $(TARGET_OUT) is (MB): " 1>&2 ;\ du -sm $(TARGET_OUT) 1>&2;\ if [ "$(INTERNAL_USERIMAGES_EXT_VARIANT)" == "ext4" ]; then \ maxsize=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE); \ if [ "$(BOARD_HAS_EXT4_RESERVED_BLOCKS)" == "true" ]; then \ maxsize=$$((maxsize - 4096 * 4096)); \ fi; \ echo "The max is $$(( maxsize / 1048576 )) MB." 1>&2 ;\ else \ echo "The max is $$(( $(BOARD_SYSTEMIMAGE_PARTITION_SIZE) / 1048576 )) MB." 1>&2 ;\ fi; \ mkdir -p $(DIST_DIR); cp $(INSTALLED_FILES_FILE) $(DIST_DIR)/installed-files-rescued.txt; \ exit 1 ) endef
build-systemimage-target方法中会进行如下几件事情:
create-system-vendor-symlink:将system/vendor软连接到vendor下面
# Create symlink /system/vendor to /vendor if necessary. $(hide) if [ -d $(TARGET_OUT)/vendor ] && [ ! -h $(TARGET_OUT)/vendor ]; then \ echo 'You cannot install files to $(TARGET_OUT)/vendor while building a separate vendor.img!' 1>&2; \ define create-system-vendor-symlink endif ifdef BOARD_USES_VENDORIMAGE echo 'Non-symlink $(TARGET_OUT)/vendor detected!' 1>&2; \ endef endef else define create-system-vendor-symlink $(hide) ln -sf /vendor $(TARGET_OUT)/vendor fi exit 1; \然后创建systemimage_intermediates目录,强制删除目录下面的system_image_info.txt文件后重新生成该文件(即更新文件)。接下来就是使用python脚本将已经生成好的system目录整体打包了。
(二)system.img打包过程
$(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) $(TARGET_OUT)在执行build_image.py文件时,需要传递四个参数 $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT)
$(TARGET_OUT)对应目录out/target/product/~/system
$(systemimage_intermediates)/system_image_info.txt是system.img的配置文件
build/tools/releasetools/build_image.py在执行的时候会先检查传入的参数是否满足条件:
def main(argv): if len(argv) != 4: print __doc__ sys.exit(1) in_dir = argv[0] glob_dict_file = argv[1] out_file = argv[2] target_out = argv[3] glob_dict = LoadGlobalDict(glob_dict_file) if "mount_point" in glob_dict: # The caller knows the mount point and provides a dictionay needed by # BuildImage(). image_properties = glob_dict else: image_filename = os.path.basename(out_file) mount_point = "" if image_filename == "system.img": mount_point = "system" elif image_filename == "system_other.img": mount_point = "system_other" elif image_filename == "userdata.img": mount_point = "data" elif image_filename == "cache.img": mount_point = "cache" elif image_filename == "vendor.img": mount_point = "vendor" elif image_filename == "oem.img": mount_point = "oem" else: print >> sys.stderr, "error: unknown image file name ", image_filename exit(1) image_properties = ImagePropFromGlobalDict(glob_dict, mount_point) if not BuildImage(in_dir, image_properties, out_file, target_out): print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir) exit(1) if __name__ == '__main__': main(sys.argv[1:])传给build_image.py的参数必须是四个,数目不对程序会自动退出。然后进行目录的判断,如果是system.img就将挂载点指向system。这里global_dict_file是指system_image_info.txt文件。调用ImagePropFromGlobalDict()方法获取img的配置参数。 def ImagePropFromGlobalDict(glob_dict, mount_point): """Build an image property dictionary from the global dictionary. Args: glob_dict: the global dictionary from the build system. mount_point: such as "system", "data" etc. """ d = {} if "build.prop" in glob_dict: bp = glob_dict["build.prop"] if "ro.build.date.utc" in bp: d["timestamp"] = bp["ro.build.date.utc"] def copy_prop(src_p, dest_p): if src_p in glob_dict: d[dest_p] = str(glob_dict[src_p]) common_props = ( "extfs_sparse_flag", "squashfs_sparse_flag", "mkyaffs2_extra_flags", "selinux_fc", "skip_fsck", "verity", "verity_key", "verity_signer_cmd", "verity_fec" ) for p in common_props: copy_prop(p, p) d["mount_point"] = mount_point if mount_point == "system": copy_prop("fs_type", "fs_type") # Copy the generic sysetem fs type first, override with specific one if # available. copy_prop("system_fs_type", "fs_type") copy_prop("system_size", "partition_size") copy_prop("system_journal_size", "journal_size") copy_prop("system_verity_block_device", "verity_block_device") copy_prop("system_root_image", "system_root_image") copy_prop("ramdisk_dir", "ramdisk_dir") copy_prop("ramdisk_fs_config", "ramdisk_fs_config") copy_prop("has_ext4_reserved_blocks", "has_ext4_reserved_blocks") copy_prop("system_squashfs_compressor", "squashfs_compressor") copy_prop("system_squashfs_compressor_opt", "squashfs_compressor_opt") copy_prop("system_squashfs_block_size", "squashfs_block_size") copy_prop("system_squashfs_disable_4k_align", "squashfs_disable_4k_align") copy_prop("system_base_fs_file", "base_fs_file") ...... return
得到system.img的参数后通过BuildImage()方法进行打包。
def BuildImage(in_dir, prop_dict, out_file, target_out=None): """Build an image to out_file from in_dir with property prop_dict. Args: in_dir: path of input directory. prop_dict: property dictionary. out_file: path of the output image file. target_out: path of the product out directory to read device specific FS config files. ...... build_command = [] fs_type = prop_dict.get("fs_type", "") if fs_type.startswith("ext"): build_command = ["mkuserimg.sh"] if "extfs_sparse_flag" in prop_dict: build_command.append(prop_dict["extfs_sparse_flag"]) prop_dict["mount_point"]]) build_command.append(prop_dict["partition_size"] ...... else: build_command = ["mkyaffs2image", "-f"] if prop_dict.get("mkyaffs2_extra_flags", None): build_command.extend(prop_dict["mkyaffs2_extra_flags"].split()) build_command.append(in_dir) build_command.append(out_file) try: if reserved_blocks and fs_type.startswith("ext4"): (ext4fs_output, exit_code) = RunCommand(build_command) else: (_, exit_code) = RunCommand(build_command) finally: if in_dir != origin_in: # Clean up temporary directories and files. shutil.rmtree(in_dir, ignore_errors=True) if fs_config: os.remove(fs_config) if base_fs_file is not None: os.remove(base_fs_file) if exit_code != 0: return False
获取在文件形同中的类型,并根据类型配置相应的打包命令
工程项目编译完成后,镜像文件在终端上可以直接通过命令打包(Linux环境中,需要sudo权限):
out/host/linux-x86/bin/make_ext4fs -s -l 300M -a system/ test/system.img system
-s 是指ext4的s模式制作
-l 300M 是指镜像文件的大小
-a 是指镜像文件用于安卓系统
挂在点是/system
将镜像文件解包可以用如下命令:
simg2img A.img A.img.raw sudo mount -t ext4 -o loop A.img.raw A