1. 为什么存储性能测试值得单独拎出来做
拿到一块 iMX8 的板子,大多数人第一反应是跑个 CPU 跑分、看看 GPU 渲染帧率,存储这块往往被一句“能启动就行”带过。但实际做过嵌入式项目的人都知道,存储性能才是决定系统响应速度和长期稳定性的隐形天花板。你系统启动慢、应用加载卡顿、数据库写入延迟高,很多时候不是 CPU 算力不够,而是 eMMC 或 SD 卡的读写带宽和 IOPS 拖了后腿。
iMX8 系列覆盖了从 iMX8QuadMax 到 iMX8M Mini 等多个型号,不同型号支持的存储接口差异很大——有的支持 eMMC 5.1 HS400,有的只到 HS200,SD 卡控制器版本也不一样。再加上实际板子上用的存储芯片品牌、容量、工艺批次不同,标称参数和实测结果之间经常有肉眼可见的差距。所以,存储性能测试不是走个过场,而是硬件选型、系统调优、量产验证三个环节都绕不开的硬功夫。
这篇内容适合谁看?如果你正在做 iMX8 平台的 BSP 移植、系统裁剪、应用性能优化,或者你是个刚接触嵌入式 Linux 的开发者,想搞清楚怎么科学地测一块板子的存储到底行不行,那接下来的内容应该能帮你省下不少翻文档和踩坑的时间。我会从测试方案设计、工具选型、实操步骤、数据解读到问题排查,把整个流程拆开讲清楚。
2. 测试方案的整体设计与思路拆解
2.1 先搞清楚你要测什么
存储性能测试不是跑一个 dd 命令看个数字就完事了。你得先明确测试目标:是测顺序读写带宽,还是随机读写 IOPS,还是混合读写场景下的延迟表现?这三个指标对应的实际场景完全不同。
顺序读写主要影响大文件传输、固件升级、视频录制这类连续数据流场景。随机读写 IOPS 则直接决定了系统启动时加载大量小文件的速度、数据库事务处理能力、以及应用冷启动的响应时间。混合读写更贴近真实使用——系统运行时既有日志写入又有配置读取,还有缓存刷新。
对于 iMX8 平台,我一般建议至少覆盖以下几个维度:
- eMMC 顺序读/写带宽(块大小 128KB 到 1MB)
- eMMC 随机读/写 IOPS(块大小 4KB,队列深度 1 到 32)
- SD 卡顺序读/写带宽(对比不同 UHS 模式)
- 文件系统层性能(ext4、f2fs、squashfs 等不同格式的差异)
- 长时间写入稳定性(持续写入 30 分钟以上看是否降速)
2.2 测试工具怎么选
嵌入式 Linux 下测存储性能,常用的工具就那么几个,但各有各的适用场景和坑。
dd是最简单的,几乎每个 BusyBox 里都有。但它的问题也很明显:默认只做顺序读写,而且受 page cache 影响极大,不绕过缓存的话测出来的数字好看但不真实。用iflag=direct和oflag=direct可以绕过缓存,但 BusyBox 的 dd 不一定支持这些 flag,得用完整版 coreutils 的 dd。
fio是功能最全的,支持顺序、随机、混合读写,能控制队列深度、块大小、运行时长,还能输出详细的延迟分布。但 fio 在嵌入式平台上编译需要依赖 libaio,而且运行时会占用一定 CPU 资源,在低端 iMX8M Mini 上可能影响测试结果。建议在 iMX8QuadMax 或 iMX8QuadXPlus 这类性能富余的型号上跑 fio,Mini 上可以用简化配置。
hdparm主要用于测硬盘,对 eMMC 和 SD 卡的支持有限,不太推荐。
bonnie++适合测文件系统层性能,但需要较大的测试空间和较长的运行时间,适合做对比测试而非快速验证。
iozone功能全面但操作复杂,在嵌入式环境下编译和运行都比较重。
我的建议是:快速验证用 dd + direct IO,深度分析用 fio,文件系统对比用 bonnie++。下面这张表可以帮你快速决策:
| 工具 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| dd | 快速顺序读写验证 | 简单、随处可用 | 功能单一、易受缓存影响 |
| fio | 全面性能分析 | 灵活、指标详细 | 编译依赖多、占CPU |
| bonnie++ | 文件系统对比 | 贴近实际文件操作 | 耗时长、占空间大 |
| iozone | 综合基准 | 测试项丰富 | 操作复杂、资源占用高 |
2.3 测试环境搭建的关键考量
测试之前,有几个环境因素必须先固定下来,否则数据没有可比性。
CPU 调频策略:iMX8 默认的 cpufreq governor 可能是 ondemand 或 schedutil,频率会动态变化。测试存储性能时,CPU 频率波动会影响中断处理和数据处理速度,建议临时把 governor 设为 performance,锁定最高频率。命令是echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor,对每个核心都执行一遍。
温度控制:eMMC 和 SD 卡在高温下会触发热保护降速。如果板子没有散热片,连续测试几分钟后温度可能就上去了。建议测试前确认散热条件,必要时加个小风扇或者间隔测试。
后台负载:确保测试期间没有其他进程在大量读写存储。可以用iostat或vmstat先观察一下基线负载。另外,系统日志服务可能会周期性写盘,测试前最好临时停掉或者把日志级别调高。
文件系统状态:新格式化的分区和用了一段时间的分区,性能表现可能不同。eMMC 的 FTL 层需要一定时间来建立映射表,刚擦除的块写入性能可能偏低。建议先做一次全盘写入预热,再开始正式测试。
3. 核心细节解析与实操要点
3.1 eMMC 与 SD 卡的接口差异
iMX8 的 eMMC 接口通常走 uSDHC 控制器,支持到 HS400 模式,理论带宽 400MB/s(8 位总线,200MHz 时钟,双沿采样)。但实际能跑到多少,取决于几个因素:eMMC 芯片本身是否支持 HS400、PCB 走线信号完整性、以及 BSP 里的时序配置是否正确。
SD 卡这边,iMX8 一般支持到 UHS-I SDR104,理论带宽 104MB/s。但注意,SD 卡控制器和 eMMC 控制器可能是同一个 uSDHC 的不同实例,共享带宽资源。如果你同时挂了一个高速 eMMC 和一个 SD 卡,两者同时读写时可能互相影响。
检查当前接口模式的命令:
# 查看 eMMC 当前速度模式 cat /sys/kernel/debug/mmc0/ios # 查看 SD 卡当前速度模式 cat /sys/kernel/debug/mmc1/ios输出里会显示clock、bus width、timing spec等字段。如果 timing spec 显示的是mmc hs200而不是mmc hs400,说明要么芯片不支持,要么配置没生效。这时候测出来的带宽上限就被卡在 200MB/s 了。
3.2 块大小与队列深度的选择逻辑
块大小(block size)直接决定了单次传输的数据量和命令开销的比例。块太小,命令开销占比高,带宽上不去;块太大,对于随机读写场景又不现实,因为实际应用很少一次读写 1MB 的随机数据。
对于顺序读写测试,我一般从 4KB 开始,逐步增加到 1MB,观察带宽变化曲线。通常在 128KB 到 256KB 之间会达到峰值,再大就趋于平缓。对于随机读写,固定 4KB 块大小,因为这是大多数文件系统和数据库的默认页大小。
队列深度(queue depth)影响的是控制器的并发处理能力。eMMC 5.1 支持命令队列(CQ),可以同时处理多个读写请求。队列深度设为 1 时,测的是单命令延迟;设为 32 时,测的是控制器的并发吞吐能力。实际应用中,文件系统的 readahead 和 writeback 机制会自然形成一定的队列深度,所以测试时至少覆盖 QD1 和 QD32 两个点。
3.3 绕过缓存与不绕过缓存的区别
这是最容易踩坑的地方。Linux 的 page cache 会把最近读写的数据缓存在内存里,如果你用 dd 不加 direct flag,第二次读同一块数据时可能直接从内存返回,测出来的“读带宽”其实是内存带宽,能到几个 GB/s,完全失真。
写测试更隐蔽:dd 写数据时,数据先写到 page cache 就返回了,实际还没落到存储上。你以为写完了,其实后台还在慢慢刷。要测真实写入性能,必须用oflag=direct绕过缓存,或者写完后执行sync并计时。
但 direct IO 也有代价:它要求块大小和偏移量按扇区对齐,而且对于某些文件系统,direct IO 的性能可能比 buffered IO 还差,因为失去了合并和预读的优化。所以我的做法是:顺序读写用 direct IO 测真实带宽,随机读写用 buffered IO 加 fio 的 fsync 控制来测实际 IOPS。
3.4 测试数据的可重复性保障
存储性能测试最怕的就是数据不可重复。同样的板子,今天测出来写 150MB/s,明天变成 120MB/s,你就不知道是板子有问题还是测试方法有问题。
保证可重复性的几个要点:
- 每次测试前清空 page cache:
echo 3 > /proc/sys/vm/drop_caches - 使用固定大小的测试文件,提前用
dd或fio创建好 - 测试文件放在独立分区,避免和其他数据混在一起
- 记录测试时的环境温度、CPU 频率、内核版本
- 每个测试项至少跑 3 次,取平均值和标准差
注意:
drop_caches只清 page cache 和 dentry/inode cache,不影响 eMMC 内部的缓存。eMMC 芯片自己也有缓存,连续测试时后面的结果可能受前面影响。建议每个测试项之间间隔 30 秒以上,让存储芯片有时间完成内部垃圾回收。
4. 实操过程与核心环节实现
4.1 环境准备与基线检查
先确认系统信息,把该记录的都记录下来:
# 内核版本 uname -a # 查看存储设备 lsblk cat /proc/partitions # 查看 eMMC 详细信息 cat /sys/block/mmcblk0/device/name cat /sys/block/mmcblk0/device/manfid cat /sys/block/mmcblk0/device/serial # 查看当前速度模式 cat /sys/kernel/debug/mmc0/ios然后锁定 CPU 频率:
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance > $cpu done检查当前频率是否生效:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq4.2 用 dd 做快速顺序读写测试
先创建一个测试文件,大小建议 512MB 到 1GB,太小了统计意义不够,太大了测试时间太长:
# 创建测试文件(buffered 写入,先不测性能) dd if=/dev/zero of=/data/testfile bs=1M count=512 conv=fsync然后清缓存,做 direct 读测试:
sync echo 3 > /proc/sys/vm/drop_caches # 顺序读测试 dd if=/data/testfile of=/dev/null bs=1M count=512 iflag=direct记录输出中的速度值。重复 3 次,取平均。
写测试:
sync echo 3 > /proc/sys/vm/drop_caches # 顺序写测试 dd if=/dev/zero of=/data/testfile bs=1M count=512 oflag=direct conv=fsync注意conv=fsync确保数据真正落盘后再返回。如果不加这个,dd 返回时数据可能还在缓存里。
4.3 用 fio 做全面性能分析
fio 的配置文件方式比命令行参数更清晰,也方便保存和复用。下面是一个典型的顺序读测试配置:
[global] ioengine=libaio direct=1 runtime=60 time_based=1 group_reporting=1 filename=/data/fio_testfile size=512M [seq-read] bs=128k rw=read numjobs=1 iodepth=8随机读写测试配置:
[global] ioengine=libaio direct=1 runtime=60 time_based=1 group_reporting=1 filename=/data/fio_testfile size=512M [rand-read] bs=4k rw=randread numjobs=1 iodepth=32 [rand-write] bs=4k rw=randwrite numjobs=1 iodepth=32运行:
fio seq-read.fio fio rand-read-write.fiofio 的输出里重点关注这几个指标:
bw:带宽,单位可以是 KB/s 或 MB/siops:每秒 IO 操作数lat (usec):延迟,看 avg 和 99.00th 百分位clat:完成延迟,比总延迟更能反映存储本身性能
4.4 文件系统层对比测试
如果你在选型阶段纠结用 ext4 还是 f2fs,可以做个对比测试。方法是在 eMMC 上分两个分区,分别格式化成不同文件系统,然后用相同的 fio 配置跑一遍。
# 格式化测试分区 mkfs.ext4 /dev/mmcblk0p3 mkfs.f2fs /dev/mmcblk0p4 # 挂载 mkdir -p /mnt/ext4 /mnt/f2fs mount /dev/mmcblk0p3 /mnt/ext4 mount /dev/mmcblk0p4 /mnt/f2fs然后修改 fio 配置里的 filename 指向不同挂载点,分别运行。
根据我的经验,ext4 在顺序读写上通常略优于 f2fs,但 f2fs 在随机写入和长期使用后的碎片化控制上表现更好。如果你的产品是监控设备、行车记录仪这类持续写入场景,f2fs 更合适;如果是通用计算平台,ext4 的兼容性和工具链支持更成熟。
4.5 长时间写入稳定性测试
这个测试最容易被忽略,但恰恰是量产中最容易出问题的地方。eMMC 在持续写入时,FTL 层需要不断做垃圾回收,当空闲块耗尽时,写入性能会断崖式下降。
测试方法:用 fio 做持续 30 分钟的顺序写入,每秒记录一次带宽:
[global] ioengine=libaio direct=1 runtime=1800 time_based=1 filename=/data/stability_test size=1G bs=1M rw=write iodepth=4 [write-stability]同时开另一个终端,每秒采样一次:
while true; do cat /sys/block/mmcblk0/stat >> /tmp/emmc_stat.log sleep 1 done测试结束后分析带宽曲线。如果前 5 分钟能跑 150MB/s,后面掉到 30MB/s,说明 eMMC 的垃圾回收机制跟不上,这种芯片在高负载写入场景下会出问题。
5. 常见问题与排查技巧实录
5.1 测出来的带宽远低于标称值
这是最常见的问题。iMX8 配 HS400 eMMC,标称 400MB/s,实测只有 80MB/s,差了好几倍。排查思路按优先级来:
第一步,确认速度模式。看/sys/kernel/debug/mmc0/ios里的 timing spec。如果是mmc hs200甚至mmc hs,那带宽上限就被卡住了。HS200 单沿 200MHz 8bit 理论 200MB/s,实际能到 150MB/s 左右;HS400 双沿理论 400MB/s,实际 250-300MB/s。
第二步,检查总线宽度。bus width 应该是 8 bit。如果显示 4 bit,要么是硬件设计只接了 4 根数据线,要么是设备树配置错了。
第三步,看时钟频率。clock 应该是 200000000(200MHz)。如果只有 50000000,说明高速模式没使能。
第四步,检查设备树配置。iMX8 的 uSDHC 节点里,bus-width、non-removable、no-sd、no-sdio这些属性要配对。eMMC 还要配置mmc-hs400-1_8v或mmc-hs200-1_8v。
第五步,排查信号完整性。如果配置都对但速度上不去,可能是 PCB 走线问题。eMMC 的 CLK、CMD、DAT 线需要等长匹配,阻抗控制在 50 欧姆。这个问题在打样阶段就要用示波器看眼图,量产阶段基本没法改。
5.2 随机读写 IOPS 异常低
4KB 随机读 IOPS 只有几百,正常 eMMC 应该在 5000 以上。可能的原因:
- 队列深度没设对。iodepth=1 时 IOPS 受限于单命令延迟,eMMC 单命令延迟大概 100-200us,算下来 IOPS 也就 5000-10000。要测并发能力,把 iodepth 提到 32。
- 没有启用命令队列。eMMC 5.1 的 CQ 功能需要在设备树里使能,并且内核配置要打开
CONFIG_MMC_CQHCI。检查/sys/kernel/debug/mmc0/下有没有 cq 相关节点。 - 文件系统开销。如果测试文件在 ext4 上,元数据操作会拖累随机 IOPS。可以试试直接对块设备测试(fio 的 filename 设为
/dev/mmcblk0p3),排除文件系统影响。 - CPU 瓶颈。iMX8M Mini 的 CPU 性能有限,处理中断和 IO 调度可能成为瓶颈。用
top看一下测试时 CPU 占用率,如果某个核心跑满 100%,说明是 CPU 限制而非存储限制。
5.3 写入测试时系统卡死或报错
这个比较危险,可能是硬件问题也可能是软件配置问题。
内核日志:dmesg | tail -50看有没有 mmc 相关的错误,比如mmc0: Timeout waiting for hardware interrupt或mmcblk0: error -110 transferring data。-110 是 ETIMEDOUT,说明命令超时,通常是信号质量或时序配置问题。
电源问题:eMMC 在高速写入时电流需求较大,如果电源纹波超标或者供电不足,会导致写入失败。用示波器看 VCC 和 VCCQ 的电压是否稳定。
温度问题:如果板子摸起来烫手,可能是过热保护。eMMC 一般 85 度以上会降速,105 度以上可能拒绝写入。加散热片或者降低环境温度再试。
文件系统错误:如果之前测试过程中断电过,文件系统可能损坏。用fsck.ext4 -f /dev/mmcblk0p3修复后再测。
5.4 不同批次板子性能差异大
量产阶段最头疼的问题。同一款 eMMC,不同批次测出来带宽差 20% 以上。可能的原因:
- eMMC 芯片批次不同。不同批次的 NAND 晶圆,读写速度本身就有差异。这个只能通过来料检验筛选。
- PCB 阻抗控制不一致。板厂每次压合的阻抗可能有偏差,影响高速信号质量。
- 焊接质量。BGA 封装的 eMMC,如果回流焊温度曲线不对,可能有虚焊或冷焊,导致信号完整性下降。
- 测试环境温度不同。夏天和冬天车间温度差 20 度,eMMC 性能也会有差异。
应对策略:制定明确的测试标准,比如顺序读不低于 200MB/s,随机写 IOPS 不低于 3000。每批板子抽检 5%,低于标准的批次退回或降级使用。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 带宽远低于标称 | 速度模式未使能 | 查 /sys/kernel/debug/mmc0/ios | 改设备树、检查硬件 |
| 随机 IOPS 低 | 队列深度不足 | 确认 iodepth 设置 | 提高 iodepth、启用 CQ |
| 写入超时 | 信号完整性差 | dmesg 看 -110 错误 | 查 PCB 走线、降速测试 |
| 持续写入降速 | 垃圾回收跟不上 | 看长时间写入曲线 | 换更高规格 eMMC |
| 批次差异大 | 芯片或 PCB 不一致 | 抽检多批次对比 | 制定来料标准 |
| 系统卡死 | 电源或温度问题 | 测电压、摸温度 | 改电源设计、加散热 |
实操心得:测试 eMMC 性能时,我习惯先用
dd快速摸个底,如果 direct 读能到 200MB/s 以上,说明硬件基本没问题,再用 fio 做精细测试。如果 dd 就卡在 80MB/s,别急着上 fio,先把速度模式和设备树查清楚,大概率是配置问题。
6. 数据解读与性能优化建议
6.1 怎么判断测试结果是否正常
拿到一组数据后,怎么知道是好是坏?我一般用以下几个基准来判断:
对于 iMX8QuadMax 配 HS400 eMMC(比如常见的 32GB 或 64GB 规格):
- 顺序读:250-300MB/s 算正常,低于 200MB/s 需要排查
- 顺序写:150-200MB/s 算正常,低于 100MB/s 需要排查
- 4KB 随机读 IOPS(QD32):8000-15000 算正常
- 4KB 随机写 IOPS(QD32):3000-8000 算正常
- 4KB 随机读延迟(QD1):100-200us 算正常
对于 iMX8M Mini 配 HS200 eMMC:
- 顺序读:150-180MB/s
- 顺序写:80-120MB/s
- 4KB 随机读 IOPS(QD32):5000-10000
SD 卡这边,UHS-I SDR104 模式下:
- 顺序读:80-95MB/s
- 顺序写:60-80MB/s(取决于卡本身)
- 4KB 随机读 IOPS:2000-5000
如果实测值只有基准的一半,先别怀疑板子,检查测试方法。direct IO 有没有加?缓存有没有清?CPU 频率有没有锁?这些因素排除后再看硬件。
6.2 从测试数据反推优化方向
测试的目的不是得到一个数字,而是指导优化。如果顺序读达标但随机写很差,说明存储芯片的随机写入能力弱,可以考虑:
- 在文件系统层启用 writeback 模式,合并小写入
- 应用层加写缓存,批量提交
- 换用带 DRAM 缓存的 eMMC 芯片(如果有的话)
如果顺序写前期快后期慢,说明垃圾回收策略有问题,可以:
- 预留更多 OP(over-provisioning)空间,减少 GC 压力
- 调整文件系统的 discard 策略,及时回收无效块
- 在应用层做写入限速,避免瞬间打满
如果 SD 卡性能波动大,可能是卡的质量问题。不同品牌的 SD 卡,甚至同品牌不同容量,性能差异都很大。建议在选型阶段多测几张卡,选性能稳定且一致性好的型号。
6.3 内核配置对存储性能的影响
有几个内核配置项会直接影响存储性能,值得关注:
IO 调度器:iMX8 默认可能是mq-deadline或none。对于 eMMC 这种自带队列管理的设备,none通常性能最好,因为不需要内核再做排序。可以通过echo none > /sys/block/mmcblk0/queue/scheduler切换。
readahead 大小:默认可能是 128KB,对于顺序读场景,增大到 512KB 或 1MB 可以提升吞吐。echo 1024 > /sys/block/mmcblk0/queue/read_ahead_kb。
文件系统挂载选项:ext4 用noatime可以减少元数据写入,data=writeback比data=ordered性能更好但安全性略低。f2fs 可以调整gc_urgent和gc_idle参数来平衡性能和寿命。
eMMC 缓存策略:有些 eMMC 支持 cache flush 和 reliable write,在设备树里配置mmc-cache-enable可以提升写入性能,但要注意断电数据安全。
6.4 测试报告应该包含什么
如果你要把测试结果交给团队或客户,一份完整的报告应该包含:
- 硬件平台信息:SoC 型号、eMMC/SD 卡型号和容量、PCB 版本
- 软件环境:内核版本、文件系统类型、挂载选项
- 测试工具和版本:fio 版本、dd 来源
- 测试条件:CPU 频率、环境温度、是否清缓存
- 原始数据:每个测试项的多次结果,不要只给平均值
- 分析结论:哪些指标达标、哪些不达标、可能的原因
- 优化建议:针对不达标项的具体改进方向
我见过太多人只丢一个“读写 200MB/s”的结论,结果别人复现不出来。把测试条件写清楚,数据才有参考价值。
7. 一些踩坑之后的个人体会
做 iMX8 存储性能测试这几年,最大的感受是:测试方法比测试工具重要,环境控制比测试数据重要。同样一块板子,不同的人测出来的数字可能差一倍,问题往往出在缓存没清、频率没锁、温度没控这些细节上。
另外,不要迷信标称参数。eMMC 厂商 datasheet 上写的 400MB/s 是理论峰值,实际能跑到 70% 就算不错了。SD 卡更是重灾区,标称 U3 的卡实际写入可能只有 30MB/s。选型阶段一定要实测,而且要多测几批样品。
最后,存储性能测试不是一次性的活。系统升级、内核更新、文件系统调整之后,都值得重新跑一遍基线测试。我习惯在项目关键节点都留一份测试数据,后面出问题时可以快速对比,判断是硬件老化还是软件改动导致的性能变化。
这个内容后续还可以这样扩展:加入 PCIe NVMe 存储的对比测试,或者针对特定应用场景(比如视频录制、数据库)做定制化的 IO 模式测试。如果你有这方面的需求,可以沿着这个框架继续往下做。