【关注我,后续持续新增专题博文,谢谢!!!】
上一篇我们讲了:
这一篇我们开始讲:
目录
[一、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_issue和block_rq_complete之间的时间间隔过长,表明 I/O 请求从发起到完成的耗时较高。- 频繁的 I/O 等待 :
wait_on_page_bit_common或io_schedule等事件频繁出现,表示进程因 I/O 阻塞而进入等待状态。- 磁盘队列堆积 :
blk_queue_busy或blk_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观察await和svctm差值,判断队列堆积或硬件延迟。ftrace/blktrace追踪具体I/O路径的耗时分布。vmstat 1检查内存回收频率与I/O关联性。优化方向:
- 底层异常需检查SMART健康状态或更换存储介质。
- 进程级问题可通过预加载、调整缓存策略或优化文件访问模式缓解。
四、Block I/O 问题分析SOP
确定底层IO性能是否异常的排查方法
平均单次IO耗时大于10ms
IO自身性能存在问题,需重点关注存储模块。检查存储硬件状态(如磁盘健康度、SSD磨损)、文件系统碎片化、RAID配置或存储驱动兼容性。必要时使用工具(如
iostat、blktrace)深入分析存储延迟分布。平均单次IO耗时小于10ms
IO自身无异常,按以下方向排查:
器件性能差异验证
使用AndroBench等基准测试工具对比目标设备与参考机的读写速度(顺序/随机)、IOPS。若差异显著,需检查存储芯片规格、控制器性能或固件版本。
内存占用分析
通过
free或proc/meminfo确认系统内存使用率。低内存可能导致频繁换页,触发额外IO。排查内存泄漏进程(如top或smem),或优化应用内存管理策略。调度问题定位
通过
systrace捕捉固定唤醒源及runnable线程阻塞时长。若存在长时间就绪态线程,需检查CPU负载均衡、锁竞争或优先级配置。调整调度策略(如CFS参数)或优化线程模型。进程IO负载检查
使用
iotop或/proc/pid/io统计各进程读写量。高IO负载进程需进一步分析其操作模式(如大量小文件写入)。考虑限制其IO带宽或优化数据访问逻辑。文件访问优化
通过
strace或lsof确认高频访问的文件路径。对必须频繁读写的文件采用内存映射(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 diskstats或iotop命令观察实时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 oom或journalctl -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_MemAvailable和node_disk_io_now等指标,设置阈值告警(如可用内存 <1GB)。
2.3 :IO 完成与调度延迟分析
IO 完成与调度延迟分析
IO 请求完成后通过 softirq 回调
endio处理函数,若该函数因CPU调度延迟未能及时执行,会导致 IO 流程未真正完成。此时尽管硬件 IO 已结束,系统仍会表现为 block IO。以下是关键现象和排查方法:
关键特征与表现
block IO 被 kworker 唤醒
- 当
endio处理因调度延迟挂起时,block IO 的完成信号依赖 kworker 线程触发。若 kworker 线程处于runnable状态但长时间未被调度,IO 完成流程会被阻塞。eMMC 器件的 cmdq 线程延迟
- 使用软件模拟 cmdq 的 eMMC 设备中,
exe_cq线程负责处理命令队列。若该线程runnable时间长,表明调度器未能及时分配 CPU 资源,导致 IO 延迟。
排查方法
检查调度延迟工具
使用
ftrace或perf sched分析调度延迟:
perf sched latency --sort max关注
kworker或exe_cq线程的wait time和sch delay。监控线程状态
通过
ps或top查看目标线程状态:
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_SOFTIRQ或TASKLET_SOFTIRQ计数持续增长,可能存在软中断堆积。
优化建议
隔离 CPU 核心
将关键线程(如
exe_cq)绑定到专用 CPU 核心,避免其他任务竞争:
taskset -pc <CORE_ID> <PID>调整内核配置
启用
CONFIG_PREEMPT或CONFIG_PREEMPT_RT,减少调度延迟。针对 eMMC 驱动,禁用cmdq的软件模拟(若硬件支持)。监控工具链
结合
sar、vmstat和iostat监控系统负载与 IO 状态,确认是否因全局 CPU 过载导致调度延迟。
典型场景示例
案例 1 :
kworker线程因高负载系统无法及时调度,导致endio未执行。
解决 :通过cgroups限制其他进程的 CPU 配额,确保kworker获得足够资源。案例 2 :
exe_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进程
通过
iotop或pidstat工具查看各进程的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调度策略(如
deadline或kyber),适用于高并发场景。- 使用
ionice限制低优先级进程的IO带宽。通过上述方法可系统性定位并缓解高IO负载问题。
解决高IO负载的方法
优化IO调度策略
调整操作系统的IO调度算法,例如Linux中可以选择
deadline、noop或cfq调度器。不同的调度器适用于不同场景,如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_uring、epoll)。任务队列化后由后台线程处理,主线程仅检查完成状态。适用于必须保留的 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 频繁读写(
kswapd或swapin高负载)会导致额外磁盘 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 的根本原因,而非仅归因于存储硬件。
【关注我,后续持续新增专题博文,谢谢!!!】
下一篇讲解: