当前位置: 首页 > 工具软件 > AndroidAcp > 使用案例 >

roc_rk3328_cc硬件平台Android编译系统分析-system.img的生成过程(根据网上资料修订版本)

公西星海
2023-12-01

roc_rk3328_cc硬件平台Android编译系统分析-system.img的生成过程(根据网上资料修订版本)


 

我们在完整编译android系统的时候,最终会生成几个重要的镜像文件,其中有system.img,userdata.img,ramdisk.img等。这篇文章的目的是分析system.img的生成过程。
回想下我们完整编译android系统时的动作,我们会在android源码顶级目录执行make命令,这样就会完整的编译android系统,我们没有传入任何参数(-jx等加快编译的除外),因为我们没有明确指定make的目标,所以android编译系统会执行默认的编译目标,也就是droid。因此,我们还是从droid着手,看看system.img怎么生成。
我们只关注system.img相关的部分,其他部分都忽略,因此会有如下依赖关系:system.img生成依赖
 

编译 Android(未包括编译uboot,编译linux kernel)
source build/envsetup.sh
lunch roc_rk3328_cc_box-userdebug
make installclean
make -j8
./mkimage.sh
 

而可以看出make -j8是在主makefile执行,主makefile(android源码树/Makefile)只有include build/core/main.mk这个一句
进入<build/core/main.mk>

# This is the default target. It must be the first declared target.

.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets
droid便是最终的目标了
<build/core/main.mk>有

# Building a full system-- the default is to build droidcore

droid_targets: droidcore dist_files
<build/core/main.mk>有

# Build files and then package it into the rom formats

.PHONY: droidcore
droidcore: files \
systemimage \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_CACHEIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
$(INSTALLED_FILES_FILE) \
$(INSTALLED_FILES_FILE_VENDOR) \
$(INSTALLED_FILES_FILE_SYSTEMOTHER)


 

<build/core/Makefile>有
一.systemimage
 

# Rules that need to be present for the all targets, even

# if they don't do anything.

.PHONY: systemimage
systemimage:
sytemimage是一个伪目标,它并不会被生成。
<build/core/Makefile>有systemimage: $(INSTALLED_SYSTEMIMAGE)
systemimage依赖于$(INSTALLED_SYSTEMIMAGE)
二.$(INSTALLED_SYSTEMIMAGE)
INSTALLED_SYSTEMIMAGE := $(PRODUCT_OUT)/system.img
INSTALLED_SYSTEMIMAGE变量的值就是system.img了,也就是说它就是我们最终要生成的目标。那么看看它的定义:
<build/core/Makefile>有
$(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))


 

(INSTALLEDSYSTEMIMAGE)有依赖了
(BUILT_SYSTEMIMAGE) 和(RECOVERYFROMBOOTPATCH)以及(ACP),我们目前无法知道这三个变量是什么,当然,这里的(ACP)是一种read−only依赖,也就是说(ACP)发生改变时,编译器并不会重新生成system.img,(ACP)其实代表的是acp可执行文件,这个执行文件由acp.c文件生成,代码在build/tools/acp/目录下。因此,acp是一个生成system.img过程中使用的工具,它的改变不会使system.img立刻重新生成。虽然我们暂且不知道
 

(BUILT_SYSTEMIMAGE) 和$(RECOVERY_FROM_BOOT_PATCH)代表的是什么,(下文可知BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img),但是我们可以先看看system.img的生成规则,看看生成规则是怎么使用这三个依赖来生成system.img镜像文件的的。
 

<build/core/Makefile>有
RECOVERY_FROM_BOOT_PATCH := $(INSTALLED_RECOVERYIMAGE_TARGET)
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img 也就是/out/target/product/roc_rk3328_cc_box/recovery.img

 

2.1$(copy-file-to-target)
copy-file-to-target的定义如下:
<build/core/definitions.mk>

# Copy a single file from one place to another,

# preserving permissions and overwriting any existing

# file.

# We disable the "-t" option for acp cannot handle

# high resolution timestamp correctly on file systems like ext4.

# Therefore copy-file-to-target is the same as copy-file-to-new-target.

define copy-file-to-target
@mkdir -p $(dir $@)
$(hide) $(ACP) -fp $< $@
endef


 

