0%

磁盘配额调研报告

1. 磁盘配额调研报告


1.1. 实际磁盘配额控制

1.1.1. 依赖项情况

  • Ext4文件系统默认开启了DQUOT_USAGE_ENABLED

Kernelfilesystem support中开启了quota support

1.1.2. 磁盘配额初始化

  • Installd开启data分区的磁盘配额 (/mnt/runtime/default

Sd卡格式化内部存储后,应该也可以支持

StorageStatesServiceinstalld的联动

invalidateMounts 函数中完成了磁盘配额的初始化

1
2
3
Found quota mount " << source << " at " << target;
quotactl(QCMD(Q_QUOTAON, USRQUOTA), source.
quotactl(QCMD(Q_QUOTAON, GRPQUOTA), source

并没有将配置放在fstab中,使用fstab对分区的文件系统进行自动配额调整。可以使用fstab对某一分区进行配额的设定。

1.1.3. 磁盘配额的额外设定

为每个应用设置的实际磁盘配额 :

  • 90% of disk blocks, or50% of disk inodes.

设定原因

Abusive or broken apps can go crazy and try allocating all of the disk space on the device. To mitigate the impact on system health,

set hard limits to block any given app from using more than 90% of disk blocks, or 50% of disk inodes.

Also define the hard limit for AID_MEDIA_RW to avoid filling up the device via the SD card.

Kick QUOTAON when scanning devices, since ext4 doesn’t toggle DQUOT_LIMITS_ENABLED during initial mount.

2. 虚拟的磁盘配额(cache quota)

Android O 围绕缓存数据提供更加出色的导航和性能。每个应用均获得一定的磁盘空间配额,用于存储 getCacheQuotaBytes(File) 返回的缓存数据。

2.1. cache quota 初始化

Framework层的StorageStatsService进行初始化:

1
mHandler.sendEmptyMessage(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE);

默认生成/data/system/cachequota.xml文件,用于持久化应用初始的cache磁盘配额。

1
2
3
4
5
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<cache-info previousBytes="0">
<quota uid="1000" bytes="338152713" />
<quota uid="10012" bytes="235993028" />
</cache-info>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@startuml
StorageStatsService -> StorageStatsService: {}create /data/system/cachequota.xml
StorageStatsService->StorageStatsService: {}MSG:MSG_LOAD_CACHED_QUOTAS_FROM_FILE
StorageStatsService->CacheQuotaStrategy: setupQuotasFromFile
CacheQuotaStrategy->CacheQuotaStrategy: readFromXml(cachequota.xml)
CacheQuotaStrategy->CacheQuotaStrategy: pushProcessedQuotas(item):
note right of CacheQuotaStrategy: \nupdate item value in java and native(installd)
StorageStatsService->StorageStatsService:{}:MSG: MSG_CHECK_STORAGE_DELTADELAY_IN_MILLIS
loop default 30s
StorageStatsService->StorageStatsService:MSG_CHECK_STORAGE_DELTADELAY_IN_MILLIS
opt previousBytes - available bytes < 0.05 * total bytes { perviousBytes保存了上一次调整时的available bytes }
StorageStatsService->CacheQuotaStrategy: recalculateQuotas
CacheQuotaStrategy->CacheQuotaStrategy: getUnfulfilledRequests
CacheQuotaStrategy->UsageStatsService: queryUsageStatsForUser[to get application usage]
CacheQuotaStrategy->CacheQuotaService: computeCacheQuotaHints(In: application usage)
CacheQuotaService-->CacheQuotaService: MSG: MSG_SEND_LIST
CacheQuotaService->CacheQuotaService: onComputeCacheQuotaHints
note right of CacheQuotaService:使用根据应用的使用时间进行排序, 排序后放在队列中。\n 根据在队列中的位置, 按照一定的加权值分配相应的配额。\n 总配额(freesize * 0.15)使用的时间越长, 获得的配额越大
CacheQuotaService->StorageStatsService: reply
end

@enduml

2.2. 内部存储不足时,根据cache quota 释放cache

当系统需要释放磁盘空间时,将开始从超过配额最多的应用中删除缓存文件。因此,如果将您的缓存数据量始终保持低于配额的水平,则在必须清除系统中的某些文件时,您的缓存文件将能坚持到最后。系统在决定删除您的应用中的哪些缓存文件时,将首先考虑删除最旧的文件(由修改时间决定)。

2.2.1.1. 目录的两种删除机制

可以针对每个目录启用两种新行为,以控制系统如何释放缓存数据:

2.2.1.1.1.1. 整体
  • setCacheBehaviorAtomic(File, boolean)

    可用于指示某个目录及其所有内容应作为一个不可分割的整体进行删除。只能作用于目录。设置后,该目录下的所有子目录及文件都标记为一个整体。

    其下面的每一个文件的修改,修改时间都会标记到该目录层级上。

2.2.1.1.1.2. 截断
  • setCacheBehaviorTombstone(File, boolean)

    可用于指示不应删除某个目录内的文件,而应将它们截断到 0 字节长度,使空文件保持完好。也可用于目录,则该级目录下的所有文件(除链接之外的)删除时都会截断到0字节长度。

跳转到的地方

2.2.1.2. 根据cache quota 释放 cache

  • 删除目录层次:

    1
    2
    3
    /data/user/<user_id>/
    /data/user_de/<user_id>
    /data/media/<user_id>/Android/data/
    • uid 为索引,创建相应的tracker。特殊的对于media下的,其为 external data
    • getCacheRatio (cacheUsed/ quota配额)为排序依据保存了tracker
    • cacheRatio越大,越排在前面
    • tracker中保存应用下 cache code_cache 子目录中的各级文件: 保存到了itemsvector中。
    • items 按照修改时间进行排序,特定的对于使用前面两个接口设置文件绑定关系(1 2)的,会绑定为一个item。

在对item进行删除时,首先找到cacheRatio最大的应用相关的目录,去其中的文件按照修改时间的顺序进行删除,每删除一项,检查删除后的空间是否满足要求,如果满足要求,则不再进行删除,如果不满足,重新计算每个应用的cacheRadio,然后,对cacheRadio最大的,找到其中修改时间最早的项。进行删除。 依此步骤,直到删除到满足条件为止。

2.2.2. 为应用分配空间时,查询可使用的空间

在需要为大文件分配磁盘空间时,可考虑使用新的 [allocateBytes(File, long, int)](#allocateBytes(java.io.File, long, int)) API,它将==自动清除==属于其他应用的缓存文件(根据需要),以满足您的请求。在确定设备是否有足够的磁盘空间保存您的新数据时,请调用 [getAllocatableBytes(File, int)](#getAllocatableBytes(java.io.File, int)) 而不要使用 getUsableSpace(),因为前者会考虑系统要为您清除的任何缓存数据。

跳转