存储-2-storaged与uid统计
一、storaged服务
1. 简介
Android 里的 storaged 是一个“存储监控与统计”系统服务,核心职责是收集并提供存储相关指标,不负责调度 I/O,也不负责文件系统挂载。可以理解为“存储观测层”的守护进程。service list 可以看到它。
storaged 服务本身定期(默认每小时)读一次 /proc/uid_io/stats,然后保存两次读取的快照,和做差值得到"这段时间的增量",
把快照和增量都持久化到磁盘(通常在 /data/misc/storaged/)。####
(1) 它主要做
a. 统计按 UID 维度的 I/O 行为。例如前台/后台读写字节、读写字符数、fsync 次数等(依赖内核导出的 UID I/O 统计)。
b. 汇总设备级存储健康信息。比如 eMMC/UFS 的寿命指标、EOL(寿命终点)等级等,通常来自 health HAL/内核接口。
c. 向系统其他模块提供统一查询入口。通过 Binder 接口给 framework、stats 组件、bugreport 使用。
d. 支持调试与问题定位。通过 dumpsys 输出当前统计快照,便于分析"谁在疯狂写盘", "是否有异常 fsync"。####
(2) 它不做
不决定 I/O 调度策略(那是内核块层调度器,如 mq-deadline/BFQ)。不直接做文件系统管理(那是 ext4/F2FS 等文件系统职责)。不直接限制某个应用的 I/O(限速/控制一般是 cgroup、IO controller、系统策略层做)。
(3) 实现原理
应用产生文件读写, 内核累计 UID 维度 I/O 计数, storaged 周期性读取并聚合, 通过 Binder/dumpsys/统计通路提供给系统服务与调试工具。
常见数据来源:UID I/O 统计接口(内核导出), 块设备/健康信息接口(health HAL、sysfs、内核驱动暴露)。系统状态(前后台、充电状态等)。
(4) 存在价值
a. 识别写放大和异常写入应用。某些应用频繁落盘、频繁 fsync,会直接拖慢系统和增加闪存磨损。
b 评估闪存寿命风险。尤其在 eMMC/UFS 设备上,寿命指标可以帮助判断设备老化是否明显。
c. 支撑性能问题闭环。卡顿、掉帧、耗电升高,很多都和 I/O 波动有关,storaged 数据常是第一手证据。
(5) 典型排障方法
a. 看服务是否活着: getprop init.svc.storaged
b. 拉取服务状态: dumpsys storaged
c. 看原始 UID I/O 数据是否存在 cat /proc/uid_io/stats
d. 看日志 logcat | grep -i storaged
若 dumpsys storaged 基本无数据,可能是服务未启动、权限受限、内核统计接口未启用。
若某 UID 写入和 fsync 持续偏高,可能是日志刷盘过于频繁、数据库提交策略过激、后台任务写放大。
若存储寿命指标异常上升,可能设备长期高写入、缓存策略不当,或存在异常循环写。
实战上通常是:先看 storaged 定位问题,再调调度器参数验证改善。
实用思路:先抓一段基线(空闲+典型使用) storaged 数据。复现场景(卡顿/掉帧/耗电)再抓一次。对比高写 UID、fsync 峰值、设备健康指标变化。再结合调度器与文件系统参数做定向优化。
二、dumpsys storaged 内容介绍
这个输出包含 时间段分组的 UID I/O 统计数据。每个时间段内,汇总了该 UID(进程/应用)对应的读写操作统计。
1. 格式拆解
(1) 时间戳行
,1780462915 0,1780462916 1780462915,1780466515 1780466515,1780470116 ,1780473717 ----------------------------------------------------------------------------- 格式 含义 ----------------------------------------------------------------------------- ,时间戳 单个时间点,从系统启动到该时刻的累计统计 时间戳1,时间戳2 时间区间,表示从时间戳1到时间戳2这段时间内的 增量 统计 -----------------------------------------------------------------------------
含义:
,1780462915:系统启动到第 1780462915 秒(UNIX 时间戳或系统启动时间)的全量数据。
0,1780462916:从第0秒(即系统启动)到第 1780462916 秒的全量数据。
1780462915,1780466515:从第 1780462915 秒到 1780466515 秒这 3600 秒(1 小时)内的增量数据。
全量数据(,xxx): 系统启动至今的累计总量,用于长期趋势。
增量数据(t1,t2): 过去1小时的新增,用于实时监控和快速定位问题。
看增量数据快速识别"最近谁在疯狂读写",看全量数据理解"长期来看哪些应用累计 I/O 最多"。
注意,这个时间戳是Unix时间戳,是从 1970年1月1日 00:00:00 UTC 开始计算的秒数。转换为UTC时间方法:
date -d @1780462915 //Wed Jun 3 13:01:55 CST 2026 转换为本地时间 date -u -d @1780462915 //Wed Jun 3 05:01:55 UTC 2026 强制输出UTC时间 date -u -d @1780462915 '+%Y-%m-%d %H:%M:%S UTC' //自定义格式,输出:2026-06-24 10:21:55 UTC date -u -d @1780462915 '+%Y-%m-%d' //自定义格式,只看日期,输出:2026-06-24 date -u -d "2026-06-3 13:01:55" +%s //反向操作,输出 1780491715 echo $((1780466515 - 1780462915)) //时间戳做差值
(2) 数据行格式
UID/应用名 字段1 字段2 字段3 字段4 字段5 字段6 字段7 字段8 字段9
每行 9 个字段,含义如下:
---------------------------------------------------------------------------------------------- 字段序号 字段名 单位 含义 说明 ---------------------------------------------------------------------------------------------- 1 读字节(前台) 字节 应用在前台时的读取字节数 用户能感知的读操作 2 读字节(后台) 字节 应用在后台时的读取字节数 后台任务、系统服务的读 3 写字节(前台) 字节 应用在前台时的写入字节数 用户能感知的写操作 4 写字节(后台) 字节 应用在后台时的写入字节数 后台日志、缓存写 5 fsync(前台) 次数 前台进程执行的fsync调用次数 前台数据强制落盘频率 6 fsync(后台) 次数 后台进程执行的fsync调用次数 后台日志落盘频率 字段7~9 预留 -- 通常为 0,预留给未来扩展 -- ----------------------------------------------------------------------------------------------
2. UID说明
---------------------------------------------------------------------------------------------- UID 身份 说明 ---------------------------------------------------------------------------------------------- 0 root 系统核心进程 1000~1999 系统 UID 系统服务(1003=system_server, 1010=camera, 1013=audio, 1017=network 等) 10000+ 应用 UID 普通应用(每个应用唯一 UID) shared:xxx 共享 UID 多个应用共享的 UID(如 Bluetooth、System、Phone) ----------------------------------------------------------------------------------------------
3. 实例解读
(1) 例1:系统进程(UID 0)
,1780462915 0 402378752 9498624 0 0 0 0 0 0
读字节(前台):402378752 字节 ≈ 384 MB
读字节(后台):9498624 字节 ≈ 9 MB
写字节(前台):0 字节
写字节(后台):0 字节
fsync:全为 0
解读:系统启动以来,root 进程读了 ~393 MB 数据,都是在前台,基本没有写操作。
(2) 例2:应用包(1小时时间段)
1780462915,1780466515 //delta=3600s com.samsung.smartcontrol 20668416 339968 10235904 123895808 0 0 0 0 0
读字节(前台):20668416 字节 ≈ 19.7 MB
读字节(后台):339968 字节 ≈ 332 KB
写字节(前台):10235904 字节 ≈ 9.7 MB
写字节(后台):123895808 字节 ≈ 118 MB
fsync:全为 0
解读:过去 1 小时,这个应用:读了 ~20 MB(前台) + 332 KB(后台), 写了 ~10 MB(前台)+ 118 MB(后台), 后台写入明显高于前台(可能在同步数据、缓存、日志)。
(3) 例3:shared:android.uid.system(共享 UID)
1780462915,1780466515 shared:android.uid.system 953810944 1493209088 0 0 0 0 0 0 0
读字节(前台):953810944 字节 ≈ 910 MB
读字节(后台):1493209088 字节 ≈ 1.39 GB
写:全为 0
解读:系统共享 UID 这 1 小时读了 ~2.3 GB 数据,后台读远多于前台。
三、原始数据节点/proc/uid_io/stats
导出文件 drivers/misc/uid_sys_stats.c。user版本也默认使能。
/proc/uid_io/stats 是 Android 内核导出的“按 UID 统计的 I/O 账户”。它不是文件列表,也不是块层请求日志,而是把某个 UID 产生的 I/O 行为汇总成一行。
每次重启,所有计数从 0 重新开始。没有持久化,关机后数据丢失,开机后随系统运行持续累加,而且只加不减,只要该 UID 有 I/O 操作,对应字段就往上加。内核在 I/O 发生时同步更新,cat看到的是最新的累计值。
打印内容如下,总共 11 列:
# cat /proc/uid_io/stats 0 412027173 89924685 383823872 90300416 0 0 0 0 314 0 10069 26 221 0 0 755296 44191 11137024 364544 0 0 10056 393465 27327 8339456 24137728 0 0 0 0 0 0 ...
1. 每个字节表示含义
uid fg_rchar fg_wchar fg_read_bytes fg_write_bytes bg_rchar bg_wchar bg_read_bytes bg_write_bytes fg_fsync bg_fsync
2. 每个字段的准确含义
------------------------------------------------------------------------------------------------------------ 列序号 字段名 含义 典型来源 ------------------------------------------------------------------------------------------------------------ 1 uid Linux UID/Android应用 UID 例如 0、10069、10056 2 fg_rchar 前台状态下,read 类系统调用“返回给调用者的字节数”累计值 逻辑读 3 fg_wchar 前台状态下,write 类系统调用“提交给内核的字节数”累计值 逻辑写 4 fg_read_bytes 前台状态下,真正从存储设备读出的字节数 物理读 5 fg_write_bytes 前台状态下,真正写到存储设备的字节数 物理写 6 bg_rchar 后台状态下,逻辑读字节数 逻辑读 7 bg_wchar 后台状态下,逻辑写字节数 逻辑写 8 bg_read_bytes 后台状态下,物理读字节数 物理读 9 bg_write_bytes 后台状态下,物理写字节数 物理写 10 fg_fsync 前台状态下调用 fsync/fdatasync 的次数 强制落盘次数 11 bg_fsync 后台状态下调用 fsync/fdatasync 的次数 强制落盘次数 ------------------------------------------------------------------------------------------------------------
3. 易混淆字段
(1) rchar/wchar 是“逻辑 I/O”,它反映的是应用从系统调用视角“读了多少、写了多少”。
例如:
read(fd, buf, 4096) 成功返回 4096,那么 rchar 增加 4096
write(fd, buf, 4096) 成功写入 4096,那么 wchar 增加 4096
这两个值回答的是:应用层声称自己读了多少数据,应用层声称自己写了多少数据,但它们不等于真正访问闪存的字节数。原因是读可能命中 page cache,应用读了 4 KB,但底层不用真的去 UFS/eMMC 取数据。写可能先写到页缓存,稍后才回写到底层。文件系统元数据、日志、回写合并、写放大,会让实际写入量和 wchar 差很多。
(2) read_bytes/write_bytes 是“物理 I/O”, 这两个值更接近“块设备层真的干了多少活”。
read_bytes:真正从存储介质读出来的字节
write_bytes:真正刷到存储介质的字节
它们回答的是:UFS/eMMC/NVMe 实际读了多少, UFS/eMMC/NVMe 实际写了多少, 所以常见现象是:
rchar > read_bytes:说明大量读命中了缓存.
write_bytes > wchar:说明除了应用数据本身,还有文件系统元数据、日志、放大写等额外成本.
wchar > write_bytes:说明很多写还停留在缓存,还没真正落盘,或者被合并了.
4. 前后台
这里的 fg 和 bg 不是简单地指“界面在不在屏幕上”,而是 Android 系统按 UID 维护的前后台状态。大体可以理解为:
fg_*:这个 UID 处于前台时产生的 I/O
bg_*:这个 UID 处于后台时产生的 I/O
这个状态通常由 framework 根据进程状态同步到内核,所以它更接近:用户可感知的 I/O, 和后台服务/定时任务/同步任务的 I/O。
四、其它uid相关统计
1. /proc/uid_procstat/set
/proc/uid_procstat/set 是只写控制接口,用于设置某个 UID 现在属于前台还是后台。主要是给 /proc/uid_io/stats 和 storaged 这类按 UID 分前后台统计 I/O 的机制提供“状态切换依据”。
实现定义在 /drivers/misc/uid_sys_stats.c 中,user版本也默认使能。
echo "10069 0" > /proc/uid_procstat/set //设 uid=10069 为前台 echo "10069 1" > /proc/uid_procstat/set //设 uid=10069 为后台
这个“前台/后台”只影响 I/O 统计该记到哪个桶里。
2. /proc/uid_cputime
除此之外还有 /proc/uid_cputime 文件,导出位置 drivers/misc/uid_sys_stats.c。
(1) /proc/uid_cputime/show_uid_stat
# cat /proc/uid_cputime/show_uid_stat 0: 40648951 484941760 //uid utime_us stime_us 从开机累计用户态时间和累计内核态时间 10069: 231589 243453 10056: 84610 278675 ...
(2) /proc/uid_cputime/remove_uid_range
是只写节点,作用是从内核的 UID CPU 统计表里,删除一段 UID 范围对应的统计记录。它接收的输入格式是 start_uid-end_uid,如: echo 10000-19999 > /proc/uid_cputime/remove_uid_range.
五、总结
1. dumpsys storaged 可以看各个UID从开机到现在和每个小时的IO逻辑和物理数据量,和 sync 次数。
posted on 2026-06-24 15:41 Hello-World3 阅读(11) 评论(0) 收藏 举报
浙公网安备 33010602011771号