1. 项目缘起与整体测试思路拆解
嵌入式平台上的存储性能,是一个被严重低估但又极其关键的指标。很多做嵌入式Linux的同行,在选型阶段把大量精力花在CPU算力、GPU性能、NPU的TOPS数值上,结果板子打回来一跑业务,发现系统卡顿、应用启动慢、数据写入延迟高,最后排查半天,问题出在存储子系统的带宽和IOPS上。我这次做的NXP iMX8存储性能测试,就是想把这块内容彻底摸清楚,给后续的项目选型和系统调优提供一份能直接参考的数据。
iMX8系列是NXP面向工业、车载、边缘计算场景的应用处理器,内部集成了多核Cortex-A核加实时核的异构架构,外设接口丰富,支持eMMC、SD卡、UFS、SATA、NVMe等多种存储介质。不同型号的iMX8在存储控制器配置上差异很大,比如iMX8M系列和iMX8QXP在USDHC控制器数量、PCIe通道数上就不一样。这意味着同一套测试方法在不同板子上跑出来的结果可能天差地别,所以测试方案必须先把硬件拓扑理清楚,再谈数据。
这次测试的核心目标有三个:第一,摸清板载eMMC的顺序读写和随机读写能力,这是系统盘的基础性能;第二,对比SD卡和eMMC在相同测试条件下的差距,为量产时存储介质的选择提供依据;第三,测试PCIe NVMe扩展存储的极限带宽,看看在高吞吐场景下iMX8的PCIe控制器能不能喂饱NVMe盘。这三个目标覆盖了从低成本方案到高性能方案的完整光谱,基本上一个嵌入式项目能用到的存储形态都包含进来了。
测试工具方面,我选择了fio作为主力工具,配合dd做快速验证,用hdparm看基础参数,用iostat做实时监控。fio的优势在于它能精确控制IO深度、块大小、读写比例、队列深度这些参数,而且输出格式规整,方便后续做数据对比。dd虽然简单,但在快速验证顺序读写带宽时非常直接,适合做第一轮摸底。这套工具组合在嵌入式Linux环境下部署方便,依赖少,交叉编译也不复杂。
整个测试的设计思路是分层递进:先做裸设备测试,排除文件系统的影响,拿到存储介质的理论性能上限;再做文件系统层测试,看ext4、f2fs这些常用文件系统在实际使用场景下的表现;最后做混合读写和长时间稳定性测试,模拟真实业务负载。这个递进逻辑很重要,因为很多人在裸设备上测出来的漂亮数据,一到文件系统层就大打折扣,如果不分层测试,根本定位不到瓶颈在哪。
注意:存储性能测试一定要在系统稳定运行、温度正常的前提下进行。iMX8在高负载下发热明显,如果散热没做好,eMMC和主控都可能触发降频,测出来的数据会偏低且不稳定。
2. 测试环境搭建与核心参数解析
2.1 硬件平台与存储介质选型
这次测试用的是一块基于iMX8M Plus的核心板,搭配的底板引出了eMMC、SD卡槽和M.2 Key M接口。eMMC是一颗32GB的eMMC 5.1颗粒,理论带宽400MB/s;SD卡槽支持UHS-I模式,用了一张64GB的Class 10 UHS-I卡;M.2接口通过PCIe Gen3 x1连接,挂了一块256GB的NVMe固态盘。内存是4GB LPDDR4,系统跑的是NXP官方Yocto构建的Linux 5.15内核。
这里有个细节值得展开说:iMX8M Plus的PCIe控制器是Gen3 x1,理论带宽8GT/s,实际可用带宽大概在985MB/s左右。这个带宽对于NVMe盘来说其实是瓶颈,因为主流NVMe盘的顺序读取都能跑到2000MB/s以上。所以这次测试NVMe的目的不是看盘本身的极限,而是看iMX8的PCIe控制器能发挥出多少,这个数据对于判断能不能用NVMe做高速数据采集的缓存盘非常关键。
eMMC这边,iMX8M Plus的USDHC控制器支持eMMC 5.1标准,理论带宽400MB/s,但实际能跑到多少取决于控制器的DMA效率、时钟配置和PCB走线质量。我见过不少板子因为eMMC走线没做好,实际带宽只有理论值的一半不到。所以测试之前,先用mmc extcsd read命令确认一下eMMC的工作模式和时钟频率,这个信息能帮你判断后续测出来的数据是否合理。
SD卡这边,UHS-I模式理论带宽104MB/s,但实际受限于SD卡本身的控制器性能和文件系统开销,能跑到80MB/s以上就算不错了。SD卡的优势在于可插拔和成本低,适合做数据导出和固件升级,但作为系统盘或者高频读写的数据盘,可靠性和性能都不如eMMC。
2.2 软件环境与工具部署
系统起来之后,第一件事是确认内核版本和存储驱动状态。用uname -a看内核版本,用dmesg | grep -i mmc看eMMC和SD卡的初始化日志,用lspci看NVMe盘有没有被正确识别。这几个命令的输出能帮你快速判断硬件是否正常工作。
fio的部署有两种方式:一种是在Yocto的recipe里加进去,直接编到根文件系统里;另一种是交叉编译一个静态版本,通过U盘或者网络传到板子上。我推荐第二种,因为静态编译的fio不依赖板子上的库版本,换板子也能直接用。交叉编译的时候记得把libaio的支持打开,fio的异步IO引擎依赖这个库,不开的话只能用同步IO,测出来的IOPS会偏低。
# 交叉编译fio的典型配置 ./configure --host=aarch64-poky-linux --enable-static --disable-shared make -j$(nproc)编译完成之后,把fio二进制拷到板子的/usr/bin/目录下,加个可执行权限就能用了。dd和hdparm一般系统自带,如果没有,用opkg或者apt装一下就行。iostat在sysstat包里,如果只是临时用,也可以直接读/proc/diskstats来算,不过iostat的输出更直观。
测试之前还要做一件事:把系统的缓存清掉,确保每次测试都是从存储介质读数据,而不是从内存缓存里读。用echo 3 > /proc/sys/vm/drop_caches这个命令,每次测试前执行一次。这个步骤很多人会忽略,结果测出来的读取速度高得离谱,其实是内存缓存在起作用。
提示:测试裸设备的时候,一定要确保设备没有被挂载。如果eMMC的系统分区正在使用,你没法直接对它做裸设备测试,这时候要么从SD卡启动系统,把eMMC完全空出来,要么用文件系统层的测试方法来间接评估。
2.3 关键测试参数的计算与选择
fio的参数很多,但核心的就那么几个:块大小(bs)、队列深度(iodepth)、读写模式(rw)、运行时间(runtime)。这几个参数怎么选,直接决定了测试结果能不能反映真实场景。
块大小方面,顺序读写用128K到1M,模拟大文件传输;随机读写用4K,模拟数据库和系统调用的IO模式。队列深度方面,裸设备测试用32,文件系统测试用16,模拟多线程并发访问。读写模式方面,先测纯读和纯写,再测70/30的混合读写,这个比例比较接近真实业务负载。
这里有个计算公式值得说一下:IOPS = 带宽 / 块大小。比如4K随机读跑到50000 IOPS,换算成带宽就是50000 * 4096 = 204.8MB/s。这个换算关系在对比不同块大小的测试结果时非常有用,能帮你快速判断数据是否合理。
还有一个参数是numjobs,也就是并发线程数。对于eMMC和SD卡这种单队列设备,numjobs设大了反而会因为锁竞争导致性能下降。我一般从1开始测,然后逐步加到4,看性能曲线的拐点在哪里。对于NVMe这种多队列设备,numjobs可以设到8甚至16,才能充分发挥盘的并行能力。
| 参数 | 裸设备测试 | 文件系统测试 | 说明 |
|---|---|---|---|
| bs | 128K/4K | 128K/4K | 顺序用大块,随机用4K |
| iodepth | 32 | 16 | 裸设备可以更深 |
| numjobs | 1-4 | 1-4 | 根据设备队列能力调整 |
| rw | read/write/randread/randwrite | 同左 | 先测纯读写,再测混合 |
| runtime | 60s | 60s | 太短不稳定,太长浪费时间 |
| ramp_time | 5s | 5s | 预热阶段,不计入结果 |
这个参数表是我经过多次测试之后总结出来的,基本上覆盖了嵌入式存储测试的常见场景。你可以根据具体需求微调,但大方向不会错。
3. 实操过程与核心环节实现
3.1 eMMC裸设备性能测试实录
裸设备测试是第一步,目的是拿到eMMC在没有文件系统开销情况下的真实性能。测试之前先确认eMMC的设备节点,一般是/dev/mmcblk0或者/dev/mmcblk2,具体看硬件设计。用lsblk命令可以清楚地看到设备树结构。
顺序读测试的命令如下:
# 清缓存 echo 3 > /proc/sys/vm/drop_caches # 顺序读测试 fio --name=seq_read --filename=/dev/mmcblk2 --rw=read --bs=128k \ --iodepth=32 --numjobs=1 --runtime=60 --ramp_time=5 \ --direct=1 --group_reporting --output-format=json这里--direct=1是关键,它绕过操作系统的页缓存,让IO直接打到存储设备上。如果不加这个参数,读测试会从内存缓存里读数据,结果完全不可信。--group_reporting让fio输出汇总结果而不是每个job的详细数据,看起来更清爽。
实测下来,这块eMMC的顺序读带宽稳定在280MB/s左右,顺序写带宽在120MB/s左右。这个数据符合eMMC 5.1的预期,读取性能接近理论带宽的70%,写入性能偏低是因为eMMC的写入放大和垃圾回收机制。写入过程中如果遇到eMMC内部做垃圾回收,带宽会突然掉到50MB/s以下,然后慢慢恢复。这个现象在长时间写入测试中特别明显,所以评估eMMC写入性能时,一定要看长时间的平均值,不能只看短时间的高峰。
随机读写测试把块大小换成4K,其他参数不变。实测4K随机读IOPS在8000左右,随机写IOPS在3000左右。这个数据对于eMMC来说算正常水平,但和NVMe动辄几十万的IOPS比起来,差距是数量级的。这也解释了为什么eMMC适合做系统盘和顺序读写为主的场景,不适合做数据库或者高并发随机读写。
实操心得:eMMC测试过程中,如果发现写入性能波动很大,可以用
mmc-utils工具查看eMMC的健康状态和磨损情况。命令是mmc extcsd read /dev/mmcblk2,输出里的EMMC_LIFE_TIME_EST_TYP_A字段能告诉你eMMC的寿命消耗情况。如果这个值超过0x05,说明eMMC已经用了不少寿命,性能下降是正常现象。
3.2 SD卡与eMMC的对比测试
SD卡的测试方法和eMMC一样,只是设备节点换成/dev/mmcblk1。测试之前先用cat /sys/block/mmcblk1/device/uevent确认一下SD卡的工作模式,看看是不是跑在UHS-I模式下。如果只跑在High Speed模式下,带宽上限只有25MB/s,测出来的数据会很难看。
实测这张UHS-I SD卡的顺序读带宽在85MB/s左右,顺序写带宽在45MB/s左右。4K随机读IOPS在2500左右,随机写IOPS在800左右。和eMMC相比,顺序读差距在3倍以上,随机读写差距在3到4倍。这个差距在系统启动和应用程序加载时体现得非常明显,用SD卡做系统盘的板子,开机时间通常比eMMC版本长一倍以上。
不过SD卡也有它的优势:可插拔、容量扩展方便、成本低。在一些对启动速度不敏感、但需要频繁导出数据的场景,比如数据采集终端,SD卡反而是更合适的选择。关键是要根据业务需求来选,而不是盲目追求高性能。
| 测试项 | eMMC 5.1 | SD卡 UHS-I | 差距倍数 |
|---|---|---|---|
| 顺序读 | 280MB/s | 85MB/s | 3.3x |
| 顺序写 | 120MB/s | 45MB/s | 2.7x |
| 4K随机读 | 8000 IOPS | 2500 IOPS | 3.2x |
| 4K随机写 | 3000 IOPS | 800 IOPS | 3.8x |
| 访问延迟 | 0.15ms | 0.8ms | 5.3x |
这个对比表很直观地展示了两种介质的性能差距。访问延迟这一项特别值得关注,eMMC的延迟只有SD卡的五分之一左右,这意味着在频繁小文件读写的场景下,eMMC的响应速度会快很多。如果你的应用涉及大量配置文件读取、日志写入、数据库操作,eMMC是更好的选择。
3.3 PCIe NVMe扩展存储的极限测试
NVMe测试是这次的重头戏。iMX8M Plus的PCIe Gen3 x1接口,理论带宽8GT/s,编码方式128b/130b,实际有效带宽约985MB/s。这个带宽就是NVMe盘在这块板子上的性能天花板,不管盘本身有多快,都突破不了这个限制。
测试之前先用lspci -vv确认NVMe盘的链路速度和宽度。如果显示的是Gen3 x1,说明链路协商正常;如果显示Gen2或者x1以下,那就要检查硬件设计了。我见过一些板子因为PCIe时钟或者复位信号处理不当,链路只能跑到Gen2,带宽直接减半。
NVMe的顺序读测试命令和eMMC类似,只是设备节点换成/dev/nvme0n1,队列深度可以设到64甚至128,因为NVMe支持多队列。实测顺序读带宽在920MB/s左右,已经非常接近PCIe Gen3 x1的理论上限了。顺序写带宽在850MB/s左右,也相当不错。
4K随机读IOPS跑到了120000左右,随机写IOPS在80000左右。这个数据虽然和NVMe盘在x86平台上的表现有差距,但考虑到PCIe x1的带宽限制,这个结果已经相当好了。说明iMX8M Plus的PCIe控制器和NVMe驱动配合得不错,没有明显的软件瓶颈。
注意:NVMe测试时板子的功耗和发热会明显增加,一定要确保散热片或者风扇工作正常。如果温度过高,NVMe盘会触发温度降频,性能直接腰斩。我测试的时候在NVMe盘上贴了一个小散热片,温度控制在50度以下,性能就很稳定。
3.4 文件系统层性能测试与对比
裸设备测试拿到的是理论性能,实际使用中文件系统的开销不可忽略。这次我对比了ext4和f2fs两种文件系统在eMMC上的表现。ext4是嵌入式Linux的默认选择,成熟稳定;f2fs是专门为闪存设计的文件系统,理论上对eMMC更友好。
测试方法是在eMMC上分一个独立分区,分别格式化成ext4和f2fs,然后用fio做文件级的读写测试。测试文件大小设为1GB,避免文件太小导致测试结果受元数据操作影响。
ext4的顺序读带宽在250MB/s左右,比裸设备低了约10%,这个开销主要来自文件系统的元数据管理和页缓存机制。顺序写带宽在100MB/s左右,比裸设备低了约17%。4K随机读IOPS在6000左右,随机写IOPS在2000左右,都比裸设备有明显下降。
f2fs的表现稍好一些,顺序读带宽在260MB/s左右,顺序写带宽在110MB/s左右,4K随机写IOPS在2500左右。f2fs的优势主要体现在随机写入和长期使用后的性能稳定性上,因为它的日志结构和垃圾回收机制更适合闪存介质。
| 测试项 | ext4 | f2fs | 裸设备 |
|---|---|---|---|
| 顺序读 | 250MB/s | 260MB/s | 280MB/s |
| 顺序写 | 100MB/s | 110MB/s | 120MB/s |
| 4K随机读 | 6000 IOPS | 6200 IOPS | 8000 IOPS |
| 4K随机写 | 2000 IOPS | 2500 IOPS | 3000 IOPS |
这个数据说明,文件系统的开销在10%到30%之间,具体取决于测试模式。顺序读写受影响较小,随机读写受影响较大。如果你的应用对随机读写性能敏感,可以考虑用f2fs,或者直接操作裸设备(当然这会牺牲文件系统的便利性)。
4. 常见问题与排查技巧实录
4.1 测试数据异常波动的排查思路
存储性能测试最让人头疼的就是数据不稳定,同一套命令跑两次,结果可能差20%以上。遇到这种情况,先别急着怀疑硬件,按下面的顺序排查。
第一步,检查系统负载。用top或者uptime看看有没有其他进程在占用IO。嵌入式系统里,日志服务、网络服务、甚至SSH连接都可能产生IO,干扰测试结果。最彻底的办法是进单用户模式,把不必要的服务都停掉。
第二步,检查温度。用cat /sys/class/thermal/thermal_zone0/temp看CPU温度,用smartctl -a /dev/nvme0n1看NVMe盘温度。如果温度超过阈值,主控或者存储颗粒会降频,性能自然不稳定。iMX8M Plus在满载时CPU温度能到80度以上,如果散热不好,eMMC控制器也可能受影响。
第三步,检查电源。存储设备在高速读写时功耗会明显增加,如果电源设计余量不足,电压跌落会导致性能下降甚至读写错误。用示波器看eMMC或者NVMe的供电纹波,如果纹波超过50mV,就要考虑加强滤波。
第四步,检查测试参数。ramp_time设得太短,测试还没进入稳态就结束了;runtime设得太短,结果受偶然因素影响大;direct=1没加,测的是内存缓存不是存储设备。这些参数问题很常见,但排查起来最容易被忽略。
避坑技巧:我习惯在每次测试前跑一个30秒的预热测试,让存储设备进入稳定状态,然后再跑正式测试。这个预热测试的数据不记录,只用来“热身”。实测下来,加了预热之后,测试结果的重复性明显提高。
4.2 eMMC写入性能骤降的原因分析
eMMC写入性能骤降是嵌入式开发中的经典问题。表现是:刚开始写入时带宽能到120MB/s,写了几百MB之后突然掉到30MB/s甚至更低,然后过一段时间又恢复。这个现象的根本原因是eMMC内部的垃圾回收机制。
eMMC的存储颗粒是NAND Flash,写入之前必须先擦除。当eMMC内部没有足够的空闲块时,主控就要做垃圾回收:把有效数据搬走,擦除旧块,腾出空间给新数据。这个过程中,写入性能会大幅下降。eMMC的容量越小、剩余空间越少,垃圾回收越频繁,性能下降越明显。
缓解这个问题的方法有几个:第一,选大容量的eMMC,留足够的剩余空间给垃圾回收做缓冲;第二,在文件系统层开启TRIM/DISCARD,让文件系统及时告诉eMMC哪些块可以回收;第三,避免长时间持续写入,给eMMC留出后台垃圾回收的时间窗口。
# 开启ext4的discard功能 mount -o remount,discard /dev/mmcblk2p2 /这个命令让ext4在删除文件时主动通知eMMC回收块,能有效缓解写入性能下降。不过discard会增加一些开销,对随机写入性能有轻微影响,需要根据实际情况权衡。
4.3 NVMe盘识别不到或性能不达标的处理
NVMe盘在iMX8上识别不到,通常有三个原因:PCIe链路没建立、驱动没加载、电源没供上。排查顺序如下。
先看lspci有没有输出。如果没有输出,说明PCIe链路没建立。检查设备树里PCIe节点有没有使能,status是不是okay。检查硬件上PCIe时钟有没有输出,复位信号时序对不对。iMX8的PCIe复位信号要求在上电后保持至少100ms,如果复位时间不够,链路可能协商失败。
如果lspci能看到设备但/dev/nvme0n1不存在,说明驱动没加载。检查内核配置里CONFIG_BLK_DEV_NVME有没有打开,nvme模块有没有编进内核或者自动加载。用dmesg | grep nvme看驱动加载日志,如果有报错信息,根据错误码进一步排查。
如果设备识别正常但性能不达标,先看链路速度。lspci -vv里看LnkSta字段,确认是Gen3还是Gen2。如果只有Gen2,检查设备树里PCIe的max-link-speed属性,有些默认配置是Gen2,需要手动改成Gen3。另外检查PCB走线,PCIe Gen3对信号完整性要求很高,走线阻抗不匹配或者长度超标都会导致链路降速。
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| lspci无输出 | PCIe链路未建立 | 检查设备树、时钟、复位时序 |
| 有lspci无块设备 | 驱动未加载 | 检查内核配置和dmesg日志 |
| 链路速度只有Gen2 | 信号完整性或配置问题 | 检查max-link-speed和PCB走线 |
| 性能远低于预期 | 温度降频或电源不足 | 检查温度和供电纹波 |
这张表基本上覆盖了NVMe在iMX8上常见的几类问题,按照这个顺序排查,大部分问题都能定位到。
4.4 文件系统层测试的注意事项
文件系统层测试和裸设备测试有几个关键区别,不注意的话很容易得出错误结论。
第一,测试文件的大小要足够大。如果测试文件只有几十MB,文件系统的缓存和预读机制会严重干扰结果。我一般用1GB到4GB的测试文件,确保测试过程中真正发生了磁盘IO。
第二,测试之前要清理文件系统缓存。echo 3 > /proc/sys/vm/drop_caches这个命令在文件系统层测试中同样重要,而且要在每次测试前都执行。另外,如果文件系统支持fsync,测试写入时最好加上--fsync=1参数,确保数据真正落盘。
第三,要注意文件系统的挂载参数。noatime能减少元数据写入,data=writeback能提高写入性能但降低数据安全性,barrier=0能减少同步开销但增加掉电丢数据的风险。这些参数对性能影响很大,测试时要记录清楚,否则数据没法对比。
第四,长时间测试后要检查文件系统的一致性。用fsck检查有没有错误,用dmesg看有没有IO错误或者文件系统报错。如果测试过程中出现文件系统错误,那性能数据就没有意义了,必须先解决稳定性问题。
实操心得:文件系统测试最好用独立的测试分区,不要用系统分区。系统分区的IO会被系统服务干扰,而且测试过程中如果写满了系统分区,可能导致系统崩溃。我一般会在eMMC上分一个2GB的测试分区,专门用来做文件系统性能测试。
5. 测试数据汇总与选型建议
把这次测试的所有数据汇总一下,方便做横向对比。测试平台是iMX8M Plus,4GB LPDDR4,Linux 5.15,fio 3.35,测试时间60秒,预热5秒,direct=1。
| 存储介质 | 顺序读 | 顺序写 | 4K随机读 | 4K随机写 | 访问延迟 |
|---|---|---|---|---|---|
| eMMC 5.1裸设备 | 280MB/s | 120MB/s | 8000 IOPS | 3000 IOPS | 0.15ms |
| eMMC 5.1 ext4 | 250MB/s | 100MB/s | 6000 IOPS | 2000 IOPS | 0.20ms |
| eMMC 5.1 f2fs | 260MB/s | 110MB/s | 6200 IOPS | 2500 IOPS | 0.18ms |
| SD卡 UHS-I | 85MB/s | 45MB/s | 2500 IOPS | 800 IOPS | 0.80ms |
| NVMe PCIe Gen3 x1 | 920MB/s | 850MB/s | 120000 IOPS | 80000 IOPS | 0.05ms |
从这张表可以得出几个很明确的结论。eMMC作为系统盘是合格的选择,顺序读写和随机读写都能满足嵌入式Linux的日常需求,f2fs比ext4略有优势但差距不大。SD卡只适合做数据导出和低频读写,作为系统盘会明显拖慢启动速度和应用程序响应。NVMe的性能是eMMC的三倍以上,随机读写更是几十倍的差距,适合高吞吐数据采集、边缘计算推理缓存、视频录制这些场景。
选型建议方面,如果项目对成本敏感、存储需求在32GB以内、读写模式以顺序为主,eMMC是性价比最高的选择。如果需要频繁导出数据、或者现场升级固件,加一个SD卡槽作为辅助存储。如果项目涉及高帧率视频采集、大规模数据缓存、或者需要跑数据库,那NVMe是必须的,eMMC的随机读写性能撑不住这些场景。
还有一个容易被忽略的点:eMMC的寿命。eMMC的擦写次数有限,TLC颗粒一般在3000次左右,QLC更低。如果应用涉及高频写入,比如日志记录、数据采集,一定要算一下每天的写入量,评估eMMC能用多久。用mmc extcsd read看寿命消耗,定期监控,快用完的时候及时更换或者优化写入策略。
我在实际项目里遇到过eMMC写坏的情况,设备跑了两年多,日志写入量太大,eMMC寿命耗尽,整块板子变砖。后来改成日志先写内存缓冲区,攒够一定量再批量写eMMC,写入量降了一个数量级,寿命问题就解决了。这个经验分享出来,希望大家在做存储选型的时候,不光看性能,也要看寿命和可靠性。
最后再分享一个小技巧:测试NVMe的时候,如果发现性能波动大,可以试试调整PCIe的ASPM电源管理策略。在/sys/module/pcie_aspm/parameters/policy里把策略设成performance,能减少链路进入低功耗状态的频率,对稳定性有一定帮助。不过这个设置会增加功耗,电池供电的设备要权衡一下。