在嵌入式Linux开发中,我们经常遇到一个令人沮丧的场景:你买了一张昂贵的、标称100MB/s(UHS-I U3)的SD卡,满怀期望地插到你的开发板(如RK3588, RV1126等)上,结果用dd一测,写入速度只有区区20MB/s。
这是卡的问题,还是板子的问题?
这篇博客将为你提供一个系统性的测试框架,帮助你从“卡片”、“控制器”、“驱动”到“文件系统”逐层排查,最终定位到真正的性能瓶颈。
核心思路:分层测试与瓶颈定位
我们的排查思路很简单:
-
基准测试:这张SD卡在“理想状态”(如PC)下的极限速度是多少?
-
板级识别检查:板子(SoC)是否以“最高速度模式”识别了这张卡?(这是最常被忽略的瓶颈点)
-
裸设备测试:Linux系统对SD卡物理块设备的顺序读写能力如何?(绕过文件系统)
-
文件系统测试:在文件系统上(如ext4)的随机和顺序读写性能如何?(模拟真实应用)
-
系统负载分析:在I/O测试时,CPU是否已经不堪重负?
第 1 步:建立基准 —— SD卡在PC上的极限速度
在怀疑板子之前,先确认卡是好的。
-
工具:一台带USB 3.0的电脑,一个高质量的USB 3.0读卡器。
-
软件:
CrystalDiskMark(Windows) 或dd/fio(Linux)。 -
目的:测出这张卡在“最佳环境”下的顺序和随机I/O性能。
在windows下测试的具体操作方法:
-
将SD卡插入USB 3.0读卡器,连接到PC的USB 3.0端口(通常是蓝色接口)。
-
下载并打开
CrystalDiskMark软件。 -
在软件界面的右上角,选择正确的盘符(例如
F:\)。 -
点击左上角的“All”按钮开始测试。
-
如何解读结果: 测试完成后,你会看到一张表格,但我们只需要关注两行:
-
SEQ1M Q8T1(或SEQ): 顺序读/写性能。这代表你拷贝单个大文件(如视频)时的速度。这个数字就是SD卡包装上标称的“100MB/s”的来源。这是你的“顺序I/O天花板”。 -
RND4K Q1T1(或4K): 4KB 随机单队列单线程读/写性能。这代表操作系统在卡上读写大量小文件(如系统日志、配置文件、数据库)时的速度。这个速度通常很低(例如 0.5MB/s ~ 3MB/s),但它对系统启动速度、应用打开速度等日常流畅度的影响,比顺序性能大得多!
-
-
把上面的测试结果记录下来
-
顺序天花板 (
SEQ1M Q1T1): 读95.95 MB/s/ 写38.08 MB/s-
(这模拟了你拷贝一个大电影文件的速度)
-
-
随机天花板 (
RND4K Q1T1): 读9.19 MB/s/ 写3.34 MB/s-
(这模拟了你系统启动和日常卡顿的程度)
-
在Linux下测试的具体操作方法
-
操作方法(假设SD卡设备为
/dev/sdb):警告:直接对
/dev/sdb操作会清除所有数据!建议对分区/dev/sdb1挂载后在文件系统上测试,或确保是测试卡。 -
模拟
CrystalDiskMark的测试(在文件系统上): 假设SD卡挂载在/media/user/sdcard:# 1. 测试顺序写 (类似 SEQ1M) fio --name=seqwrite --directory=/media/user/sdcard --size=1G \ --rw=write --bs=1M --direct=1 --ioengine=libaio --group_reporting # 2. 测试顺序读 (类似 SEQ1M) fio --name=seqread --directory=/media/user/sdcard --size=1G \ --rw=read --bs=1M --direct=1 --ioengine=libaio --group_reporting --fdatasync=1 # 3. 测试 4K 随机写 (类似 RND4K Q1T1) fio --name=randwrite --directory=/media/user/sdcard --size=256M \ --rw=randwrite --bs=4k --direct=1 --ioengine=libaio --group_reporting --numjobs=1 --iodepth=
记录测试结果
记下顺序读写速度和随机读写速度,把这份数据当作sd卡本身的最大性能。有了这份数据,当你的开发板在后续步骤中测出“顺序写 20.5MB/s”时,你就有了确凿的证据:性能损失了约 77%。你的排查目标将非常明确:找出丢失的这 69MB/s 到底被谁(DTS配置、驱动、CPU瓶颈)吃掉了。
第 2 步:关键排查 —— 板子究竟如何“看待”这张SD卡?
在 PC 上,我们验证了卡的“理想性能”。现在,我们要在板子上验证“实际性能”。性能不佳的第一个、也是最常见的瓶颈,就出在板子的SoC(系统级芯片)与SD卡之间的“沟通方式”上。
这个“沟通方式”就是 SD 卡总线速度模式 (Bus Speed Mode)。
1. 什么是“速度协商”?
当你把SD卡插入开发板时,一个由硬件和软件(内核驱动)共同参与的“协商”过程就开始了:
-
内核驱动 (mmc driver) 被唤醒,它通过SoC的SDMMC控制器(硬件)给卡上电。
-
驱动问卡:“你好,你是谁?你支持哪些速度模式?” (读取卡的CSD/SCR寄存器)
-
卡片回复:“我是UHS-I卡,我最快支持SDR104模式 (104MB/s),也兼容老的HS模式 (25MB/s) 等。”
-
驱动转头看自己的“配置图”(Device Tree),心想:“老板(DTS)告诉我,这块板子的硬件电路(pinmux, 供电)支持哪些模式?”
-
最终决策:驱动会取一个“交集”——在卡片支持的、且DTS允许的模式中,选择最高的那一个来运行。
瓶颈就在这里:如果你的卡支持 104MB/s,但你的DTS文件里压根没写 SDR104,或者干脆写了 no-1-8-v(禁止切换到UHS所需的1.8V电压),那么驱动就只能被迫选择那个“老的”、“慢的” 25MB/s HS 模式。
2. 如何检查协商结果?—— dmesg 内核日志
内核会在协商完成后,把结果打印到启动日志中。这是我们必须查看的第一手证据。
在板子启动后,或插入SD卡后,立刻在终端执行:
dmesg | grep -i "mmc"
提示:在很多开发板上(特别是Rockchip),mmc0 可能是板载的eMMC,而 mmc1 或 mmc2 才是外置的SD卡插槽。你需要根据日志上下文来判断哪个是你的目标。
你需要在这堆日志里,像侦探一样找出那行最关键的描述:
| dmesg 日志中看到的关键字 | 模式 | 信号电压 | 理论最高速度 | 性能等级 |
mmc0: new high speed ... card | HS (High Speed) | 3.3V | 25 MB/s | ❌ 慢 (最常见降速) |
mmc0: new high speed DDR50 ... card | DDR50 | 3.3V | 50 MB/s | ⚠️ 中等 (不常见) |
mmc0: new ultra high speed SDR50 ... | UHS-I SDR50 | 1.8V | 50 MB/s | ✅ 快 |
mmc0: new ultra high speed SDR104 ... | UHS-I SDR104 | 1.8V | 104 MB/s | ✅ 目标模式 |
mmc0: new ultra high speed HS200 ... | HS200 (eMMC/SD) | 1.8V | 200 MB/s | 🚀 极快 (需特定支持) |
3. 排查结果:定位瓶颈
现在,对比你的日志和上表:
情况A (坏):你看到了 new high speed card (25MB/s)
-
诊断:恭喜你,你100%找到了性能瓶颈!你的板子工作在了降速模式。无论你用
dd怎么测,速度上限都被锁死在了 25MB/s。 -
主要原因:Device Tree (DTS/DTB) 配置不当。
-
解决方案:
-
打开你的DTS文件(例如
rk3588-orangepi-5-plus.dts)。 -
找到对应的
sdmmc节点(例如&sdmmc0或&sdmmc1)。 -
检查:是否缺少UHS模式的属性?
-
必须有
bus-width = <4>;(4线模式)。 -
必须有
sd-uhs-sdr104;、sd-uhs-sdr50;等属性来声明支持UHS。
-
-
重点检查:是否错误地包含了
no-1-8-v;这个属性?-
这是一个“性能杀手”。它明确告诉内核“禁止切换到1.8V”。
-
所有UHS模式(SDR50/SDR104)都必须工作在1.8V。一旦禁止,驱动就只能退回到3.3V的
HS模式 (25MB/s)。
-
-
修改DTS,重新编译并烧录
dtb,然后重启,再次dmesg检查。
-
情况B (好):你看到了 new ultra high speed SDR104 (104MB/s)
-
诊断:非常好。这说明板子的硬件、DTS配置、内核驱动都已正确工作,并成功将SD卡协商到了最高速度模式。
-
下一步:如果在这种情况下,你的
dd或fio测试速度还是很慢(例如只有 40MB/s),那么瓶颈就不在DTS配置,而在别处。你需要进入后面的第3步(裸设备测试)和第4步(文件系统测试),去排查Linux驱动的I/O效率、文件系统开销、或者CPU瓶颈。
4. 高级技巧:实时检查 /sys 文件系统
dmesg 只记录了“启动时”的协商结果。如果你想实时查看当前卡片的工作状态,sysfs 是更好的工具。
# 1. 找到你的mmc主机 (假设是 mmc1)
cd /sys/class/mmc_host/mmc1
# 2. 你的卡信息在 mmc1:xxxx 目录中 (xxxx是卡片ID)
# 用 ls 找到它,例如:
ls
# 输出: mmc1:0001 ... (就进入这个目录)
cd mmc1:0001
# 3. 查看卡片信息
cat name # 卡片名字
cat cid # 卡片ID
cat csd # 卡片能力
cat scr # SD卡配置
# 4. 最关键的:回到上一层,看主机的I/O状态
cd ..
cat ios
执行 cat ios 后,你会看到类似这样的输出:
clock: 208000000 Hz (208 MHz)
actual clock: 200000000 Hz (200 MHz)
...
signal voltage: 1.80 V
timing spec: sd uhs sdr104
bus width: 4 bit
这份“体检报告”一目了然:时钟 200MHz(接近SDR104的208MHz),电压 1.80V,时序模式 sdr104,总线位宽 4 bit。这有力地证明了系统正处于最佳性能状态。
第 3 步:裸设备测试(绕过文件系统)
目的:这一步是“压力测试”。我们要搞清楚 SoC的SDMMC控制器 + Linux驱动 这条硬件路径的I/O吞吐极限。
为此,我们必须绕过文件系统(如ext4)这一层软件。文件系统会带来额外的开销,比如日志(journaling)、元数据更新、块分配策略等,这些都会干扰我们对底层硬件性能的判断。
工具:dd 操作:直接读写SD卡的块设备节点(如 /dev/mmcblk0)。
⚠️ 终极警告:这会立即、彻底地摧毁你SD卡上的所有数据! 你的分区表、所有文件都会瞬间消失。请只在100%确认是可牺牲的测试卡时才执行此操作。
# 假设 /dev/mmcblk0 是你的SD卡设备节点
# 1. 测试顺序写 (使用 direct 绕过缓存)
# if=/dev/zero: 一个虚拟的"数据源",以最快速度提供0字节数据
# bs=1M: 使用1MB的大块来测试顺序吞吐量
# count=100: 总共写入 100 * 1M = 100MB 数据
# oflag=direct:【关键】绕过Linux页面缓存(RAM),强制数据直接写入硬件
dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=100 oflag=direct
# 2. 测试顺序读 (先清空缓存)
# echo 3 > ...: 强制内核丢弃所有缓存,确保我们读的是物理卡
echo 3 > /proc/sys/vm/drop_caches
# iflag=direct:【关键】同样绕过缓存,强制从硬件读取
dd if=/dev/mmcblk0 of=/dev/null bs=1M count=100 iflag=direct
排查结果
-
如果裸设备速度(例如 90MB/s)接近第1步的基准(例如 95MB/s):
-
诊断:完美。你的硬件(SoC)、DTS配置、Linux驱动程序都工作正常且高效。
-
下一步:如果你的应用依然感觉慢,那么瓶颈 100% 在第 4 步,即文件系统或应用层。
-
-
如果裸设备速度(例如 40MB/s)远低于基准,但第2步的模式(SDR104)是正确的:
-
诊断:这是一个深层问题。说明瓶颈出在Linux的
sdmmc驱动效率(比如DMA未正常工作,降级为CPU拷贝的PIO模式)或SoC的SDMMC控制器硬件设计本身(它虽然协商上了SDR104,但内部总线就是跑不满)。这是最难解决的硬件/驱动问题。
-
第 4 步:板级性能测试(基于文件系统)
目的:现在我们在实际生产中的真实场景下进行测试。这一步测试的是你的应用程序(如跑在ext4上的Python脚本或C++程序)实际能体验到的读写速度。这一层包含了文件系统带来的所有开销。
工具一:dd (测试顺序读写) 这是对“拷贝大文件”场景的模拟
# 假设SD卡已格式化为ext4,并挂载在 /mnt/sdcard
# 1. 测试文件系统顺序写 (必须加 oflag=sync)
# oflag=sync:【最关键参数】
# 没有它, dd会把数据写入RAM缓存(速度飞快,几百MB/s)然后立即退出
# sync会强制dd等待,直到数据从RAM被真正刷入(commit)到物理SD卡上
# 这才是真实、安全的写入速度。
dd if=/dev/zero of=/mnt/sdcard/testfile bs=1M count=100 oflag=sync
# 2. 测试文件系统顺序读 (必须先清空缓存)
sudo echo 3 > /proc/sys/vm/drop_caches
dd if=/mnt/sdcard/testfile of=/dev/null bs=1M count=100
# 3. 清理
rm /mnt/sdcard/testfile
工具二:fio (测试随机读写) 系统启动、读写日志、数据库操作……这些都不是顺序读写,而是琐碎的随机I/O。dd 在这里毫无用处,fio 才能衡量这个“卡顿”的元凶。
# 假设 fio 已安装,在 /mnt/sdcard 目录下测试
# 1. 测试 4K 随机写入
# rw=randwrite: 随机写入模式
# bs=4k: 使用4K小块, 这是文件系统和OS最常用的I/O单元
# direct=1: 同样绕过缓存, 测算物理I/O
# size=256M: 在256MB大小的文件范围内进行随机读写
fio -directory=/mnt/sdcard -name=randwrite_test \
-ioengine=libaio -direct=1 -rw=randwrite -bs=4k \
-size=256M -numjobs=1 -runtime=60 -group_reporting
# 2. 测试 4K 随机读取
fio -directory=/mnt/sdcard -name=randread_test \
-ioengine=libaio -direct=1 -rw=randread -bs=4k \
-size=256M -numjobs=1 -runtime=60 -group_reporting
排查结果
fio 的结果会告诉你两个关键指标:
-
bw (Bandwidth):带宽 (MB/s)。
-
IOPS (Input/Output Operations Per Second):每秒读写次数。
-
如果第3步(裸)很快,但第4步
dd(文件)很慢:-
诊断:文件系统开销过大。
ext4的日志功能在写入时会带来不小的性能损失。 -
解决:可以尝试更换文件系统(如
f2fs,专为闪存优化),或者在挂载ext4时使用特定选项(如noatime,nodiratime,data=writeback)来减少开销(但这可能牺牲数据安全性)。
-
-
如果 4K 随机 IOPS 非常低(例如低于 100):
-
诊断:这直接解释了为什么你的系统启动慢、应用打开卡顿。
-
解决:这是闪存介质(SD卡)的物理特性。除了换一张A1/A2等级(明确标称高IOPS)的卡之外,别无他法。这也是为什么eMMC(通常IOPS更高)的系统体验远超SD卡。
-
第 5 步:终极排查 —— 是不是CPU在拖后腿?
有时候,卡和控制器都没问题,但I/O速度就是上不去。为什么?
因为CPU太忙了!
当I/O请求非常密集时,CPU需要花费大量时间来处理中断和数据拷贝(kworker进程或 irq/... 进程)。如果你在fio测试的同时,CPU的一个核已经被占满(100%),那么瓶颈就不在SD卡,而在CPU。
-
工具:
top或htop -
操作:
-
打开一个SSH终端,运行
top。 -
打开第二个SSH终端,运行
fio随机读写测试。 -
观察第一个终端
top的输出。
-
排查结果: 如果在fio运行时,你看到 %cpu 占用率极高,特别是 si (software interrupt) 或 wa (iowait) 飙升,或者某个 kworker 进程占满CPU,这说明:
板子的CPU处理能力已经跟不上SD卡的I/O速度了。
这不是SD卡的问题,而是系统的I/O子系统(可能是驱动,也可能是CPU本身)的瓶颈。
结论:我的诊断清单
当再遇到“SD卡慢”的问题时,请拿出这张清单:
-
[基准] 这张卡在PC上能跑多快?(测试sd卡自身的能力极限是多少)
-
[DTS]
dmesg显示的SD卡工作模式是什么?(SDR104还是HS?) -
[驱动]
dd读写裸设备/dev/mmcblk0速度快吗? -
[文件系统]
dd ... oflag=sync读写文件系统的速度快吗? -
[应用]
fio测试的 4K 随机IOPS是多少? -
[CPU]
fio运行时,top里的CPU是否已经 100% 占满了?
通过这一套组合拳,你几乎可以100%定位到SD卡性能的真正瓶颈。对于嵌入式Linux开发板来说,最常见的问题永远是第2步——Device Tree配置不当,导致SD卡工作在了降速模式。
2656

被折叠的 条评论
为什么被折叠?



