0%

优化cache

优化cache

cache文件系统损坏问题

因文件系统损坏, 可能导致可用空间信息不准确, 或cache空间变成一半.
开机加入强制check cache, 可以解决可用空间错误的问题.

http://gerritlvs.pt.miui.com/#/c/641165/

原生方案

没有强制check cache. 开机阶段对cache分区进行检查, 如果出现过异常关机的情况, 会对cache分区进行修复.

  • fstab中options配置了check
  • 读取文件系统超级块信息时返回的状态是FS_STAT_UNCLEAN_SHUTDOWNFS_STAT_QUOTA_ENABLED

FS_STAT_UNCLEAN_SHUTDOWN状态可以理解完出现了异常关机

FS_STAT_QUOTA_ENABLED状态则是分区是否启用了磁盘配额机制

1
2
3
4
5
6
read_ext4_superblock(blk_device, &sb, &fs_stat)
// fstab中options配置了check或者读取文件系统超级块信息时返回的状态是FS_STAT_UNCLEAN_SHUTDOWN或FS_STAT_QUOTA_ENABLED
if ((rec->fs_mgr_flags & MF_CHECK) ||
(fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
check_fs(blk_device, rec->fs_type, rec->mount_point, &fs_stat, rec);
}
因cache文件系统损坏导致的recovery无法更新成功问题

解决方案: (适用于bootloader加锁的情况)

执行“fastboot continue开机后, 长按power键+ 音量下,直接进fastboot , 然后fastboot continue开机”.

目的是触发非正常关机, 再次开机时cache分区文件系统超级块检查后变为FS_STAT_UNCLEAN_SHUTDOWN, 触发check修复, 然后recovery更新所在的服务flash_recovery即可正常执行.

改进方案

我们的方案即在原生方案基础上在fstab中加入了check选项, 这样每次开机都会对cache进行check. 如在ext4上执行

1
2
#define E2FSCK_BIN      "/system/bin/e2fsck"
const char* e2fsck_forced_argv[] = {E2FSCK_BIN, "-f", "-y", blk_device};

对于cache空间变成一半的问题, 因当前还没有root-cause, 也没有dump出的现场的cache.img, 问题还需要继续跟踪.

总空间变成一半的案例:

https://jira.n.xiaomi.com/browse/HTH-60766

cache损坏, 可用空间变成一半的案例

https://jira.n.xiaomi.com/browse/MIUI-1605226

因ota升级重启导致的cache缓存遗留问题

在applypatch的代码中, 如果差分升级某些分区, 会有cache缓存残留.

方案

  1. ota升级过程中屏蔽power键功能, 保证ota升级过程中用户不能通过长按power键重启
  2. 在校验分区已经满足目标版本时, 删除对应的分区残留项.

方案一

第一个方案正在对所有机型计划合入中, 依赖底层kernel中power节点的使能.

对应高通机型, http://gerrit.pt.miui.com/#/c/648868/, 需要kpdpwr_reset节点使能即可.

http://gerrit.pt.miui.com/#/c/665992/, 节点前的路径需要底层同事提供.

1
2
3
4
5
on property:vendor.kpdpwr.reset.enabled=0
write /sys/devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-00/c440000.qcom,spmi\:qcom,pm660@0\:qcom,power-on@800/kpdpwr_reset 0

on property:vendor.kpdpwr.reset.enabled=1
write /sys/devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-00/c440000.qcom,spmi\:qcom,pm660@0\:qcom,power-on@800/kpdpwr_reset 1

对应mtk机型, 可以参考G7的修改.

http://gerrit.pt.miui.com/#/c/665678/

http://gerrit.pt.miui.com/#/c/664037/

方案二

在校验分区已经满足目标版本时, 删除对应的分区残留项.

1
2
3
4
5
6
7
8
9
10
if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
// The early-exit case: the patch was already applied, this file has the desired hash, nothing
// for us to do.
// cache/saved.file
size_t cache_temp_source_size = GetFileSize(CacheLocation::location().cache_temp_source());
if(cache_temp_source_size != 0)
unlink(CacheLocation::location().cache_temp_source().c_str());
printf("already %s\n", short_sha1(target_sha1).c_str());
return 0;
}

案例: https://jira.n.xiaomi.com/browse/HQ-43785

尽可能删除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
2
3
4
5
6
std::vector<std::string> dirs{ "/cache", Paths::Get().cache_log_directory() };
for (const auto& dirname : dirs) {
if (RemoveFilesInDirectory(bytes, dirname, FreeSpaceForFile)) {
return true;
}
}

改进方案

遍历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
2
3
4
patch_partition("EMMC:/dev/block/bootdevice/by-name/boot:51242240:5d92bec9964000c0b11229cdc51470662feda96b",
"EMMC:/dev/block/bootdevice/by-name/boot:50926848:07700e132a84e47c26380a7a7a0fc57f8164fa60",
package_extract_file("boot.img.p")) ||
abort("E3008: Failed to apply patch to EMMC:/dev/block/bootdevice/by-name/boot:50926848:07700e132a84e47c26380a7a7a0fc57f8164fa60");

自更新是需要缓存的, 就是怕在更新过程中意外退出了, 那原始的分区也会被破坏. 使用缓存来作备份, 保存原始分区的内容.

优化方案

recovery更新时调用applypatch不再需要缓存文件. 也就去除了对cache分区需要有可用空间的依赖.

http://gerrit.pt.miui.com/#/c/646042/1/applypatch/applypatch.cpp

ota打包时, 能使用的最大的cache空间

原生行为, ota打包时,最大能够使用的cache空间为cache partition size80%.

优化方案

调整为cache文件系统总空间的80%. 和系统升级app端行为保持一致, (升级前先检查cache的使用量, 如果超过20%要求用户对其格式化)

案例: https://jira.n.xiaomi.com/browse/HTH-56790

相关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
2
block_bytes_key = r'Creating journal\s\((\d+)\sblocks\).*'
block_count_key = r'Created filesystem with[\s\/\d]+inodes\sand\s(\d+)\/(\d+)\sblocks'