结合注释,这段代码的功能是拷贝文件,并且在拷贝的过程中会保留文件的权限和覆盖已有的文件。$<代表的是第一个依赖,也就是这里的
(BUILT_SYSTEMIMAGE),这里首先会创建/out/target/product/xxx/目录,其中xxx是产品名,然后把(BUILTSYSTEMIMAGE)拷贝到该目录下并命名为system.img。因此,system.img诞生。所以说它的诞生是由
 

(BUILT_SYSTEMIMAGE)变量所代表的文件直接拷贝而来,因此,要搞清system.img的生成过程,必须搞清$(BUILT_SYSTEMIMAGE)的生成过程。
(BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img)
2.2assert-max-image-size
 

紧随其后的assert-max-image-size函数又做了什么呢?调用它的时候传入了两个参数,分别是1.system.img 2.(RECOVERYFROMBOOTPATCH)
 

这个函数对system.img的大小做一个检查,如果system.img太大,超过了flash允许的最大分区的大小,这里就会报错。
因此,assert-max-image-size函数可以理解为检查system.img的合法性。
 

三.$(BUILT_SYSTEMIMAGE)
我们分析system.img的生成规则发现,system.img其实是(BUILTSYSTEMIMAGE)的一份拷贝。那么
(BUILT_SYSTEMIMAGE)又是怎么生成的呢?
(BUILTSYSTEMIMAGE)其实也是一个system.img文件,只不过它在(systemimage_intermediates)目录下:
<build/core/Makefile>
systemimage_intermediates := \
$(call intermediates-dir-for,PACKAGING,systemimage)
BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
(BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img)


 

systemimage_intermediates 被赋值:=为
target/product/xxx/obj/PACKAGING/systemimage_intermediates
也就是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/

# ls out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/

system_image_info.txt system.img

 

$(BUILT_SYSTEMIMAGE)的依赖与生成规则如下:
<build/core/Makefile>
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
$(call build-systemimage-target,$@)

 

我们不知道它依赖的是什么,但是我们可以先看一下它的生成规则:
build-systemimage-target函数定义如下:
<build/core/Makefile>文件有如下

# $(1): output file

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
 


$(1) 就是BUILT_SYSTEMIMAGE,(BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img)
/
 

这个函数做了四件事情:
1.create-system-vendor-symlink
<build/core/Makefile>
define create-system-vendor-symlink
$(hide) if [ -d $(TARGET_OUT)/vendor ] && [ ! -h $(TARGET_OUT)/vendor ]; then \
echo 'Non-symlink $(TARGET_OUT)/vendor detected!' 1>&2; \
echo 'You cannot install files to $(TARGET_OUT)/vendor while building a separate vendor.img!' 1>&2; \
exit 1; \
fi
$(hide) ln -sf /vendor $(TARGET_OUT)/vendor
endef
如果存在vendor目录,就给vendor目录创建一个软连接。
//
<build/core/envsetup.mk目录下>TARGET_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM)且一般没有指定LOCAL_MODULE_PATH 时,模块编译输出默认到TARGET_OUT。本质就是out/target/product/xxx/system/所在,即Android7.1.2/ROC-RK3328-CC_Android7.1.2_git_20171204/out/target/product/roc_rk3328_cc_box/system目录,也就是打包后的system.img所在,把system镜像所在的块分区在init进程中会被挂载到root/system下面( mount ext4 /dev/block/mmcblk1p16 /system)。root根目录下部分文件链接到system内部,如etc等
///
 

2.创建target/product/xxx/obj/PACKAGING/systemimage_intermediates目录并删除这个目录下的system_image_info.txt文件。
3.重新向system_image.info.txt中写入数据
 

# $(1): the path of the output dictionary file

# $(2): additional "key=value" pairs to append to the dictionary file.

define generate-userimage-prop-dictionary
$(if $(INTERNAL_USERIMAGES_EXT_VARIANT),$(hide) echo "fs_type=$(INTERNAL_USERIMAGES_EXT_VARIANT)" >> $(1))
$(if $(BOARD_SYSTEMIMAGE_PARTITION_SIZE),$(hide) echo "system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)" >> $(1))
$(if $(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "system_fs_type=$(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
$(if $(BOARD_SYSTEMIMAGE_JOURNAL_SIZE),$(hide) echo "system_journal_size=$(BOARD_SYSTEMIMAGE_JOURNAL_SIZE)" >> $(1))
$(if $(BOARD_HAS_EXT4_RESERVED_BLOCKS),$(hide) echo "has_ext4_reserved_blocks=$(BOARD_HAS_EXT4_RESERVED_BLOCKS)" >> $(1))
$(if $(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "system_squashfs_compressor=$(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
$(if $(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "system_squashfs_compressor_opt=$(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
$(if $(BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "userdata_fs_type=$(BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
$(if $(BOARD_USERDATAIMAGE_PARTITION_SIZE),$(hide) echo "userdata_size=$(BOARD_USERDATAIMAGE_PARTITION_SIZE)" >> $(1))
$(if $(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "cache_fs_type=$(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
$(if $(BOARD_CACHEIMAGE_PARTITION_SIZE),$(hide) echo "cache_size=$(BOARD_CACHEIMAGE_PARTITION_SIZE)" >> $(1))
$(if $(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "vendor_fs_type=$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
$(if $(BOARD_VENDORIMAGE_PARTITION_SIZE),$(hide) echo "vendor_size=$(BOARD_VENDORIMAGE_PARTITION_SIZE)" >> $(1))
$(if $(BOARD_VENDORIMAGE_JOURNAL_SIZE),$(hide) echo "vendor_journal_size=$(BOARD_VENDORIMAGE_JOURNAL_SIZE)" >> $(1))
$(if $(BOARD_OEMIMAGE_PARTITION_SIZE),$(hide) echo "oem_size=$(BOARD_OEMIMAGE_PARTITION_SIZE)" >> $(1))
$(if $(BOARD_OEMIMAGE_JOURNAL_SIZE),$(hide) echo "oem_journal_size=$(BOARD_OEMIMAGE_JOURNAL_SIZE)" >> $(1))
$(if $(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG),$(hide) echo "extfs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG)" >> $(1))
$(hide) echo "selinux_fc=$(SELINUX_FC)" >> $(1)
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER),$(hide) echo "boot_signer=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity_key=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),$(hide) echo "verity_signer_cmd=$(VERITY_SIGNER)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION),$(hide) echo "system_verity_block_device=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION),$(hide) echo "vendor_verity_block_device=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot_key=$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_KEY)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),$(hide) echo "futility=$(FUTILITY)" >> $(1))
$(if $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot_signer_cmd=$(VBOOT_SIGNER)" >> $(1))
$(if $(filter true,$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)),\
$(hide) echo "system_root_image=true" >> $(1);\
echo "ramdisk_dir=$(TARGET_ROOT_OUT)" >> $(1))
$(if $(2),$(hide) $(foreach kv,$(2),echo "$(kv)" >> $(1);))
endef


 

4.使用build_image.py脚本生成system.img镜像文件。
四.$(FULL_SYSTEMIMAGE_DEPS)
 

FULL_SYSTEMIMAGE_DEPS又有以下两部分组成:
<build/core/Makefile>
FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)

 

1.$(INTERNAL_SYSTEMIMAGE_FILES)
 

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子目录下那些文件。
<build/core/envsetup.mk目录下>TARGET_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM) 本质就是out/target/product/xxx/system/所在,即Android7.1.2/ROC-RK3328-CC_Android7.1.2_git_20171204/out/target/product/roc_rk3328_cc_box/system目录
 

ALL_PREBUILT:要拷贝到目标设备上去的文件。
ALL_COPIED_HEADERS:要拷贝到目标设备上去的头文件。
ALL_GENERATED_SOURCES:要拷贝到目标设备上去的由工具自动生成的源代码文件。
ALL_DEFAULT_INSTALLED_MODULES:要安装要目标设备上的所有的模块文件。
PDK_FUSION_SYSIMG_FILES是从PDK(Platform Development Kit)提取出来的相关文件。
RECOVERY_RESOURCE_ZIP描述的是Android的recovery系统要使用的资源文件,对应于/system/etc目录下的recovery-resource.dat文件。
 

2.$(INTERNAL_USERIMAGES_DEPS)
ifeq ($(INTERNAL_USERIMAGES_USE_EXT),true)
INTERNAL_USERIMAGES_DEPS := $(SIMG2IMG)
INTERNAL_USERIMAGES_DEPS += $(MKEXTUSERIMG) $(MAKE_EXT4FS) $(E2FSCK)
ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
INTERNAL_USERIMAGES_DEPS += $(MKF2FSUSERIMG) $(MAKE_F2FS)
endif
endif
 

ifeq ($(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),squashfs)
INTERNAL_USERIMAGES_DEPS += $(MAKE_SQUASHFS) $(MKSQUASHFSUSERIMG) $(IMG2SIMG)
endif
 

ifeq ($(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),squashfs)
INTERNAL_USERIMAGES_DEPS += $(MAKE_SQUASHFS) $(MKSQUASHFSUSERIMG) $(IMG2SIMG)
endif
 

INTERNAL_USERIMAGES_BINARY_PATHS := $(sort $(dir $(INTERNAL_USERIMAGES_DEPS)))
 

ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY))
INTERNAL_USERIMAGES_DEPS += $(BUILD_VERITY_TREE) $(APPEND2SIMG) $(VERITY_SIGNER)
endif
 

SELINUX_FC := $(TARGET_ROOT_OUT)/file_contexts
INTERNAL_USERIMAGES_DEPS += $(SELINUX_FC)


 

从以上可以看出INTERNAL_USERIMAGES_DEPS描述的是制作system.img镜像所依赖的工具。例如,如果要制作的system.img使用的是yaffs2文件系统,那么对应工具就是mkyaffs2image。
总结:也就是四小节提供镜像打包工具和所有需要的文件,这些文件在之前的编译中已经生成好了,然后交由三小节的build-systemimage-target函数使用build_image.py生成system.img镜像文件,这个镜像文件在target/product/xxx/obj/PACKAGING/systemimage_intermediates目录下,之后再由二小节中的拷贝函数将其拷贝到target/product/xxx目录下,xxx是产品名。
 

最终编译,从编译输出的信息可看到
make_ext4fs -s -T -1 -S out/target/product/roc_rk3328_cc_box/root/file_contexts.bin -L system -l 1208860800 -a system out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img out/target/product/roc_rk3328_cc_box/system out/target/product/roc_rk3328_cc_box/system
 

<device/rockchip/common/mkimage.sh> 有
MAKE_EXT4FS_ARGS=" -L system -S $OUT/root/file_contexts -a system $IMAGE_PATH/system.img $OUT/system"
ok=0
while [ "$ok" = "0" ]; do
make_ext4fs -l $system_size $MAKE_EXT4FS_ARGS >/dev/null 2>&1 &&
tune2fs -c -1 -i 0 $IMAGE_PATH/system.img >/dev/null 2>&1 &&
ok=1 || system_size=$(($system_size + 5242880))
done




 

参考原文(有修改改动):https://blog.csdn.net/u011913612/article/details/52503318
 

roc_rk3328_cc硬件平台Android编译系统分析-system.img的生成过程(根据网上资料修订版本)

我们在完整编译android系统的时候,最终会生成几个重要的镜像文件,其中有system.img,userdata.img,ramdisk.img等。这篇文章的目的是分析system.img的生成过程。
回想下我们完整编译android系统时的动作,我们会在android源码顶级目录执行make命令,这样就会完整的编译android系统,我们没有传入任何参数(-jx等加快编译的除外),因为我们没有明确指定make的目标,所以android编译系统会执行默认的编译目标,也就是droid。因此,我们还是从droid着手,看看system.img怎么生成。
我们只关注system.img相关的部分,其他部分都忽略,因此会有如下依赖关系:system.img生成依赖

编译 Android(未包括编译uboot,编译linux kernel)
source build/envsetup.sh
lunch roc_rk3328_cc_box-userdebug
make installclean
make -j8
./mkimage.sh

而可以看出make -j8是在主makefile执行,主makefile(android源码树/Makefile)只有include build/core/main.mk这个一句
进入<build/core/main.mk>

This is the default target. It must be the first declared target.

.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets
droid便是最终的目标了
<build/core/main.mk>有

Building a full system-- the default is to build droidcore

droid_targets: droidcore dist_files
<build/core/main.mk>有

Build files and then package it into the rom formats

.PHONY: droidcore
droidcore: files
systemimage
$(INSTALLED_BOOTIMAGE_TARGET)
$(INSTALLED_RECOVERYIMAGE_TARGET)
$(INSTALLED_USERDATAIMAGE_TARGET)
$(INSTALLED_CACHEIMAGE_TARGET)
$(INSTALLED_VENDORIMAGE_TARGET)
$(INSTALLED_SYSTEMOTHERIMAGE_TARGET)
$(INSTALLED_FILES_FILE)
$(INSTALLED_FILES_FILE_VENDOR)
$(INSTALLED_FILES_FILE_SYSTEMOTHER)

<build/core/Makefile>有
一.systemimage

Rules that need to be present for the all targets, even

if they don’t do anything.

.PHONY: systemimage
systemimage:
sytemimage是一个伪目标,它并不会被生成。
<build/core/Makefile>有systemimage: (INSTALLED_SYSTEMIMAGE) systemimage依赖于(INSTALLEDS​YSTEMIMAGE)systemimage依赖于(INSTALLED_SYSTEMIMAGE)
二.$(INSTALLED_SYSTEMIMAGE)
INSTALLED_SYSTEMIMAGE := $(PRODUCT_OUT)/system.img
INSTALLED_SYSTEMIMAGE变量的值就是system.img了,也就是说它就是我们最终要生成的目标。那么看看它的定义:
<build/core/Makefile>有
$(INSTALLED_SYSTEMIMAGE): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH) | $(ACP)
@echo “Install system fs image: $@”
$(copy-file-to-target)
$(hide) (call assert-max-image-size,(callassert−max−image−size,@ (RECOVERY_FROM_BOOT_PATCH),(RECOVERYF​ROMB​OOTP​ATCH),(BOARD_SYSTEMIMAGE_PARTITION_SIZE))

(INSTALLEDSYSTEMIMAGE)有依赖了
(BUILT_SYSTEMIMAGE) 和(RECOVERYFROMBOOTPATCH)以及(ACP),我们目前无法知道这三个变量是什么,当然,这里的(ACP)是一种read−only依赖,也就是说(ACP)发生改变时,编译器并不会重新生成system.img,(ACP)其实代表的是acp可执行文件,这个执行文件由acp.c文件生成,代码在build/tools/acp/目录下。因此,acp是一个生成system.img过程中使用的工具,它的改变不会使system.img立刻重新生成。虽然我们暂且不知道

(BUILT_SYSTEMIMAGE) 和$(RECOVERY_FROM_BOOT_PATCH)代表的是什么,(下文可知BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img),但是我们可以先看看system.img的生成规则,看看生成规则是怎么使用这三个依赖来生成system.img镜像文件的的。

<build/core/Makefile>有
RECOVERY_FROM_BOOT_PATCH := $(INSTALLED_RECOVERYIMAGE_TARGET)
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img 也就是/out/target/product/roc_rk3328_cc_box/recovery.img

2.1$(copy-file-to-target)
copy-file-to-target的定义如下:
<build/core/definitions.mk>

Copy a single file from one place to another,

preserving permissions and overwriting any existing

file.

We disable the “-t” option for acp cannot handle

high resolution timestamp correctly on file systems like ext4.

Therefore copy-file-to-target is the same as copy-file-to-new-target.

define copy-file-to-target
@mkdir -p $(dir $@)
$(hide) $(ACP) -fp $< $@
endef

结合注释,这段代码的功能是拷贝文件,并且在拷贝的过程中会保留文件的权限和覆盖已有的文件。$<代表的是第一个依赖,也就是这里的
(BUILT_SYSTEMIMAGE),这里首先会创建/out/target/product/xxx/目录,其中xxx是产品名,然后把(BUILTSYSTEMIMAGE)拷贝到该目录下并命名为system.img。因此,system.img诞生。所以说它的诞生是由

(BUILT_SYSTEMIMAGE)变量所代表的文件直接拷贝而来,因此,要搞清system.img的生成过程,必须搞清$(BUILT_SYSTEMIMAGE)的生成过程。
(BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img)
2.2assert-max-image-size

紧随其后的assert-max-image-size函数又做了什么呢?调用它的时候传入了两个参数,分别是1.system.img 2.(RECOVERYFROMBOOTPATCH)

这个函数对system.img的大小做一个检查,如果system.img太大,超过了flash允许的最大分区的大小,这里就会报错。
因此,assert-max-image-size函数可以理解为检查system.img的合法性。

三.$(BUILT_SYSTEMIMAGE)
我们分析system.img的生成规则发现,system.img其实是(BUILTSYSTEMIMAGE)的一份拷贝。那么
(BUILT_SYSTEMIMAGE)又是怎么生成的呢?
(BUILTSYSTEMIMAGE)其实也是一个system.img文件,只不过它在(systemimage_intermediates)目录下:
<build/core/Makefile>
systemimage_intermediates :=
$(call intermediates-dir-for,PACKAGING,systemimage)
BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
(BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img)

systemimage_intermediates 被赋值:=为
target/product/xxx/obj/PACKAGING/systemimage_intermediates
也就是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/

ls out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/

system_image_info.txt system.img

$(BUILT_SYSTEMIMAGE)的依赖与生成规则如下:
<build/core/Makefile>
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
(call build-systemimage-target,(callbuild−systemimage−target,@)

我们不知道它依赖的是什么,但是我们可以先看一下它的生成规则:
build-systemimage-target函数定义如下:
<build/core/Makefile>文件有如下

$(1): output file

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=(hide)PATH=(foreach p,(INTERNAL_USERIMAGES_BINARY_PATHS),(INTERNALU​SERIMAGESB​INARYP​ATHS),§KaTeX parse error: Undefined control sequence: \ at position 6: PATH \̲ ̲ ./build/t…((maxsize - 4096 * 4096));
fi;
echo “The max is KaTeX parse error: Expected 'EOF', got '&' at position 32: …8576 )) MB." 1>&̲2 ;\ …(( $(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


$(1) 就是BUILT_SYSTEMIMAGE,(BUILT_SYSTEMIMAGE就是target/product/xxx/obj/PACKAGING/systemimage_intermediates/system.img 或者说是out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img)
/

这个函数做了四件事情:
1.create-system-vendor-symlink
<build/core/Makefile>
define create-system-vendor-symlink
$(hide) if [ -d $(TARGET_OUT)/vendor ] && [ ! -h $(TARGET_OUT)/vendor ]; then
echo ‘Non-symlink $(TARGET_OUT)/vendor detected!’ 1>&2;
echo ‘You cannot install files to $(TARGET_OUT)/vendor while building a separate vendor.img!’ 1>&2;
exit 1;
fi
$(hide) ln -sf /vendor $(TARGET_OUT)/vendor
endef
如果存在vendor目录,就给vendor目录创建一个软连接。
//
<build/core/envsetup.mk目录下>TARGET_OUT := (PRODUCT_OUT)/(PRODUCTO​UT)/(TARGET_COPY_OUT_SYSTEM)且一般没有指定LOCAL_MODULE_PATH 时,模块编译输出默认到TARGET_OUT。本质就是out/target/product/xxx/system/所在,即Android7.1.2/ROC-RK3328-CC_Android7.1.2_git_20171204/out/target/product/roc_rk3328_cc_box/system目录,也就是打包后的system.img所在,把system镜像所在的块分区在init进程中会被挂载到root/system下面( mount ext4 /dev/block/mmcblk1p16 /system)。root根目录下部分文件链接到system内部,如etc等
///

2.创建target/product/xxx/obj/PACKAGING/systemimage_intermediates目录并删除这个目录下的system_image_info.txt文件。
3.重新向system_image.info.txt中写入数据

$(1): the path of the output dictionary file

$(2): additional “key=value” pairs to append to the dictionary file.

define generate-userimage-prop-dictionary
$(if (INTERNAL_USERIMAGES_EXT_VARIANT),(INTERNALU​SERIMAGESE​XTV​ARIANT),(hide) echo “fs_type=$(INTERNAL_USERIMAGES_EXT_VARIANT)” >> $(1))
$(if (BOARD_SYSTEMIMAGE_PARTITION_SIZE),(BOARDS​YSTEMIMAGEP​ARTITIONS​IZE),(hide) echo “system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)” >> $(1))
$(if (BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),(BOARDS​YSTEMIMAGEF​ILES​YSTEMT​YPE),(hide) echo “system_fs_type=$(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE)” >> $(1))
$(if (BOARD_SYSTEMIMAGE_JOURNAL_SIZE),(BOARDS​YSTEMIMAGEJ​OURNALS​IZE),(hide) echo “system_journal_size=$(BOARD_SYSTEMIMAGE_JOURNAL_SIZE)” >> $(1))
$(if (BOARD_HAS_EXT4_RESERVED_BLOCKS),(BOARDH​ASE​XT4R​ESERVEDB​LOCKS),(hide) echo “has_ext4_reserved_blocks=$(BOARD_HAS_EXT4_RESERVED_BLOCKS)” >> $(1))
$(if (BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR),(BOARDS​YSTEMIMAGES​QUASHFSC​OMPRESSOR),(hide) echo “system_squashfs_compressor=$(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR)” >> $(1))
$(if (BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR_OPT),(BOARDS​YSTEMIMAGES​QUASHFSC​OMPRESSORO​PT),(hide) echo “system_squashfs_compressor_opt=$(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR_OPT)” >> $(1))
$(if (BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE),(BOARDU​SERDATAIMAGEF​ILES​YSTEMT​YPE),(hide) echo “userdata_fs_type=$(BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE)” >> $(1))
$(if (BOARD_USERDATAIMAGE_PARTITION_SIZE),(BOARDU​SERDATAIMAGEP​ARTITIONS​IZE),(hide) echo “userdata_size=$(BOARD_USERDATAIMAGE_PARTITION_SIZE)” >> $(1))
$(if (BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE),(BOARDC​ACHEIMAGEF​ILES​YSTEMT​YPE),(hide) echo “cache_fs_type=$(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE)” >> $(1))
$(if (BOARD_CACHEIMAGE_PARTITION_SIZE),(BOARDC​ACHEIMAGEP​ARTITIONS​IZE),(hide) echo “cache_size=$(BOARD_CACHEIMAGE_PARTITION_SIZE)” >> $(1))
$(if (BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),(BOARDV​ENDORIMAGEF​ILES​YSTEMT​YPE),(hide) echo “vendor_fs_type=$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE)” >> $(1))
$(if (BOARD_VENDORIMAGE_PARTITION_SIZE),(BOARDV​ENDORIMAGEP​ARTITIONS​IZE),(hide) echo “vendor_size=$(BOARD_VENDORIMAGE_PARTITION_SIZE)” >> $(1))
$(if (BOARD_VENDORIMAGE_JOURNAL_SIZE),(BOARDV​ENDORIMAGEJ​OURNALS​IZE),(hide) echo “vendor_journal_size=$(BOARD_VENDORIMAGE_JOURNAL_SIZE)” >> $(1))
$(if (BOARD_OEMIMAGE_PARTITION_SIZE),(BOARDO​EMIMAGEP​ARTITIONS​IZE),(hide) echo “oem_size=$(BOARD_OEMIMAGE_PARTITION_SIZE)” >> $(1))
$(if (BOARD_OEMIMAGE_JOURNAL_SIZE),(BOARDO​EMIMAGEJ​OURNALS​IZE),(hide) echo “oem_journal_size=$(BOARD_OEMIMAGE_JOURNAL_SIZE)” >> $(1))
$(if (INTERNAL_USERIMAGES_SPARSE_EXT_FLAG),(INTERNALU​SERIMAGESS​PARSEE​XTF​LAG),(hide) echo “extfs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG)” >> $(1))
(hide) echo "selinux_fc=(hide)echo"selinuxf​c=(SELINUX_FC)" >> $(1)
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER),(hide) echo "boot_signer=(hide)echo"boots​igner=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),(hide) echo "verity=(hide)echo"verity=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),(hide) echo "verity_key=(hide)echo"verityk​ey=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),(hide) echo "verity_signer_cmd=(hide)echo"veritys​ignerc​md=(VERITY_SIGNER)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION),(hide) echo "system_verity_block_device=(hide)echo"systemv​erityb​lockd​evice=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SYSTEM_VERITY_PARTITION)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION),(hide) echo "vendor_verity_block_device=(hide)echo"vendorv​erityb​lockd​evice=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VENDOR_VERITY_PARTITION)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),(hide) echo "vboot=(hide)echo"vboot=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),(hide) echo "vboot_key=(hide)echo"vbootk​ey=(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_KEY)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),(hide) echo "futility=(hide)echo"futility=(FUTILITY)" >> $(1))
$(if (PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT),(hide) echo "vboot_signer_cmd=(hide)echo"vboots​ignerc​md=(VBOOT_SIGNER)" >> $(1))
$(if (filter true,(filtertrue,(BOARD_BUILD_SYSTEM_ROOT_IMAGE)),
$(hide) echo “system_root_image=true” >> KaTeX parse error: Undefined control sequence: \ at position 5: (1);\̲ ̲ echo "ramdi…(TARGET_ROOT_OUT)" >> $(1))
$(if (2),(2),(hide) (foreach kv,(foreachkv,(2),echo “$(kv)” >> $(1))
endef

4.使用build_image.py脚本生成system.img镜像文件。
四.$(FULL_SYSTEMIMAGE_DEPS)

FULL_SYSTEMIMAGE_DEPS又有以下两部分组成:
<build/core/Makefile>
FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)

1.$(INTERNAL_SYSTEMIMAGE_FILES)

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子目录下那些文件。
<build/core/envsetup.mk目录下>TARGET_OUT := (PRODUCT_OUT)/(PRODUCTO​UT)/(TARGET_COPY_OUT_SYSTEM) 本质就是out/target/product/xxx/system/所在,即Android7.1.2/ROC-RK3328-CC_Android7.1.2_git_20171204/out/target/product/roc_rk3328_cc_box/system目录

ALL_PREBUILT:要拷贝到目标设备上去的文件。
ALL_COPIED_HEADERS:要拷贝到目标设备上去的头文件。
ALL_GENERATED_SOURCES:要拷贝到目标设备上去的由工具自动生成的源代码文件。
ALL_DEFAULT_INSTALLED_MODULES:要安装要目标设备上的所有的模块文件。
PDK_FUSION_SYSIMG_FILES是从PDK(Platform Development Kit)提取出来的相关文件。
RECOVERY_RESOURCE_ZIP描述的是Android的recovery系统要使用的资源文件,对应于/system/etc目录下的recovery-resource.dat文件。

2.(INTERNAL_USERIMAGES_DEPS) ifeq ((INTERNALU​SERIMAGESD​EPS)ifeq((INTERNAL_USERIMAGES_USE_EXT),true)
INTERNAL_USERIMAGES_DEPS := $(SIMG2IMG)
INTERNAL_USERIMAGES_DEPS += $(MKEXTUSERIMG) $(MAKE_EXT4FS) (E2FSCK) ifeq ((E2FSCK)ifeq((TARGET_USERIMAGES_USE_F2FS),true)
INTERNAL_USERIMAGES_DEPS += $(MKF2FSUSERIMG) $(MAKE_F2FS)
endif
endif

ifeq ($(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),squashfs)
INTERNAL_USERIMAGES_DEPS += $(MAKE_SQUASHFS) $(MKSQUASHFSUSERIMG) $(IMG2SIMG)
endif

ifeq ($(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),squashfs)
INTERNAL_USERIMAGES_DEPS += $(MAKE_SQUASHFS) $(MKSQUASHFSUSERIMG) $(IMG2SIMG)
endif

INTERNAL_USERIMAGES_BINARY_PATHS := $(sort $(dir $(INTERNAL_USERIMAGES_DEPS)))

ifeq (true,(PRODUCTS.(PRODUCTS.(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY))
INTERNAL_USERIMAGES_DEPS += $(BUILD_VERITY_TREE) $(APPEND2SIMG) $(VERITY_SIGNER)
endif

SELINUX_FC := $(TARGET_ROOT_OUT)/file_contexts
INTERNAL_USERIMAGES_DEPS += $(SELINUX_FC)

从以上可以看出INTERNAL_USERIMAGES_DEPS描述的是制作system.img镜像所依赖的工具。例如,如果要制作的system.img使用的是yaffs2文件系统,那么对应工具就是mkyaffs2image。
总结:也就是四小节提供镜像打包工具和所有需要的文件,这些文件在之前的编译中已经生成好了,然后交由三小节的build-systemimage-target函数使用build_image.py生成system.img镜像文件,这个镜像文件在target/product/xxx/obj/PACKAGING/systemimage_intermediates目录下,之后再由二小节中的拷贝函数将其拷贝到target/product/xxx目录下,xxx是产品名。

最终编译,从编译输出的信息可看到
make_ext4fs -s -T -1 -S out/target/product/roc_rk3328_cc_box/root/file_contexts.bin -L system -l 1208860800 -a system out/target/product/roc_rk3328_cc_box/obj/PACKAGING/systemimage_intermediates/system.img out/target/product/roc_rk3328_cc_box/system out/target/product/roc_rk3328_cc_box/system

<device/rockchip/common/mkimage.sh> 有
MAKE_EXT4FS_ARGS=" -L system -S $OUT/root/file_contexts -a system $IMAGE_PATH/system.img OUT/system" ok=0 while [ "OUT/system"ok=0while["ok" = “0” ]; do
make_ext4fs -l $system_size $MAKE_EXT4FS_ARGS >/dev/null 2>&1 &&
tune2fs -c -1 -i 0 KaTeX parse error: Expected 'EOF', got '&' at position 36: …g >/dev/null 2>&̲1 && …(($system_size + 5242880))
done

参考原文(有修改改动):https://blog.csdn.net/u011913612/article/details/52503318

Markdown 13759 字数 298 行数 当前行 1, 当前列 0

HTML 15860 字数 228 段落

 类似资料: