Android性能系列专题理论之十一:block IO问题分析思路

【关注我,后续持续新增专题博文,谢谢!!!】

上一篇我们讲了:

这一篇我们开始讲:

目录

[一、block I/O背景](#一、block I/O背景)

[二、Block I/O 原理详解](#二、Block I/O 原理详解)

[三、Block I/O问题分类与特征分析](#三、Block I/O问题分类与特征分析)

[四、Block I/O 问题分析SOP](#四、Block I/O 问题分析SOP)

[五、Block I/O 的常见原因](#五、Block I/O 的常见原因)


一、block I/O背景

uninterruptible sleep (D状态)与block I/O的关系

当进程因等待I/O操作而进入uninterruptible sleep(标记为D状态)时,表示该进程正在等待磁盘或网络等底层资源响应。在systrace中表现为长时间停留在D状态,通常与以下场景相关:

  • 慢速存储设备(如机械硬盘或高延迟网络存储)
  • I/O队列拥塞或设备过载
  • 文件系统锁竞争或元数据操作

systrace中识别block I/O问题

systrace的CPU频率视图或进程活动区域中:

  • 查找长时间处于D状态的进程(红色标记)
  • 检查对应时间点的mmc(存储设备)或block内核函数调用
  • 观察iowait数值突增的CPU核心

典型模式表现为:

复制代码
<idle>-0     [000] d.h.  1234.567890: sched_blocked_reason: pid=1234 iowait=1 caller=ext4_file_write_iter+0x123

Block I/O 在 systrace 中的表现

在 systrace 中,Block I/O 导致的卡顿通常表现为以下特征:

  • 高延迟的 I/O 操作block_rq_issueblock_rq_complete 之间的时间间隔过长,表明 I/O 请求从发起到完成的耗时较高。
  • 频繁的 I/O 等待wait_on_page_bit_commonio_schedule 等事件频繁出现,表示进程因 I/O 阻塞而进入等待状态。
  • 磁盘队列堆积blk_queue_busyblk_mq_run_hw_queue 显示磁盘请求队列堆积,可能导致后续 I/O 请求延迟。

二、Block I/O 原理详解

以下是 Linux 内核中 Block I/O 操作的核心流程,涉及页面缓存、锁机制与底层文件系统交互:


内存分配与缓存管理

调用 __page_cache_alloc 从内存管理子系统分配一个物理页面(page)。该页面初始状态为空,未包含任何有效数据。

通过 add_to_page_cache_lru 将新分配的页面插入页面缓存(page cache)的 LRU(最近最少使用)链表。此时页面被纳入内核的统一缓存管理体系。


页面锁定与状态标记

设置页面的 PG_locked 标志位,标记该页面正处于 I/O 操作中。此时任何尝试访问该页面的进程将被阻塞。

调用 __lock_page 函数显式锁定页面,确保在数据加载完成前不会被其他进程修改或释放。


文件系统与块层交互

文件系统通过 readpages 方法处理批量页面读取请求。该方法将逻辑文件偏移转换为物理磁盘块地址。

块层(block layer)的 submit_bio 提交构造好的 I/O 请求(bio 结构体)。请求包含目标设备、扇区地址和内存缓冲区(即之前分配的 page)。


I/O 完成处理

磁盘控制器完成数据读取后,通过中断或轮询机制通知内核。数据被 DMA 传输到之前锁定的 page 内存中。

在 I/O 结束处理(endio)中清除 PG_locked 标志位,唤醒等待该页面的进程。此时页面数据已有效,可被正常访问。


关键点说明

  • 页面缓存:作为磁盘数据的缓存层,减少实际 I/O 操作次数
  • 锁机制PG_locked 保证数据一致性,防止竞态条件
  • 异步 I/O:整个流程非阻塞,进程可在页面锁定后挂起或执行其他任务

该流程体现了 Linux 将内存管理、文件系统与块设备驱动分层协作的设计哲学。

三、Block I/O问题分类与特征分析

底层异常导致的I/O性能问题
  • 表现特征:平均单次I/O耗时显著增加,通常超过10ms。
  • 常见原因
    • 存储空间接近或达到容量上限,导致碎片化或写放大。
    • 存储器件(如eMMC/UFS)老化,物理性能下降。
    • 底层调度策略(如CPU/Block层调度)出现异常或配置不当。
  • 典型场景
    • 长时间高负载写入后,器件寿命衰减。
    • 文件系统未及时维护(如TRIM未触发)。
进程行为或系统资源导致的I/O问题
  • 表现特征:单次I/O耗时较短(约几毫秒),但频繁的短暂I/O累积成高延迟。
  • 常见原因
    • 进程密集加载文件(如启动时加载大量动态库)。
    • 低内存状态下缓存被回收,触发重复I/O(如inode/dentry缓存失效)。
    • 小文件随机读写占主导,导致寻址开销叠加。
  • 典型场景
    • 应用程序初始化阶段或资源热加载。
    • 内存压力下频繁触发kswapd回收页缓存。

诊断方法建议

  • 工具使用

    • iostat -x 1观察awaitsvctm差值,判断队列堆积或硬件延迟。
    • ftrace/blktrace追踪具体I/O路径的耗时分布。
    • vmstat 1检查内存回收频率与I/O关联性。
  • 优化方向

    • 底层异常需检查SMART健康状态或更换存储介质。
    • 进程级问题可通过预加载、调整缓存策略或优化文件访问模式缓解。

四、Block I/O 问题分析SOP

确定底层IO性能是否异常的排查方法

平均单次IO耗时大于10ms

IO自身性能存在问题,需重点关注存储模块。检查存储硬件状态(如磁盘健康度、SSD磨损)、文件系统碎片化、RAID配置或存储驱动兼容性。必要时使用工具(如iostatblktrace)深入分析存储延迟分布。

平均单次IO耗时小于10ms

IO自身无异常,按以下方向排查:

器件性能差异验证

使用AndroBench等基准测试工具对比目标设备与参考机的读写速度(顺序/随机)、IOPS。若差异显著,需检查存储芯片规格、控制器性能或固件版本。

内存占用分析

通过freeproc/meminfo确认系统内存使用率。低内存可能导致频繁换页,触发额外IO。排查内存泄漏进程(如topsmem),或优化应用内存管理策略。

调度问题定位

通过systrace捕捉固定唤醒源及runnable线程阻塞时长。若存在长时间就绪态线程,需检查CPU负载均衡、锁竞争或优先级配置。调整调度策略(如CFS参数)或优化线程模型。

进程IO负载检查

使用iotop/proc/pid/io统计各进程读写量。高IO负载进程需进一步分析其操作模式(如大量小文件写入)。考虑限制其IO带宽或优化数据访问逻辑。

文件访问优化

通过stracelsof确认高频访问的文件路径。对必须频繁读写的文件采用内存映射(mmap)或预加载;异步写入合并策略可减少IO次数。避免直接操作小文件,改用数据库或缓存中间层。

2.1:器件性能差异验证

确认存储器件性能差异的方法

使用AndroBench工具测试存储性能,获取关键指标(如顺序读写、随机读写速度),与对比机数据进行比对。

测试步骤

下载并安装AndroBench(适用于Android平台),确保测试期间无其他后台任务干扰。运行基准测试,记录以下数据:

  • 顺序读取/写入速度(MB/s)
  • 随机读取/写入速度(IOPS)
  • SQLite数据库操作性能(若涉及数据库场景)

数据比对

将测试结果与目标对比机的历史数据或行业标准(如同类存储芯片规格)对比。若差异超过10%-15%,需考虑硬件性能差异对Block I/O问题的影响。

其他辅助验证手段

检查硬件规格

通过系统命令或硬件信息工具(如adb shell cat /proc/scsi/scsi或第三方App)确认存储型号(如UFS 2.1/3.1、eMMC 5.1等),不同型号的底层性能存在显著差异。

监控实时I/O负载

使用adb shell dumpsys diskstatsiotop命令观察实时I/O负载情况,结合业务场景分析是否因硬件瓶颈导致延迟。

注意事项

  • 测试时需关闭省电模式,避免CPU降频影响结果。
  • 多次测试取平均值,减少偶然误差。
  • 若条件允许,交叉验证其他工具(如A1 SD Bench)以确保数据一致性。

2.2 :排查内存占用异常的方法

检查系统内存使用情况 使用 free -h 命令查看内存使用情况,重点关注 available 字段,表示当前可用内存。若 available 值极低(如小于总内存的10%),可能存在内存压力。

分析缓存占用比例 通过 free -h 输出的 buff/cache 字段查看缓存占用量。Linux 会利用空闲内存缓存文件数据,若该值较高但 available 充足,属于正常行为。

监控缓存回收情况 使用 vmstat 1 观察 si(swap in)和 so(swap out)列。若频繁出现非零值,说明系统正在回收缓存或触发交换,可能因内存不足。

定位低内存引发的 IO 问题

观察块设备 IO 延迟 使用 iostat -x 1 查看 await%util 指标。高 await(如 >50ms)伴随高 %util(如 >80%)表明存在 IO 瓶颈,可能与缓存失效导致的直接磁盘读写有关。

跟踪进程级 IO 请求 通过 iotop -o 查看实时 IO 写入/读取进程。若某些进程在低内存时突然出现高 IO,可能是因缓存被回收后被迫从磁盘加载数据。

检查内核日志 使用 dmesg | grep -i oomjournalctl -k --grep="oom" 查找 OOM(Out of Memory)事件记录。OOM Killer 触发后可能导致进程异常终止并重新加载数据。

优化建议

调整缓存回收策略 修改 /proc/sys/vm/swappiness(默认值60)为更低值(如10-30),减少交换倾向,优先释放文件缓存而非触发交换。

限制内存密集型进程 使用 cgroups 为特定进程组设置内存限制(memory.limit_in_bytes),避免单一进程耗尽内存导致全局缓存失效。

增加监控与告警 部署 Prometheus + Grafana 监控 node_memory_MemAvailablenode_disk_io_now 等指标,设置阈值告警(如可用内存 <1GB)。

2.3 :IO 完成与调度延迟分析

IO 完成与调度延迟分析

IO 请求完成后通过 softirq 回调 endio 处理函数,若该函数因CPU调度延迟未能及时执行,会导致 IO 流程未真正完成。此时尽管硬件 IO 已结束,系统仍会表现为 block IO。以下是关键现象和排查方法:


关键特征与表现

  1. block IO 被 kworker 唤醒

    • endio 处理因调度延迟挂起时,block IO 的完成信号依赖 kworker 线程触发。若 kworker 线程处于 runnable 状态但长时间未被调度,IO 完成流程会被阻塞。
  2. eMMC 器件的 cmdq 线程延迟

    • 使用软件模拟 cmdq 的 eMMC 设备中,exe_cq 线程负责处理命令队列。若该线程 runnable 时间长,表明调度器未能及时分配 CPU 资源,导致 IO 延迟。

排查方法

检查调度延迟工具

  • 使用 ftraceperf sched 分析调度延迟:

    复制代码
    perf sched latency --sort max

    关注 kworkerexe_cq 线程的 wait timesch delay

监控线程状态

  • 通过 pstop 查看目标线程状态:

    复制代码
    watch -n 1 "ps -eo pid,comm,state,pcpu | grep -E 'kworker|exe_cq'"

    若线程长期处于 R(runnable)状态但 CPU 占用低,可能存在调度问题。

内核调度参数调整

  • 提高目标线程的调度优先级(需 root 权限):

    复制代码
    chrt -f -p 99 <PID>

    将线程设置为实时调度策略(SCHED_FIFO),优先级 99。

中断与 softirq 分析

  • 检查 softirq 处理是否被延迟:

    复制代码
    watch -n 1 "cat /proc/softirqs"

    HI_SOFTIRQTASKLET_SOFTIRQ 计数持续增长,可能存在软中断堆积。


优化建议

  1. 隔离 CPU 核心

    将关键线程(如 exe_cq)绑定到专用 CPU 核心,避免其他任务竞争:

    复制代码
    taskset -pc <CORE_ID> <PID>
  2. 调整内核配置

    启用 CONFIG_PREEMPTCONFIG_PREEMPT_RT,减少调度延迟。针对 eMMC 驱动,禁用 cmdq 的软件模拟(若硬件支持)。

  3. 监控工具链

    结合 sarvmstatiostat 监控系统负载与 IO 状态,确认是否因全局 CPU 过载导致调度延迟。


典型场景示例

  • 案例 1kworker 线程因高负载系统无法及时调度,导致 endio 未执行。
    解决 :通过 cgroups 限制其他进程的 CPU 配额,确保 kworker 获得足够资源。

  • 案例 2exe_cq 线程因实时性不足导致 eMMC 性能下降。
    解决 :切换线程调度策略为 SCHED_FIFO,并绑定到独立 CPU 核心。

2.4 :排查高IO负载的方法

IO带宽与阻塞IO的关系

IO带宽可以类比为马路的通行能力,当大量车辆同时行驶时,容易造成交通拥堵。类似地,当多个进程同时发起大量IO读写操作时,IO带宽可能成为瓶颈,导致阻塞IO(Blocking IO)现象。

高IO负载导致阻塞IO的原因

多个进程同时进行高频率的IO操作时,IO设备的处理能力可能无法满足需求。磁盘、网络等设备的吞吐量有限,当请求超过其处理能力时,IO操作会被排队等待,导致进程阻塞。

检查系统整体IO负载

使用iostat命令查看磁盘IO情况,重点关注%util(磁盘利用率)和await(IO平均等待时间)。若%util持续接近100%或await显著升高,表明存在高IO负载。

示例命令:

复制代码
iostat -x 1 5

定位高IO进程

通过iotoppidstat工具查看各进程的IO读写情况。iotop直接显示实时读写速率,pidstat可统计历史数据。

示例命令:

复制代码
sudo iotop -o
pidstat -d 1

统计IO读写量

使用脚本或工具统计特定时间段内的IO扇区读写量(1 sector=512字节)。例如通过/proc/<pid>/io获取进程级IO数据:

复制代码
cat /proc/<pid>/io | grep read_bytes

分析具体IO操作

通过strace跟踪进程的系统调用,或使用blktrace工具深入分析磁盘IO请求的延迟和类型(读/写)。

示例命令:

复制代码
strace -p <pid> -e trace=read,write
blktrace -d /dev/sda -o - | blkparse -i -

优化建议

  • 对频繁读写的小文件,考虑使用内存缓存(如tmpfs)。
  • 调整内核IO调度策略(如deadlinekyber),适用于高并发场景。
  • 使用ionice限制低优先级进程的IO带宽。

通过上述方法可系统性定位并缓解高IO负载问题。

解决高IO负载的方法

优化IO调度策略

调整操作系统的IO调度算法,例如Linux中可以选择deadlinenoopcfq调度器。不同的调度器适用于不同场景,如deadline适合减少IO延迟。

使用异步IO(AIO)

异步IO允许进程在发起IO请求后继续执行其他任务,而不必等待IO完成。例如Linux的libaio库可以实现非阻塞的IO操作,避免进程被阻塞。

增加缓存层

在应用层或系统层增加缓存,减少直接IO操作。例如使用redis缓存热点数据,或利用操作系统的页缓存(Page Cache)机制。

分散IO负载

将IO操作分散到不同的物理设备或时间段。例如使用RAID技术将数据分布到多块磁盘,或错峰执行批量IO任务。

监控与诊断工具

iostat命令可以监控系统的IO负载情况,观察设备的吞吐量和利用率。iotop工具能查看各个进程的IO使用量,帮助定位高IO进程。

复制代码
iostat -x 1  # 查看设备IO详细统计
iotop -o     # 显示活跃的IO进程

2.5 :排查进程读写文件的方法

优化blockio最彻底的办法就是不发起IO操作。因此当发生blockio时,我们需要知道进程在读写什么文件,文件读写是不是必须的?

使用strace跟踪系统调用 strace -p <PID> -e trace=file可以实时跟踪指定进程的文件操作,其中-e trace=file过滤出文件相关的系统调用(如open/read/write)。输出会显示文件名、操作类型及返回状态。

通过lsof查看已打开文件 lsof -p <PID>列出进程当前打开的所有文件描述符,包括文件路径、读写模式(如W表示写,R表示读)。结合-n禁用域名解析可加速输出。

检查/proc文件系统 ls -l /proc/<PID>/fd/显示进程的文件描述符符号链接,指向实际文件路径。cat /proc/<PID>/io提供进程的读写统计(如rchar/wchar)。

评估IO操作的必要性

分析业务逻辑 确认读写操作是否属于关键路径:日志文件写入可能异步延迟,而数据库事务文件同步则必须立即完成。结合应用文档或代码审查判断。

检查文件访问模式 高频小文件随机读写(如临时文件)可通过合并IO或内存缓存优化;大文件顺序读写(如媒体流)需调整预读策略。

使用性能工具定位瓶颈 iotop -oPa显示实时磁盘IO占用排名,blktrace分析块层延迟。若某文件的IO延迟显著高于其他,需针对性优化。

优化策略示例

减少非必要IO 日志文件改为缓冲写入或异步刷盘,临时文件使用内存文件系统(如tmpfs)。

合并与批量操作 将多次小写合并为单次大写(如writev系统调用),数据库启用批量提交而非单行提交。

调整IO调度与缓存 CFQ调度器适合机械盘,NVMe盘改用none调度器。增大vm.dirty_ratio让更多脏页缓存在内存。

代码层优化 内存映射文件(mmap)替代read/write,避免重复解析同一配置文件(如启动时加载到内存)。

优化 Block I/O 的深度策略

减少或消除不必要的 I/O 操作

分析进程的 I/O 行为,识别正在读写的文件是否属于关键路径。若非必需,可移除相关操作或调整其执行时机(如延迟加载)。例如,启动阶段的非核心日志文件读取可能被推迟。

高频访问资源的常驻内存

对频繁读写的系统库或数据文件,根据其访问频率和大小评估内存常驻(pinning)的可行性。通过 mlock 或类似机制将文件锁定在物理内存,避免重复磁盘访问。需注意内存开销与收益的平衡。

异步与非阻塞 I/O 模型

替换同步阻塞调用为异步接口(如 io_uringepoll)。任务队列化后由后台线程处理,主线程仅检查完成状态。适用于必须保留的 I/O 操作,减少直接阻塞时间。

缓存与预加载机制

实现应用层缓存(如 LRU 缓存热点数据)或利用操作系统预读(readahead)。对可预测的访问模式,提前加载数据至内存,避免运行时阻塞。

文件系统与硬件层优化

选择低延迟文件系统(如 XFS 对高频小文件更优),或使用 SSD 替代 HDD。调整内核 I/O 调度器(如 kyber 针对 NVMe)可进一步降低延迟。

五、Block I/O 的常见原因

Block I/O 的常见原因(非存储模块独占)

1. 文件系统或 VFS 层问题

  • 文件系统锁竞争(如 inode_lock 争用)会导致多个进程阻塞在元数据操作上。
  • 文件系统日志(如 ext4 的 journal)写入频繁,增加 I/O 延迟。

2. 内存压力与页面回收

  • 内存不足时触发直接内存回收(direct reclaim),可能阻塞进程执行同步 I/O。
  • Swap 频繁读写(kswapdswapin 高负载)会导致额外磁盘 I/O。

3. 进程调度与 I/O 优先级

  • 低优先级进程(如后台服务)的 I/O 请求可能被高优先级进程抢占,导致延迟上升。
  • CFQ 或 BFQ 等调度算法的配置不合理可能导致 I/O 带宽分配不均。

4. 应用层 I/O 模式问题

  • 小文件随机读写(如数据库未优化)会加剧磁盘寻道开销。
  • 同步 I/O(O_SYNC)或频繁 fsync() 强制刷盘,增加阻塞概率。

5. 硬件与驱动层因素

  • 磁盘本身的吞吐量或 IOPS 不足(如低端 eMMC)。
  • 驱动或固件缺陷导致 I/O 处理异常(如 NVMe 驱动超时)。

排查与优化建议

  • systrace 标记 :结合 trace_io_* 事件追踪具体进程的 I/O 行为。
  • 内存调优 :调整 vm.swappiness 或禁用 Swap 以减少回收压力。
  • I/O 调度器 :针对场景选择 deadline(低延迟)或 bfq(公平性)。
  • 文件系统优化 :如 ext4 关闭 barrier(需权衡安全性)或调整日志模式。

通过综合分析 systrace 中的阻塞点和上下文,可以更精准定位 Block I/O 的根本原因,而非仅归因于存储硬件。

【关注我,后续持续新增专题博文,谢谢!!!】

下一篇讲解:

相关推荐
余生皆假期-2 小时前
YuanHub 源码分析【一】FlashDB 初始化与项目应用
笔记·单片机·嵌入式硬件
Deitymoon2 小时前
STM32——串口通信发送数据
stm32·单片机·嵌入式硬件
玩转单片机与嵌入式2 小时前
嵌入式AI场景:哪些应用场景不适合将AI模型部署到单片机(MCU)中?
人工智能·单片机·嵌入式硬件
开开心心就好2 小时前
近200个工具的电脑故障修复合集
安全·智能手机·pdf·电脑·consul·memcache·1024程序员节
小妖6662 小时前
怎么用 tauri 创建编译 android 应用程序
android·tauri
懋学的前端攻城狮2 小时前
iOS 列表性能优化实战:从 45fps 到 60fps 的蜕变
ios·性能优化·ui kit
꯭爿꯭巎꯭3 小时前
星界智联APP下载手机版
智能手机
ellis19703 小时前
Unity UI性能优化一之插件【Unity UI Optimization Tool】
unity·性能优化
czwxkn3 小时前
8STM32(stdl)低功耗模式
stm32·单片机·嵌入式硬件