Android 框架实现分析 - 构建 - releasetools

build_image.py

build/tools/releasetools/build_image.py:

-------------------------------------------------------------------------

  in_dir = argv[0]

  glob_dict_file = argv[1]

  out_file = argv[2]

mkuserimg.sh [-s] in_dir, out_file, fs_type mount_point partition_size selinux_fc

mkyaffs2image –f [-c $(BOARD_NAND_PAGE_SIZE)] [-s $(BOARD_NAND_SPARE_SIZE)] in_dir out_file selinux_fc mount_point

common.py

全局函数

  1. Run(args, **kwargs)

启动进程,返回subprocess.Popen对象。

  1. LoadInfoDict(zip)

从META/misc_info.txt读取META字典,并返回字典对象(包括”fstab”和"build.prop")。

  1. LoadBuildProp(zip)

从SYSTEM/build.prop读取构建属性字典,并返回字典对象。

  1. LoadRecoveryFSTab(zip, fstab_version)

读取并解析RECOVERY/RAMDISK/etc/recovery.fstab,返回分区表字典。

  1. BuildBootableImage(sourcedir, fs_config_file, info_dict=None)

通过mkbootfs、minigzip、mkbootimg 工具将目录打包成bootable image。

  1. GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir, info_dict=None)

获取启动image,boot或者recovery,先检查BOOTABLE_IMAGES/prebuilt_name,如果没有,则通过tree_subdir调用BuildBootableImage构建一个。

  1. UnzipTemp(filename, pattern=None)

解压zip文件到临时目录,返回临时目录名和zipfile.ZipFile对象。

  1. GetKeyPasswords(keylist)
  2. SignFile(input_name, output_name, key, password, align=None, whole_file=False)

通过signapk命令签名zip文件,如果align,通过zipalign命令对齐zip中的文件。

  1. CheckSize(data, target, info_dict)
  2. ReadApkCerts(tf_zip)
  3. ComputeDifferences(diffs)
  4. GetTypeAndDevice(mount_point, info)

返回分区类型和设备路径,info中包含分区表(info["fstab"])。

  1. ParseCertificate(data)

 class DeviceSpecificParams

处理升级包制作干预机制。

class File

  1. <data>
    1. name       文件名(完整路径)
    2. data       文件内容
    3. size           文件大小
    4. sha1           文件内容的hash值
  2. FromLocalFile(cls, name, diskname)

从本地文件读取内容构建一个File对象。

  1. WriteToTemp(self)

将文件内容写入临时文件中,返回NamedTemporaryFile对象。

  1. AddToZip(self, z)

将文件名,文件内容写入zip归档中。

class Difference

  1. ComputePatch(self)

计算源文件、目标文件的差异,根据扩展名选择不同的diff工具,如imgdiff、bsdiff。*.gz,*.zip,*.apk,*.jar,*.img使用imgdiff。

  1. GetPatch(self)

img_from_target_files.py

        该脚本用来制作用于fastboot update的升级包。

ota-from-target-files.py

        该脚本用来制作升级包。

        根据输入参数情形,制作的升级包有“完整包”和“差量包”两种。

        制作过程的输入文件是target-files结构的zip文件,输出文件是ota-package结构的zip文件。

全局函数

  • IsSymlink
  • IsRegular
  • ClosestFileMatch(src, tgtfiles, existing)

        寻找最大相关性文件,匹配的算法是:完整路径一样 > SHA1一样 > 文件名一样,后两种情况需要升级程序支持重命名函数(update_rename_support),并且源文件只能使用一次(不在existing中)。

  • CopySystemFiles(input_zip, output_zip=None, substitute=None)

        从源zip文件拷贝SYSTEM/*文件到目标zip文件system/*,跳过符号链接, substitute控制覆盖一些文件的内容。该接口返回所有符号链接(内容和名称)。

  • SignOutput(temp_zip_name, output_zip_name)

        用package_key签名整个zip包。

        package_key默认是build/target/product/security/testkey。

  • MakeRecoveryPatch(input_tmp, output_zip, recovery_img, boot_img)

        通过imgdiff计算recovery_img, boot_img的差异,输出到recovery/recovery-from-boot.p,同时生成升级shell脚本recovery/etc/install-recovery.sh。

  • WriteFullOTAPackage(input_zip, output_zip)

        生成完整升级包。

  • WriteIncrementalOTAPackage(target_zip, source_zip, output_zip)

        生成增量升级包。

  • WritePolicyConfig(file_context, output_zip)
  • WriteMetadata(metadata, output_zip)
  • LoadSystemFiles(z)

        读取zip文件中的SYSTEM目录下文件,返回字典:name->File对象。

  • GetBuildProp(prop, info_dict)

 class Item

  • <data>
    • name
    • uid
    • gid
    • mode
    • selabel
    • capabilities
    • dir
  • GetMetadata(cls, input_zip)

        从Metadata("META/filesystem_config.txt")中读取每个item的属性。

  • CountChildMetadata(self)

        根据属性分类统计当前节点和所有子节点的个数,输出一个字典:{(uid, gid, dmode, fmode, selabel, capabilities): count}。同时计算最大分类,对应属性记录在best_subtree中。

  • SetPermissions(self, script)

        追加“权限修改指令”到升级脚本(由script对象维护)中。

 主流程(main

  1. 解析输入参数(common.ParseOptions)
  2. 解压输入zip文件(input_zip,common.UnzipTemp)
  3. 读入META信息字典(common.LoadInfoDict)
    1. META/misc_info.txt
    2. 其他可选的META目录下的文件:mkyaffs2-extra-flags.txt、recovery-api-version.txt、tool-extensions.txt、imagesizes.txt
    3. 读入recovery分区表(LoadRecoveryFSTab,从RECOVERY/RAMDISK/etc/ recovery.fstab)
    4. 读入build属性(LoadBuildProp,从SYSTEM/build.prop)
  4. 如果META字典中有selinux_fc,selinux_fc=“BOOT/RAMDISK/file_contexts”
  5. 创建输出zip文件(output_zip,zipfile.ZipFile),如果需要签名,使用临时文件中转
  6. 根据是否有增量源OPTIONS.incremental_source(-i参数指定)
    1. 写完整升级包(WriteFullOTAPackage)
    2. 创建增量源文件(source_zip),写增量升级包(WriteIncrementalOTAPackage)
  7. 如果需要,进行签名(SignOutput)

完整升级包(WriteFullOTAPackage

  1. 创建升级脚本生成器edify_generator.EdifyGenerator
  2. 定义metadata:{post-build: ro.build.fingerprint, pre-device: ro.product.device, post-timestamp: ro.build.date.utc}
  3. 创建升级包制作干预common.DeviceSpecificParams对象
  4. 【升级脚本】检查时间(没有--omit_prereq)、版本一致性
  5. 【升级脚本】格式化/data分区(如果wipe_user_data)
  6. 如果META字典中有selinux_fc,写入selinux_fc(文件路径)到zip文件(WritePolicyConfig)
  7. 【升级脚本】格式化并挂载/system分区
  8. 【升级脚本】解压recovery、system目录到/system
  9. 从输入zip拷贝文件到输出zip
  10. 【升级脚本】创建符号链接
  11. 获取boot.img、recovery.img(common.GetBootableImage)
  12. 生成recovery image相对boot image增量patch,以及recovery升级脚本
  13. 解析文件属性定义(Item.GetMetadata)
  14. 【升级脚本】修改system目录下文件属性
  15. 写入boot.img到输出zip
  16. 【升级脚本】解压boot.img到boot分区
  17. 【升级脚本】卸载分区
  18. 将升级脚本写入到输出zip(script.AddToZip,META-INF/com/google/android/updater-script)
  19. 将metadata写入到输出zip(WriteMetadata,META-INF/com/android/metadata)

增量升级包(WriteIncrementalOTAPackage

  1. 创建升级脚本生成器edify_generator.EdifyGenerator
  2. 定义metadata:{post-build: ro.build.fingerprint, pre-device: ro.product.device, post-timestamp: ro.build.date.utc}
  3. 创建升级包制作干预common.DeviceSpecificParams对象
  4. 加载源、目标zip文件中的SYSTEM目录文件(LoadSystemFiles)
  5. 构建源、目标文件对应关系(算法在ClosestFileMatch中),分成“重命名组”、“覆盖组(包括源文件不存在的文件)”,“差异组”,“覆盖组”文件写入到输出zip文件
  6. 计算所有“差异组”文件的差别
  7. 写入目标文件(如果差量反而更大移动到“覆盖组”)或者增量文件到输出zip文件
  8. 【升级脚本】挂载/system分区
  9. 【升级脚本】检查Fingerprint是否一致
  10. 从源zip、目标zip中分别获取boot.img、recovery.img(common.GetBootableImage)
  11. 【升级脚本】校验所有源文件的SHA1(script.PatchCheck)
  12. 【升级脚本】如果boot有更新,校验boot的SHA1
  13. 【升级脚本】检验cache剩余空间是否足够(至少largest_source_size)
  14. 【升级脚本】删除“覆盖组”文件(包括被直接替换的文件)
  15. 【升级脚本】应用patch(“差异组”,但是system/build.prop延后)
  16. 【升级脚本】如果boot有更新,应用boot patch升级
  17. 如果recovery有更新,生成recovery image相对boot image增量patch,以及recovery升级脚本
  18. 【升级脚本】如果recovery有更新,删除老的recovery增量patch和升级脚本
  19. 提取源、目标zip中的所有符号链接
  20. 解析文件属性定义(Item.GetMetadata)
  21. 【临时升级脚本】修改system目录下文件属性
  22. 【升级脚本】删除不再需要的符号链接
  23. 【升级脚本】解压system目录到/system
  24. 【升级脚本】如果recovery有更新,解压recovery目录到/system
  25. 【升级脚本】重命名绑定的那些文件
  26. 【升级脚本】删除并重建目标符号链接
  27. 【升级脚本】增加【临时升级脚本】
  28. 【升级脚本】对一些延后处理的文件,应用patch
  29. 【升级脚本】设置/system/build.prop的属性
  30. 【升级脚本】卸载分区
  31. 将升级脚本写入到输出zip(script.AddToZip,META-INF/com/google/android/updater-script)
  32. 将metadata写入到输出zip(WriteMetadata,META-INF/com/android/metadata)

参考:

设计细节

  • 文件权限

升级命令set_perm_recursive能够递归设置目录下的所有文件权限,利用它能够提高效率(至少减少了升级脚本的行数)。这里的思想是寻找目录下最常见的权限设置,先通过set_perm_recursive设置,然后处理例外的情形。

在遍历目录时不处理符号链接,其权限都是root、root、777。

在增量升级的时候,权限设置会全部重新做一遍。

  • 增量算法
  1. 构建文件字典(路径->File):source_data、target_data
  2. 对source_data的文件,
    1. 构建path:matching_file_cache
    2. 如果目标文件集中没有路径相同文件,增加file、sha1映射
  3. 对每个target_data中的文件
    1. 在matching_file_cache寻找匹配文件,
      1. 如果找到path匹配,返回之
      2. 如果update_rename_support为False,返回None
      3. 如果文件小于1K,返回None
      4. 如果sha1匹配,并且没有绑定,返回之
      5. 如果file(文件名)匹配,并且没有重命名绑定,返回之
      6. 返回None
    2. 如果找到匹配,但是路径不一样,加入到重命名组renames(字典:源文件路径->目标File),增加重命名绑定
    3. 如果没有找到匹配,加入到覆盖组:verbatim_targets(二元组:目标文件名,文件大小)
    4. 如果找到匹配,但是sha1不一样,加入到差异组:diffs(数组:Difference)
    5. 如果找到匹配,并且sha1一样,直接pass
  4. 计算差异组的差异文件
  5. 对每个差异组文件
    1. 如果差异文件大小大于目标文件大小的一定比例(patch_threshold),移动到覆盖组
    2. 否则,移动到补丁组:patch_list(五元组:源文件名,目标文件File,源文件File,目标文件大小,差异文件大小),更新最大源文件大小:largest_source_size
  6. 升级顺序
    1. 补丁组
    2. 覆盖组
    3. 重命名组

        从目标文件的角度总结一下:

  1. 如果有path匹配
    1. 如果sha1不一样,差异组
      1. 如果差异太大,覆盖组
      2. 否则补丁组
    2. 否则,不加入任何组
  2. 如果有sha1匹配,重命名组
  3. 如果file匹配,重命名组,同时加入差异组
    1. 如果差异太大,覆盖组
    2. 否则补丁组
  4. 如果没有匹配,覆盖组

        加入覆盖组,目标文件输出到zip,加入补丁组,差异文件输出到zip。

        如果同时在补丁组和重命名组,先做补丁,再重命名。

  • recovery升级

        Recovery升级是在正常升级完成之后才执行的,目的是保证至少有一个可用系统存在。

        Recovery的升级文件是recovery分区相对与boot分区的补丁文件。通过打补丁的方式升级,因为这两个分区(或者img文件)包含很多相同内容,所以这种方式升级可以减少升级包的大小。

        在主系统init.rc中,有启动recovery升级的服务定义:

service flash_recovery /system/etc/install-recovery.sh

    class main

    oneshot

        该shell脚本在函数MakeRecoveryPatch中生成,内容为:

#!/system/bin/sh

if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then

  log -t recovery "Installing new recovery image"

  applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p

else

  log -t recovery "Recovery image already installed"

fi

  • 符号链接

        符号链接收集在二元数组(链接目标名,链接名)中,创建链接时以链接目标分组,同时创建到同一个目标的多个链接。

  • 升级进度

        升级进度有两个层次的控制,show_progress(frac, dur)控制进度在dur秒内增加frac。如果dur为0,那么由set_progress(frac)在上一个show_progress划定的块里面更新进度。

        全量更新:

ShowProgress(0.5, 0)    —— 解压system、recovery

ShowProgress(0.2, 0)    —— 设置文件权限

ShowProgress(0.2, 10)   —— 更新boot分区

ShowProgress(0.1, 0)    —— 其他

        增量更新:

ShowProgress(0.1, 0)    —— 校验文件sha1,删除文件

    SetProgress(so_far / total_verify_size)

ShowProgress(0.8, 0)    —— 打补丁

    SetProgress(so_far / total_patch_size)

ShowProgress(0.1, 10)   —— 创建符号链接,重命名文件

  • 签名

密钥来源:

  1. -k <package_key>(OPTIONS.package_key)
  2. META/misc_info.txt(OPTIONS.info_dict[“default_system_dev_certificate”])
  3. “build/target/product/security/testkey”

公钥扩展名:    .x509.pem

私钥扩展名:    .pk8

签名程序:     signapk.jar -w <公钥> <私钥> <输入文件> <输出文件>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值