优化cache
cache文件系统损坏问题
因文件系统损坏, 可能导致可用空间信息不准确, 或cache空间变成一半.
开机加入强制check cache, 可以解决可用空间错误的问题.
http://gerritlvs.pt.miui.com/#/c/641165/
原生方案
没有强制check cache. 开机阶段对cache分区进行检查, 如果出现过异常关机的情况, 会对cache分区进行修复.
- fstab中options配置了check
- 读取文件系统超级块信息时返回的状态是FS_STAT_UNCLEAN_SHUTDOWN或FS_STAT_QUOTA_ENABLED
FS_STAT_UNCLEAN_SHUTDOWN状态可以理解完出现了异常关机
FS_STAT_QUOTA_ENABLED状态则是分区是否启用了磁盘配额机制
1 | read_ext4_superblock(blk_device, &sb, &fs_stat) |
因cache文件系统损坏导致的recovery无法更新成功问题
解决方案: (适用于bootloader加锁的情况)
执行“fastboot continue开机后, 长按power键+ 音量下,直接进fastboot , 然后fastboot continue开机”.
目的是触发非正常关机, 再次开机时cache分区文件系统超级块检查后变为FS_STAT_UNCLEAN_SHUTDOWN, 触发check修复, 然后recovery更新所在的服务flash_recovery即可正常执行.
改进方案
我们的方案即在原生方案基础上在fstab中加入了check选项, 这样每次开机都会对cache进行check. 如在ext4上执行
1 |
|
对于cache空间变成一半的问题, 因当前还没有root-cause, 也没有dump出的现场的cache.img, 问题还需要继续跟踪.
总空间变成一半的案例:
https://jira.n.xiaomi.com/browse/HTH-60766
cache损坏, 可用空间变成一半的案例
因ota升级重启导致的cache缓存遗留问题
在applypatch的代码中, 如果差分升级某些分区, 会有cache缓存残留.
方案
- ota升级过程中屏蔽power键功能, 保证ota升级过程中用户不能通过长按power键重启
- 在校验分区已经满足目标版本时, 删除对应的分区残留项.
方案一
第一个方案正在对所有机型计划合入中, 依赖底层kernel中power节点的使能.
对应高通机型, http://gerrit.pt.miui.com/#/c/648868/, 需要kpdpwr_reset节点使能即可.
http://gerrit.pt.miui.com/#/c/665992/, 节点前的路径需要底层同事提供.
1 | on property:vendor.kpdpwr.reset.enabled=0 |
对应mtk机型, 可以参考G7的修改.
http://gerrit.pt.miui.com/#/c/665678/
http://gerrit.pt.miui.com/#/c/664037/
方案二
在校验分区已经满足目标版本时, 删除对应的分区残留项.
1 | if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { |
尽可能删除cache下的文件
在检查及清除cache下文件时, 尽可能删除cache下的文件.
在尽量保留recovery log的基础上对cache下的所有文件进行排序.
排序按照优先级, 大小的双重规则进行安排.
对log和升级相关的文件尽可能不删除, 对ota升级过程中的stash文件不做处理, 对其他文件优先按照文件大小进行排序, 逐个删除, 直到可用空间满足要求为止.
相关的修改参考: http://gerrit.pt.miui.com/#/c/646042/1/applypatch/freecache.cpp
原生方案
androidP 只删除/cache根目录下的文件, 还有*/cache/recovery/otatest*目录下的文件.
1 | const char* dirs[2] = {"/cache", "/cache/recovery/otatest"}; |
androidQ上只删除*/cache根目录下的文件和/cache/recovery/*下的log文件, 保留last_log和last_kmsg.
1 | std::vector<std::string> dirs{ "/cache", Paths::Get().cache_log_directory() }; |
改进方案
遍历cache目录, 对其下所有文件进行追踪, 记录其文件名, 大小, 和优先级.
优先级从高->低, 越大的越容易被删除. 同优先级的比较大小, 文件越大的越容易被删除. 同时定义了 **CANNOT_DEL_PRI
**级别, 对应不能被删除的文件.
打印log, 给出了/cache下最大的20个文件名.
不能被删除的文件
- /cache/recovery/block.map
- /cache/recovery/last_log
- /cache/recovery/last_log.1
- ==/cache/saved.file==
/cache/recovery/<dir_name>/*
- /cache/ 下小于2k的小文件, 包含了/cache/recovery下升级相关的日志文件, 升级文件等.
优先级排序
从上到下越容易被删除的程度
- /cache/recovery/
/* 主要是考虑升级时stash相关的文件, 没有固定文件名 - /cache/recovery/ 保存了相关recovery的升级日志等.
- /cache/mqsas/
- 其他
applypatch优化
在ota升级后, recovery更新时, 原生方案依赖cache分区的可用空间. 主要是***/cache/saved.file的处理. 对于recovery更新的场景来说, 是使用/dev/block/bootdevice/by-name/boot + system/recovery-from-boot.p 文件, 生成recovery分区的镜像, 写入/dev/block/bootdevice/by-name/recovery*, 这个过程中原始文件boot不会被损坏, 所以是不需要缓存文件的.
applypatch可以用作使用别的分区升级当前分区, 如 boot-[recovery-from-boot.p]->new recovery
1 | applypatch EMMC:/dev/block/bootdevice/by-name/boot:67108864:8c05fe713233ad8b5c85db717bfdae0cecdca5bc EMMC:/dev/block/bootdevice/by-name/recovery 870ddfcf72425a76f95899254a8df3b32ac2dad0 67108864 8c05fe713233ad8b5c85db717bfdae0cecdca5bc:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed" |
使用当前分区内容升级当前的分区, 即自更新. boot-[boot.p]-> new boot
1 | patch_partition("EMMC:/dev/block/bootdevice/by-name/boot:51242240:5d92bec9964000c0b11229cdc51470662feda96b", |
自更新是需要缓存的, 就是怕在更新过程中意外退出了, 那原始的分区也会被破坏. 使用缓存来作备份, 保存原始分区的内容.
优化方案
recovery更新时调用applypatch不再需要缓存文件. 也就去除了对cache
分区需要有可用空间的依赖.
http://gerrit.pt.miui.com/#/c/646042/1/applypatch/applypatch.cpp
ota打包时, 能使用的最大的cache空间
原生行为, ota打包时,最大能够使用的cache空间为cache partition size的80%.
优化方案
调整为cache文件系统总空间的80%. 和系统升级app端行为保持一致, (升级前先检查cache的使用量, 如果超过20%要求用户对其格式化)
相关change: http://gerrit.pt.miui.com/#/c/661519/
主要原理是在调用文件系统对应的打包脚本生成cache.img时, 截取used_blocks和total_blocks.
文件系统-> 打包脚本
- ext: mkuserimg_mke2fs
- squash: mksquashfsimage.sh
- f2fs: mkf2fsuserimg.sh
当前cache是ext4的, 会输出下面的信息, 从中可以截取出used_blocks和total_blocks.
1 | block_bytes_key = r'Creating journal\s\((\d+)\sblocks\).*' |