存储-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                                例如 01006910056
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)    收藏  举报

导航