ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

eMMC是闪存不是内存:存储与内存差异及健康度排查

eMMC是闪存不是内存:存储与内存差异及健康度排查

1. 先把结论摆在桌面上:eMMC 是闪存,不是内存

这个问题我在技术群里见过不下二十次。有人拿着板子问“这颗 eMMC 是 8G 的内存吗,能不能跑程序”,也有人在选型表里把 eMMC 和 LPDDR 填在同一栏,还有人把设备卡顿归咎于“eMMC 内存不够”。每次我都得从头解释一遍:eMMC 是闪存,属于非易失性存储(Storage),不是内存(Memory/RAM)。它和 DDR、LPDDR 这类真正意义上的内存,从物理结构、访问方式到在系统里承担的角色,完全是两码事。

这个混淆不是外行才犯。存储与内存这两个词在日常语境里被混用得厉害——手机参数页写“8+256”,前者是内存后者是存储,但销售话术里常常统称“内存”;电脑城说“加个内存条”,其实加的是 DRAM;而 eMMC 因为焊在板子上、容量又不大不小,最容易被贴上“板载内存”的标签。名称里带个“memory card”的历史包袱,更是雪上加霜。

我写这篇东西的目的很直接:把 eMMC 这颗芯片拆开讲清楚它内部长什么样,把存储与内存在硬件层面的本质差异一条条摆出来,再给出一套能直接上手的识别、测试、健康度检查方法。不管你是刚接触嵌入式的新人、负责硬件选型的工程师,还是被“内存不足”和“存储不足”折磨过的系统调试者,看完至少能在下次会议上不再把这两个词混着用,也能在设备出问题时快速判断到底该查哪一边。

2. 从“仓库”和“工作台”说起:存储与内存的本质区别

2.1 断电之后还记不记得,是第一道分水岭

要理解这两类器件的差别,最省事的类比是仓库和工作台。内存是工作台,程序运行时要用的指令和数据必须摊在台面上,CPU 伸手就能拿;存储是仓库,东西放在里面长期保存,用的时候得先搬到工作台上。工作台断电就清空,仓库断电东西还在。

技术上的说法叫易失性(Volatile)与非易失性(Non-Volatile)。DRAM 每个存储单元是一个晶体管加一个电容,数据以电荷形式存在电容里。电容会漏电,所以内存控制器必须每隔几十毫秒把所有单元重新读出来再写回去,这个动作叫刷新(Refresh)。一旦断电,刷新停止,电荷在毫秒级内泄漏干净,数据全部消失。这也是为什么服务器断电后内存里的内容无法恢复,而硬盘里的文件还在。

NAND Flash 走的是另一条路。它用浮栅晶体管或者电荷陷阱结构把电子关在一个绝缘层包围的“笼子”里,写进去的电荷没有外部电压驱动就跑不掉。断电之后电荷依然保留,数据保持期在常温下通常标称十年量级(消费级产品,工业级会做更严的验证)。eMMC 内部用的就是 NAND Flash 颗粒,所以它的数据是掉电不丢的。这一点决定了它在系统里的定位:存放操作系统镜像、应用程序、配置文件、日志、用户数据,而不是给 CPU 当临时草稿纸。

注意:非易失不等于绝对安全。NAND 的电荷会随时间缓慢泄漏,高温会加速这个过程;读取次数过多也会引起读干扰(Read Disturb)。所谓“十年保持”是在特定温度和擦写次数下的典型值,不是承诺。

2.2 速度差着数量级,寻址方式根本不同

第二道分水岭是访问方式和延迟。CPU 访问 DRAM 是按字节随机寻址,内存控制器把物理地址映射到具体的行、列、bank,一次读写的延迟在几十纳秒量级。CPU 的 load/store 指令可以直接打到内存地址上,不需要经过任何中间层。现代处理器的多级缓存、预取器、乱序执行,全都是围绕“内存可以字节寻址且延迟低”这个前提设计的。

NAND Flash 完全不具备这个特性。它是块设备,读写的最小单位是页(Page),常见 4KB、8KB、16KB;擦除的最小单位是块(Block),通常由几百个页组成,几 MB 量级。你没法只改一个字节,想改一个字节也得把整页读出来、改完、写到新页、再把旧页标记为无效。读一次页的延迟在几十微秒,写一次几百微秒,擦一个块要几毫秒——和 DRAM 差了三到四个数量级。而且 NAND 有“先擦后写”的硬约束,不能原地覆盖。

正因为这个差异,eMMC 内部必须带一个控制器,把主机的“读写某个逻辑地址”翻译成“操作哪个物理页、什么时候触发垃圾回收、坏块怎么跳过”。主机看到的是一块连续的、可以按扇区读写的线性空间,背后全是控制器在替它擦屁股。这个抽象层叫 FTL(Flash Translation Layer)。DRAM 不需要 FTL,因为它是真正随机可写的;NAND 需要,代价是延迟不确定、写放大和寿命损耗。

2.3 容量、成本与寿命的三重取舍

第三层差异体现在工程取舍上。DRAM 单位容量成本高,但速度快、无写入寿命限制(理论上可以无限次改写),所以容量通常做得小,几个 GB 到几十 GB,够当前运行的进程用就行。NAND 单位容量成本低得多,可以堆到几百 GB 甚至 TB 级,但有 P/E 循环限制——每个存储单元的擦写次数是有限的。TLC 颗粒典型值在一千到三千次,MLC 三千到一万次,SLC 能到五万到十万次。

这个寿命限制直接催生了一堆工程手段:磨损均衡(Wear Leveling)把写操作分散到全盘,避免某几个块被写穿;垃圾回收(GC)整理无效页;预留空间(Over-Provisioning)留出一部分容量专门给控制器调度;写放大(Write Amplification)则是最需要盯住的指标——主机写 1GB,颗粒实际可能写了 2GB 甚至更多,寿命就按实际写入量消耗。

内存这边没有这些烦恼,但有另一套约束:刷新功耗、容量密度、带宽。LPDDR 系列为了省电把工作电压一降再降,刷新策略也做了优化,但本质上还是那套电容存储。所以你在系统设计里看到的典型分工是:内存按“够用且带宽足”配,存储按“够存且寿命够”配。两者的容量比例在嵌入式设备上常见是 1:4 到 1:8. 比如 2GB 内存配 16GB eMMC,4GB 配 32GB。

2.4 一张表把两类器件钉死

对比维度内存(DRAM/LPDDR)闪存(eMMC/UFS/SSD)
数据保持断电即失,需周期性刷新断电保持,典型十年量级
寻址单位字节/字,随机访问页读写、块擦除,经控制器映射
访问延迟几十纳秒读几十微秒,写几百微秒,擦除毫秒级
带宽量级十几到几十 GB/s几百 MB/s(eMMC HS400 上限 400MB/s)
写入寿命无实际限制P/E 循环有限,TLC 约 1k~3k 次
单位容量成本高低
系统角色运行现场:代码、堆栈、缓存、页表长期仓库:系统镜像、应用、数据、日志
掉电风险数据丢失但不损坏器件可能丢数据、损坏映射表、产生坏块
典型容量512MB ~ 64GB4GB ~ 512GB(eMMC 常见 8~64GB)

这张表建议存下来。以后但凡有人把两者混为一谈,把表甩过去比解释十分钟管用。

3. eMMC 到底是什么:拆开这颗 BGA 看内部

3.1 名字里的 MMC 是总线,不是颗粒

eMMC 全称 Embedded MultiMediaCard,直译是“嵌入式多媒体卡”。名字里的 MultiMediaCard 指的是它沿用的那套串行总线协议和命令集,最早是为可插拔存储卡设计的。加上 Embedded 前缀,意思是把这套接口固化成一顆 BGA 封装芯片,直接焊在板子上,不再需要卡槽。

这里有个关键认知:eMMC 是一整套子系统,不是一颗裸 NAND 颗粒。一颗典型的 eMMC 芯片内部至少包含三部分——NAND Flash 存储阵列(可能是一颗或多颗 Die 堆叠)、闪存控制器、以及符合 MMC 协议的接口逻辑。有些产品还会在里面集成电压调节、温度传感器。主机侧看到的只是 MMC 总线上的一个设备,通过 CMD、DAT、CLK 这几组信号跟它通信。

总线宽度可以从 1 位、4 位扩展到 8 位,时钟频率随版本演进不断提高。eMMC 4.5 引入 HS200(200MHz 单沿采样,8 位宽,理论 200MB/s),eMMC 5.0 引入 HS400(200MHz 双沿采样,理论 400MB/s),5.1 又加了 HS400 Enhanced Strobe 提升信号完整性。实际产品能跑到的顺序读通常在 250~300MB/s,顺序写 100~150MB/s,具体看控制器能力和颗粒制程。这个带宽和 LPDDR 动辄十几 GB/s 完全不在一个层级,这也是它只能当仓库、不能当工作台的根本原因。

3.2 控制器才是 eMMC 的灵魂

很多人以为 eMMC 就是把 NAND 封起来,性能寿命全看颗粒。这个理解偏差很大。真正决定一颗 eMMC 好不好用的,是里面那个控制器,以及它跑的固件。

控制器要干的事包括:把主机发来的逻辑块地址翻译成物理页地址;维护逻辑到物理的映射表;执行磨损均衡,把写操作摊到全盘;在后台做垃圾回收,把零散的有效页搬出来腾出整块;管理坏块,出厂坏块要标记,使用中新产生的坏块要隔离;处理掉电,在电压跌落时尽量把映射表刷进安全区;响应主机的 TRIM/Discard 命令,把已删除的块标记为可回收;还有 BKOPS(后台操作)的调度——在总线空闲的时候偷偷做 GC,避免主机等着。

这些机制带来了两个必须理解的后果。第一,eMMC 的性能不是恒定的。刚出厂、空闲空间充足时写起来飞快;写满到 80% 以上,GC 压力上来,写入速度可能掉到原来的三分之一甚至更低。第二,寿命是按颗粒实际写入量算的,不是按主机写入量。写放大系数高的时候,主机写 100GB,颗粒可能已经被写了 300GB. 这也是为什么监控 eMMC 健康度不能只看自己写了多少。

提示:选型时别只盯着标称顺序读写。厂商数据手册里的随机写 IOPS 往往是在特定队列深度和空闲状态下测的,实际工况下的持续写入性能才是关键指标。有条件的话一定要拿到样品自己压测。

3.3 内部区域的划分:Boot、RPMB、User Data

一颗 eMMC 的地址空间不是一整块。按规范它被划分成几个逻辑区域,每个区域的用途和访问权限都不一样。

Boot Area Partition通常有两个,各 1MB 到 4MB 不等,专门用来存放启动代码。很多 SoC 上电后不是从 User 区取第一条指令,而是直接从 eMMC 的 Boot 分区读,这样可以避开完整的存储栈初始化,启动更快。Boot 分区一般可以通过写 EXT_CSD 里的 PARTITION_CONFIG 寄存器来切换启用哪一个,以及设置启动时的总线宽度。

RPMB(Replay Protected Memory Block)是一个带重放保护的小分区,容量通常 512KB 到 4MB. 它不能随便读写,每次写入需要先用一个预置密钥计算出 HMAC 签名,同时内部有一个单调递增的写计数器。读取时返回数据连同计数器,主机可以校验是不是被回滚过。这个东西主要用来存密钥、证书、指纹特征这类敏感小数据。它的写寿命比普通区短,规范里通常标称十万到百万次量级,绝对不能拿来当普通存储频繁写。

General Purpose Partition最多可以有四个,大小可配置,用来做数据隔离或者给不同系统分区。

User Data Area是剩下的全部空间,也就是主机在操作系统里看到的那个主分区,通常再被划分成 boot、rootfs、data 等逻辑分区。

EXT_CSD是一个 512 字节的寄存器表,所有配置和状态都从这里读写。启动模式、总线宽度、高速模式、分区使能、写保护、缓存开关、寿命估计,全在里面。会用mmc-utils读这个表,基本就掌握了 eMMC 的“体检报告”。

3.4 速度等级与总线模式:从 legacy 到 HS400

eMMC 的性能演进基本就是总线模式的演进史。早期 legacy 模式只有 26MHz 时钟、1 位或 4 位数据线,理论带宽二十几 MB/s,放到今天连日志都写得费劲。后来一步步往上爬:

模式时钟数据线采样方式理论带宽
Legacy26MHz1/4/8 bit单沿约 26MB/s
High Speed52MHz8 bit单沿约 52MB/s
HS200200MHz8 bit单沿(SDR)200MB/s
HS400200MHz8 bit双沿(DDR)+ 数据选通400MB/s
HS400ES200MHz8 bit双沿 + 增强选通400MB/s,信号更稳

要跑到 HS400 不是把寄存器一改了事。硬件上需要 eMMC 和 SoC 之间的走线尽可能等长、阻抗匹配,数据选通信号(DS)要单独处理,电源纹波要控制住。我见过不少板子标称支持 HS400,实测只能稳定跑在 HS200,一跑高速就出现 CRC 错误重传,速度反而更慢。调试这类问题得从信号完整性下手,先降速验证功能,再逐步提升时钟观察误码,最后用示波器看眼图。

另外一个容易被忽略的点是温度和电压对速度的影响。高温下 NAND 的编程窗口变窄,写入变慢;低压时驱动能力下降,高速模式更容易出错。工业级产品会把这些裕量做进去,消费级就不一定了。

4. 把 eMMC 和它旁边的兄弟区分开

4.1 eMMC 与 UFS:并行与串行的代差

UFS 是 eMMC 的继任者,定位相同但架构不同。eMMC 用的是并行总线,半双工,读写不能同时进行,命令队列能力有限(5.1 才引入 Command Queue,深度也不大)。UFS 改用 M-PHY 串行差分链路,全双工,支持多命令队列,读写可以并发,带宽从 UFS 2.1 的每通道 1.2GB/s 量级一路做到 UFS 4.0 的数 GB/s。

打个比方,eMMC 像一条双向两车道的省道,同一时间只能一个方向通车;UFS 像双向八车道高速,还带匝道分流。两者的差距在顺序大文件传输上可能只有两三倍,但在随机小 IO 和混合读写场景下能差出一个数量级。这也是中高端手机早就换 UFS 的原因——多任务、应用启动、相册加载,全是随机读写。

但 eMMC 并没有被淘汰。它的成本低、布线简单、生态成熟、功耗控制容易,在工业控制、车载娱乐、智能家居、机顶盒、低端平板这些场景里依然是主力。选型逻辑很朴素:如果产品的 IO 压力不大、成本敏感、开发周期紧,eMMC 仍然是划算的选择。

4.2 eMMC 与 NVMe SSD:焊死与插拔的不同活法

NVMe SSD 走 PCIe 总线,协议栈更现代,队列深度可以到几万,延迟比 eMMC 低一个数量级。但它是插拔式的,通常做成 M.2 或 U.2 形态,面向的是标准化的整机市场。eMMC 是焊死的,一旦选定型号和容量,后期改版就得动 PCB.

焊死有焊死的好处:省空间、抗振动、BOM 里一颗料搞定、不需要额外的连接器和散热设计,整机成本低。坏处也明显:容量无法升级,出了问题只能返修,而且不同批次的芯片可能来自不同原厂,性能表现有差异,必须做兼容性验证。我踩过一次坑,同一块板子换了一批 eMMC,同样的固件启动时间多了两秒,最后发现是新批次控制器对 BKOPS 的调度策略不同,导致 boot 阶段的后台操作抢了总线。这种问题在插拔式方案里几乎遇不到,因为用户可以自己换盘。

4.3 eMMC 与 LPDDR/DRAM:同板不同命

这两者经常出现在同一块板子上,位置相邻,封装相似,最容易被搞混。区别前面讲过,这里再补一个实操层面的判断方法:看它在系统启动流程里的角色。

内存是 CPU 上电后最先要初始化的东西。Boot ROM 跑起来之后第一件事就是配置内存控制器,把 DRAM 训练到能稳定工作,然后把 bootloader 搬到内存里执行。整个过程中内存是“被执行代码的地方”。如果内存有问题,设备根本起不来,串口通常一个字都不打印,或者打印到一半乱码。

eMMC 是 bootloader 起来之后才去访问的。它存着 bootloader 本身、内核镜像、根文件系统。如果 eMMC 有问题,典型表现是启动到某个阶段卡住、报读取超时、或者进入恢复模式,但之前的内存初始化日志是完整打出来的。

这个判断顺序非常实用。设备起不来先查内存(包括焊接、供电、时序参数),能跑到一半再挂就重点查 eMMC 和它后面的链路。

4.4 eMMC 与 SPI NOR:容量差一个数量级

还有一种常见组合是 SPI NOR Flash 加 eMMC. SPI NOR 容量小(几 MB 到几十 MB),接口简单(SPI 四线),可靠性高,适合存 bootloader 和关键参数;eMMC 容量大,适合存系统和数据。两者不是替代关系而是互补关系。设计上通常让 SoC 从 SPI NOR 启动,初始化完 eMMC 之后再切过去。

这里有个常见的认知误区:看到板子上有一颗小 Flash 就以为系统跑在它上面,看到 eMMC 就以为它是内存。实际分工要看启动流程和分区表。拿到一块陌生板子,第一件事应该是读原理图和设备树,搞清楚存储拓扑,而不是靠猜。

5. 动手实测:在 Linux 下把 eMMC 摸清楚

5.1 认出设备节点和容量

Linux 下 eMMC 通常被识别为mmcblk设备。第一个控制器上的设备是mmcblk0,它的分区是mmcblk0p1、mmcblk0p2,如果有 RPMB 还会有mmcblk0rpmb。先跑几条命令确认基本情况:

# 看块设备拓扑 lsblk -o NAME,SIZE,TYPE,MOUNTPOINT # 看设备型号、CID 等信息 cat /sys/block/mmcblk0/device/name cat /sys/block/mmcblk0/device/type cat /sys/block/mmcblk0/device/cid # 看当前协商的总线模式和时钟 cat /sys/kernel/debug/mmc0/ios

ios那个文件是关键,里面会打印 clock、bus width、timing spec。如果 timing spec 显示的是mmc high speed (52MHz)而你预期是 HS200,那说明要么设备树配置没生效,要么硬件链路有问题。我排查过一例,设备树里明明写了mmc-hs200-1_8v,实际跑在 high speed,最后发现是供电一直给的是 3.3V,没切到 1.8V 信号电平,HS200 根本没法协商上去。

5.2 读 EXT_CSD 看寿命和状态

mmc-utils是必装工具。最常用的是读 EXT_CSD:

# 完整导出 512 字节的 EXT_CSD mmc extcsd read /dev/mmcblk0 # 只关心寿命相关的字段 mmc extcsd read /dev/mmcblk0 | grep -Ei "life|eol|pre_eol"

输出里最该盯的是两个字段。PRE_EOL_INFO表示寿命预警状态:0x01 正常,0x02 表示已消耗 80% 寿命,0x03 表示已消耗 90%,需要尽快安排更换。DEVICE_LIFE_TIME_EST_TYP_A和TYP_B分别对应 SLC 区和 TLC/MLC 区的寿命估计,取值 0x01 到 0x0A 对应 10% 到 100% 的消耗,0x0B 表示超出设计寿命。

# 也可以直接读 sysfs cat /sys/block/mmcblk0/device/pre_eol_info cat /sys/block/mmcblk0/device/life_time

这两个值在产线老化测试和现场设备巡检里特别有用。我们给现场设备加了个定时任务,每天采集一次写进日志,一旦pre_eol_info变成 0x02 就触发告警。比起等设备真的写坏了再返修,提前几个月发现能省下大量售后成本。

注意:不同原厂对寿命估计的算法不一样,这个值只能作为趋势参考,不要当成精确的剩余寿命。有的产品在 50% 消耗时就开始报 0x02,有的到 90% 才报。真正可靠的判断还是结合写入量统计和现场故障率。

5.3 分区与 RPMB 的操作边界

Boot 分区和 User 区是分开的,切换访问需要用mmc bootpart或者直接改 PARTITION_CONFIG. 这个操作有风险:如果写到一半掉电,或者把 PARTITION_CONFIG 写坏,设备可能再也起不来。

# 查看当前 boot 分区配置 mmc extcsd read /dev/mmcblk0 | grep -i boot # 使能 boot0 分区并设为主启动(示例,务必确认器件手册) mmc bootpart enable 1 1 /dev/mmcblk0 # 读 boot 分区原始内容做备份 dd if=/dev/mmcblk0boot0 of=/tmp/boot0_backup.bin bs=512k

RPMB 的操作门槛更高,需要先做认证。规范里写数据要提供 256 位密钥计算出的 MAC,密钥要在量产时烧进去且不可读出。如果密钥丢了,这块 RPMB 就等于废了。所以量产流程里密钥管理必须单独设计,不能跟固件一起明文存放。

# 读 RPMB 写计数器(不需要认证) mmc rpmb read-counter /dev/mmcblk0rpmb

计数器是单调递增的,可以用来检测是否被回滚。我们有个产品用它来防止降级攻击——每次启动校验计数器的值不小于上次记录,如果变小就拒绝启动。

5.4 性能实测:顺序写和随机写差在哪

评估一颗 eMMC 到底能不能撑住业务负载,光看手册没用,得自己压。我一般分三步。

第一步测顺序写,看持续吞吐:

# 写入 1GB 数据,direct 模式绕开页缓存 dd if=/dev/zero of=/data/testfile bs=1M count=1024 oflag=direct # 读回来测顺序读 dd if=/data/testfile of=/dev/null bs=1M count=1024 iflag=direct

第二步用 fio 测随机读写,这才是真实业务的写照:

fio --name=randwrite --filename=/data/fiotest --direct=1 \ --rw=randwrite --bs=4k --size=512M --numjobs=1 \ --iodepth=32 --time_based --runtime=120 --group_reporting fio --name=randread --filename=/data/fiotest --direct=1 \ --rw=randread --bs=4k --size=512M --numjobs=1 \ --iodepth=32 --time_based --runtime=120 --group_reporting

第三步最关键:在磁盘被写满 80% 以上的状态下重测一遍。很多 eMMC 的空盘性能和满盘性能能差三倍以上。如果产品的实际使用场景就是会把存储写满(比如持续录像、日志堆积),那必须按满盘状态的数据来评估,否则上线就翻车。

测试项空盘典型值接近写满时典型值说明
顺序写100~150 MB/s30~60 MB/sGC 压力上升导致
顺序读250~300 MB/s200~280 MB/s读性能受影响较小
4K 随机写3000~8000 IOPS500~2000 IOPS波动大,取决于 GC 时机
4K 随机读8000~15000 IOPS6000~12000 IOPS相对稳定
写延迟 P99几毫秒几十到上百毫秒后台 GC 抢占的典型表现

5.5 一个小脚本定期采集健康度

现场设备没法天天趴上去看,写个脚本丢进 crontab 每天跑一次,把关键指标记下来:

#!/bin/bash DEV=/dev/mmcblk0 LOG=/var/log/emmc_health.log TS=$(date '+%Y-%m-%d %H:%M:%S') PRE_EOL=$(cat /sys/block/mmcblk0/device/pre_eol_info 2>/dev/null) LIFE=$(cat /sys/block/mmcblk0/device/life_time 2>/dev/null) RPMB_CNT=$(mmc rpmb read-counter ${DEV}rpmb 2>/dev/null | awk '{print $NF}') USED=$(df -h /data | awk 'NR==2 {print $5}') echo "$TS pre_eol=$PRE_EOL life=$LIFE rpmb_cnt=$RPMB_CNT data_used=$USED" >> $LOG # 简单轮转,防止日志本身把盘写满 tail -n 2000 $LOG > ${LOG}.tmp && mv ${LOG}.tmp $LOG

这个脚本本身写得很少,但坚持采集三个月,你就能画出寿命消耗曲线,推算还剩多久需要更换。我手上有个项目就是这么做的,三年下来发现某个批次的 eMMC 消耗速度是老批次的 1.8 倍,追查下去是固件里一个日志模块的刷盘策略变了,改完就恢复正常。没有这条曲线,这个问题大概率要等到批量返修才会暴露。

6. 真实场景里的坑:内存不足和存储不足怎么区分

6.1 现象相似、根因相反的两个案例

这两类故障在用户侧的表象很像:设备变卡、应用闪退、操作无响应。但排查方向完全相反,判断错了白折腾半天。

内存不足的典型特征是:系统开始频繁换页(如果开了 swap),free显示的 available 持续走低,dmesg里出现Out of memory: Killed process,进程被 OOM killer 干掉。日志里会有oom-killer相关记录,被杀的通常是占用最大的那个进程。这时候去看cat /proc/meminfo和ps aux --sort=-%mem,能找到具体是谁在吃内存。常见原因是缓存没释放、内存泄漏、或者并发数配得太大。

存储不足的特征是:写文件返回No space left on device(errno 28),df -h显示某个分区 100%,应用报错通常是写入失败、配置保存不了、日志丢失。这时候问题很明确,要么清理,要么扩容量,要么改写入策略。麻烦的是另一种情况——df显示还有空间,但写入极其缓慢甚至超时,这往往是 eMMC 进入 GC 挣扎状态,空闲块耗尽,每次写都要先搬数据。这种情况用iostat -x 1看%util和await,会发现磁盘利用率接近 100% 而吞吐很低。

现象内存不足存储不足/劣化
系统卡顿全局性,切进程都慢集中在读写文件的操作
报错信息OOM killer、分配失败ENOSPC、IO 超时
关键指标free/meminfo 异常df/iostat 异常
进程表现被 kill 或卡在缺页卡在 write/fsync
重启后立刻缓解短暂缓解,很快复发
排查命令dmesg、ps、smemdf、iostat、mmc extcsd read

6.2 eMMC 掉速的几种典型诱因

eMMC 用久了变慢,原因不止一个,得按顺序排。

空闲空间不足是最常见的。控制器需要预留空间做 GC 和磨损均衡,一旦主机把盘写满,控制器没有腾挪余地,每次写都要现场搬数据,性能断崖式下跌。解决办法是保留至少 10%~20% 的空闲空间,或者在文件系统层开启 discard,让已删除的块及时归还。ext4 可以用-o discard挂载,或者定期跑fstrim。f2fs 对闪存更友好,自带后台 GC.

写放大过高是第二个。小文件随机写、频繁 fsync、数据库的日志模式,都会让写放大飙升。1 字节的修改可能触发整页重写,再加上 GC 搬迁,实际写入量是主机写入量的好几倍。用iostat对比主机的写请求量和 eMMC 内部消耗(部分器件支持通过 vendor command 读),或者简单点,看寿命消耗速度是不是远超预期。

温度过高是第三个,尤其在密闭机箱或者夏天户外设备上。NAND 在高温下编程更慢,控制器也可能主动降频保护。表现为持续写入一段时间后速度骤降,凉下来又恢复。这种情况要么改散热,要么降低写入负载,要么选宽温器件。

信号完整性问题是第四个,通常表现为 CRC 错误重传。看内核日志里的mmc0: Timeout waiting for hardware interrupt或者mmc_blk_err,如果频繁出现,说明总线不稳定。降速能缓解,但根治要改硬件。

6.3 写放大与寿命提前耗尽的排查路径

寿命消耗异常快,一般不是颗粒本身的问题,而是上层用法的问题。我按这个顺序查:

先看是不是有高频小写入。日志模块每写一行就 fsync 一次,这是经典杀手。改成批量写、定时刷盘,能降一个数量级。数据库的synchronous=FULL也是同理,非关键业务可以调成 NORMAL.

再看文件系统选型。ext4 的 journal 会带来额外写入,f2fs 针对闪存优化过,随机写表现更好。挂载参数里noatime能省掉大量元数据更新,commit=60把日志提交间隔拉长。

然后看有没有反复写同一区域。比如某个状态文件每秒更新一次,位置固定,磨损均衡再好也会被局部写穿。改成写入 tmpfs 再定期落盘,或者用追加写代替覆盖写。

最后才是怀疑器件。如果以上都排除了,寿命还是掉得离谱,那可能是买到了翻新片或者 downgrade 片,也可能是控制器固件有 bug. 这时候拿同批次样品做对比测试,或者换一家供应商验证。

提示:判断写放大有个土办法。记录一段时间内的主机写入量(/proc/diskstats里的 sectors written),同时记录 eMMC 寿命消耗的百分比,反推颗粒实际写入量,两者相除就是近似写放大。虽然不精确,但用来发现异常足够了。

7. 选型与采购的实操清单

7.1 容量之外必须看的四个参数

采购 eMMC 最容易犯的错是只看容量和价格。实际上至少有四个参数会直接影响产品表现。

接口版本和速度模式。5.1 和 4.5 差的不只是带宽,还有命令队列、Cache、可靠写这些特性。如果主控只支持到 4.5,买 5.1 的片子是浪费;反过来主控支持 HS400 却配了只到 HS200 的片子,性能跑不满。下单前把两边的手册对照一遍。

耐久等级和 TBW。厂商一般不直接给 TBW,而是给 P/E 次数或者按特定 workload 的寿命估算。自己算的话:总写入量 ≈ 容量 × P/E × 有效率 / 写放大系数。比如 32GB TLC、P/E 3000、写放大按 2 算,理论 TBW 约 48TB. 这个数要跟产品的设计寿命对齐,别出现硬盘寿命比整机短的情况。

工作温度范围。消费级通常是 0~70℃,工业级 -25~85℃ 或 -40~85℃。温度不只影响能不能开机,还影响数据保持和写入性能。户外设备、车载、工控必须按宽温选,并且做高温老化验证。

封装尺寸和引脚。BGA 封装有 153 ball、100 ball 等多种规格,尺寸和球距不一样,PCB 焊盘设计完全不同。选型早期就要把封装定下来,后期换料代价很大。

7.2 工业级与消费级的差别在哪

同样标称 32GB,工业级价格可能是消费级的两三倍。贵在哪?

一是筛选。工业级产品会做更严格的高低温测试、老化筛选,剔除边缘品,保证宽温下的稳定性。消费级按常规标准出货,边缘品也可能进市场。

二是颗粒和固件策略。工业级常用 pSLC 模式,把 TLC 当 SLC 用,牺牲容量换耐久和性能,P/E 能提升到几万次。固件里的 GC 策略、掉电保护、数据保持算法也更保守。

三是供货周期承诺。工业级产品通常承诺五到十年的长期供货,消费级可能一两年就停产换型号。这对产品生命周期长的设备是刚需,换料带来的重新验证成本远超芯片差价。

四是可追溯性。批次、晶圆来源、测试数据都能查,出了问题能定位到具体批次。

7.3 一套可落地的评估流程

我现在的做法是四步走。

第一步,拿到样品先做基础功能验证:能不能识别、容量对不对、各个分区能不能正常读写、boot 分区能不能启动。这一步用开发板跑一遍,半天搞定。

第二步做性能基线测试:空盘、半满、满盘三种状态下分别测顺序读写和随机读写,记录下来作为基线。同时跑一个持续写入测试(比如连续写 24 小时),观察速度衰减曲线和温度变化。

第三步做异常场景验证:写入过程中反复断电,看能不能正常恢复;高温箱里跑满载测试;模拟总线错误看内核怎么处理。这一步最能暴露固件的坑。

第四步做长期可靠性跟踪:小批量试产,装到实际设备上跑三个月,定期采集寿命和性能数据。这一步费时间,但能发现实验室里遇不到的问题。

这套流程走完,基本能对一颗 eMMC 的脾气摸得七七八八。急着量产跳过后面几步的,后面大概率要用售后成本补回来。

8. 常见问题速查表

问题现象可能原因排查手段处理方向
设备完全起不来,串口无输出内存初始化失败检查 DDR 供电、参考时钟、时序参数换内存颗粒或调整训练参数
启动到一半卡住,报读取超时eMMC 通信异常看内核 mmc 日志、量总线信号降速验证、检查焊接和走线
写文件报 No space left分区写满df -h、du -sh清理、扩容、改写入策略
写入很慢但 df 有余量GC 挣扎、空闲块耗尽iostat -x、fstrim后重测保留空闲空间、开 discard
寿命消耗异常快写放大高、高频小写对比主机写入量与寿命消耗优化日志策略、换文件系统
pre_eol_info 变成 0x02已消耗 80% 寿命mmc extcsd read确认安排备件、准备更换方案
高温下写入骤降颗粒编程变慢、控制器降频温度传感器读数、风枪加热复现改散热、选宽温器件
CRC 错误频繁重传信号完整性差示波器看眼图、降速对比改走线、加匹配、降时钟
掉电后文件系统损坏缓存未刷、映射表损坏检查 fsck 结果、看挂载参数加掉电检测、缩短 commit 间隔
RPMB 读计数器失败密钥未烧录或分区未使能mmc rpmb read-counter检查量产流程和 PARTITION_CONFIG

这里补一个我自己踩过的坑。有次现场设备批量出现启动失败,日志显示读取 boot 分区时 CRC 错误。第一反应是硬件批次问题,换了芯片还是复现。最后查出来是产线烧录时用了不匹配的 boot 分区大小配置,把 4MB 的镜像写进了 2MB 的分区,越界写坏了相邻区域的元数据。这类问题的教训是:分区配置必须和镜像大小严格对齐,烧录脚本里要加校验,不能靠人肉确认。

另外一个经验是关于 fstrim 的。很多嵌入式系统为了省事不开 discard,觉得影响性能。实际测下来,定期 fstrim(比如每周一次,在系统空闲时跑)对长期性能帮助很大,尤其是那些会频繁删文件的场景。代价只是几秒钟的 IO 占用。我的建议是把它做成 systemd timer 或者 cron 任务,别指望人工去跑。

最后说个判断技巧。当你拿不准一块板子上某颗芯片是内存还是存储时,看它的丝印和周边电路就能猜个八九不离十:内存旁边通常紧挨着 SoC,走线短且密集,供电简单(一路 1.1V 左右加一路 1.8V/2.5V);eMMC 位置相对自由,周围常有去耦电容阵列,有时还能看到独立的 1.8V/3.3V 供电和上拉电阻。当然最靠谱的还是查原理图和设备树,猜只是应急手段。

这个内容后面其实还能往下延展,比如 eMMC 的掉电保护电路怎么设计、UFS 的迁移评估怎么做、或者用 f2fs 替换 ext4 的实测数据对比。等有机会再单独开一篇聊。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进