资讯详情

资讯详情

嵌入式平台DDR稳定性验证:Memtest移植与调试实战

前阵子给一块国产嵌入式平台做DDR稳定性验证需要把手头的Memtest工具移植过去跑测试。说句实话这事儿看着不起眼真正动手才发现全是细节交叉编译环境怎么搭、静态链接还是动态链接、测试内存怎么分配、跑出来的报错怎么定位。尤其嵌入式平台的DDR验证随便跑两圈“PASSED”可不算完得让它真能在高负载下把颗粒、走线、时序的老底都翻出来。这篇就把我这次移植和调试的完整过程复盘一遍从工具选型、交叉编译到目标板上的运行策略再到常见坑点的排查方法适合做嵌入式Linux开发、产测软件或者硬件验证的同行参考。1. 移植前想清楚为什么偏偏选Memtest1.1 内存验证的下场跑一遍真不一定行很多朋友觉得DDR测试就是把memtester丢到板子上跑个几百兆内存最后看有没有输出“PASSED”就完事了。但内存这东西跟CPU不一样CPU坏了往往直接不启动DDR出了问题反而是千奇百怪的可能是随机进程崩溃可能是文件系统读写出错可能是视频图像花屏甚至可能跑着跑着直接死机重启之后又什么事儿都没有。这些大概率都和DDR颗粒本身的稳定性、PCB走线、焊接质量、电源纹波、控制器时序这些因素有关。DDR颗粒在高速读写时相邻bit线之间会有串扰地址线跳变时容易出现建立/保持时间不够高温环境下数据保持时间会缩短这些都不是靠单纯跑一遍某个应用能暴露出来的。需要用专门的内存测试工具设计多种数据模式去“折磨”内存。行业里常用的做法就是在系统启动后跑memtester这类工具或者用平台SDK自带的DDR底层测试程序。普通应用测试只能验证“当前软件功能正常”而内存测试工具是把内存当作被测对象目的就是尽可能暴露它内部的问题。1.2 Memtest家族怎么选不是所有“Memtest”都适合嵌入式“Memtest”这个名字下面其实有一大家子工具最容易搞混的是两个memtest86 / memtest86以及memtester。memtest86是经典的独立内存测试工具它在x86平台上做得非常成熟通常在BIOS/UEFI阶段启动运行不依赖任何操作系统。但问题也在这它需要独立的启动环境和底层芯片、BIOS深度绑定想要拿到嵌入式ARM平台上来跑基本行不通因为嵌入式平台根本没有传统BIOS这一套启动流程差异太大。真正适合嵌入式Linux场景的是memtester这是一个用户态工具核心源码就两个C文件编译出来体积很小依赖非常少。它通过操作系统动态分配一块内存然后在这块内存上做各种读写校验整个过程完全不需要修改内核也不需要专门的驱动交叉编译之后直接扔到目标板就能跑。这个特性在嵌入式平台上是巨大的优势因为产线验证、老化测试很多时候不希望为了测试去改动固件和系统镜像。有些平台厂商会提供专用的DDR底层测试程序能做PHY训练、寄存器级测试这类工具当然好但它往往要配合特定内核驱动和SDK版本使用场景受限。我的做法一般是两者配合先用平台专用工具验证控制器和PHY的底层配置再用memtester做应用层、系统级的压力验证。这篇文章主要讲memtester这个通用型工具的移植和运用。1.3 动手之前先摸清目标平台的DDR底细每个嵌入式平台的DDR控制器和内存接口都不太一样。有的平台用的是DDR3有的用DDR3L有的是DDR4比较新的还有LPDDR4/LPDDR5它们的工作电压、时序参数、刷新率、片内终结电阻配置都不一样。同一颗DDR颗粒在不同平台上可能表现差异很大。所以在跑memtester之前至少要确认清楚几个信息平台支持的内存类型和最大容量当前固件里DDR频率和时序参数是多少内存总线位宽是多少16bit、32bit还是64bit。这些信息怎么确认一般看平台SDK里的启动日志或者uboot引导阶段的打印信息会有DRAM大小、频率、时序相关的显示。比如有的平台会打印类似“DDR3 2GB 32bit 800MHz”这样的信息意思是两条32bit总线、总容量2GB、运行在800MHz。如果固件里DDR频率是偷偷“超”上去的那memtester这种压力工具跑起来就特别容易暴露问题这其实是个好现象因为它说明设计余量不足或者硬件存在风险点。还有一点目标平台的内存映射关系要大概了解。嵌入式Linux里DDR物理地址通常从某个基地址开始连续排布用户态程序通过虚拟地址访问物理内存换算关系由MMU管理。大多数情况下memtester跑的就是虚拟内存背后的物理页这一层不用我们操太多心但后面讲到如何确认“跑得到底是不是真内存”时这些知识就派上用场了。2. 交叉编译与移植实操从源码到目标板可执行文件2.1 搭好交叉编译环境工具链、系统根目录、环境变量嵌入式平台上的应用没法直接在PC上编译因为目标平台的CPU架构、内核头文件、C库版本都可能不一样。我们需要用交叉编译工具链在PC上编译出能在目标平台运行的可执行文件。我这次用的目标平台是某国产嵌入式处理器的开发板CPU是32位ARM架构所以工具链选择了arm-linux-gnueabihf-gcc这一类的交叉编译器。如果目标是64位ARM平台一般就用aarch64-linux-gnu-gcc对应工具链。拿到工具链后我的习惯是先建一个干净的交叉编译环境目录然后设置环境变量export PATH/opt/arm-toolchain/bin:$PATH export CROSS_COMPILEarm-linux-gnueabihf- export CC${CROSS_COMPILE}gcc设置完先验证一下编译器能不能工作arm-linux-gnueabihf-gcc -v看到版本信息输出后还要确认目标平台的系统根目录(sysroot)路径是否有遗漏因为交叉编译时头文件和库文件要从sysroot里取。一般厂商SDK里都会带好sysroot比如可能包含lib/arm-linux-gnueabihf这样的目录结构。如果sysroot配置不对后面编译时会出现找不到头文件、找不到标准库之类的报错。2.2 编译memtester的关键参数静态链接是首选memtester源码一般从官方站点或者开源镜像下载解压后目录里有Makefile和conf-cc、conf-ld等配置文件。我之前用的版本是4.5.1结构很干净。拿到源码后先用简单的方式编译试试make CCarm-linux-gnueabihf-gcc CFLAGS-O2 -static LDFLAGS-static这里有两个关键点。第一个是-O2优化选项编译器会对代码做一些优化让生成的指令效率更高。跑内存测试工具本身不需要调试等级直接用-O2没问题。第二个是-static这个很关键它会把所有依赖的C库代码静态链入可执行文件。为什么嵌入式平台上静态链接更好因为目标板上的动态库环境经常很“脏”有的板子系统镜像裁剪过缺了某些动态库或者动态库版本和本机不一致容易出现“page not found”或者Segmention fault。静态编译出来的文件虽然体积会大一些但基本上丢到任何同CPU架构的板子上都能跑不操心库依赖。实测下来静态编译的memtester体积也就几百KB对嵌入式存储来说完全不是负担。编译完成后用file命令确认一下产物类型file memtester如果输出里包含“ARM”字样而不是“x86-64”说明交叉编译成功。这一步很多人会忽略直接拷到板子上才发现跑不了那就是浪费大量时间。2.3 定制编译选项对齐、取消安全检查、适应硬浮点不同平台的ARM处理器有些微妙的差异编译时最好检查几个默认行为。比如有些ARM核不支持非对齐访问如果memtester内部某些结构体或者数据缓冲出现了非对齐地址就可能导致总线错误。为了稳妥可以在CFLAGS里加上-mno-unaligned-access告诉编译器不要生成非对齐访问指令或者反过来加上-malign-data这类选项。还有一个常见坑是_FORTIFY_SOURCE相关的报错。有些新工具链默认开启了_FORTIFY_SOURCE2之类的强化选项但sysroot里的头文件和库版本如果跟编译器版本不匹配就会在链接编译时出现strcpy、memcpy相关符号找不到的情况。我建议编译memtester这种老牌工具时干脆显式加上-D_FORTIFY_SOURCE0把安全检查关掉反正它的核心逻辑就是反复读写内存用不着额外缓冲保护。至于硬浮点和软浮点的问题看目标CPU是否带有硬件FPU。比如Cortex-A7、A9这类核心一般有VFPv3或NEON单元用-mfloat-abihard -mfpuneon-vfpv4这类选项可以让浮点运算更快。不过memtester本身对浮点运算依赖不大大多数测试都是整数位运算所以哪怕默认浮点选项不对只要CPU类型不差太多跑起来也没太大区别。关键是别出现“hard-float ABI”和“soft-float ABI”冲突造成的链接错误。2.4 静态编译后的测试文件怎么扔到板子上目标板拿到编译好的memtester之后可以用多种方式拷贝进去。我这次用的开发板支持TFTP、NFS和USB/SD卡挂载项目调试阶段为了方便我一般先开NFS把整个测试目录挂到目标板。比如在PC上共享/tftpboot目录目标板上执行mount -t nfs -o nolock 192.168.1.100:/tftpboot /mnt cp /mnt/memtester /usr/bin/ chmod x /usr/bin/memtester如果目标板存储空间紧张也可以不拷贝直接从NFS临时目录运行。但产测阶段建议放到本地/usr/bin或/bin避免网络不稳定导致测试中断。放过去之后立即试跑一下短测试比如memtester 64M 1如果命令能正常打印版本信息并开始测试没有报Segmentation fault、没有报Illegal instruction说明这个二进制跟目标板环境兼容后续就可以放心做长时间测试了。3. 在目标板上跑DDR测试的完整流程参数、策略和日志分析3.1 测试之前先搞明白内存大小怎么选memtester的命令格式是memtester [-p 物理地址] 内存大小 [测试次数]其中内存大小支持单位比如64M、1G。但这个“内存大小”并不是让你随便填填大了可能直接被系统杀掉填小了又测不到真正需要关注的那片区域。那怎么选一般原则是要“保留系统运行的基础内存”再去测试剩余部分。比如一个板子物理内存是512MB系统起来后free查看可用内存大概是430MB左右内核、init进程、驱动占用一部分这时候测试内存建议定在256M到400M之间。定256M比较保守系统不会因为内存压力出现OOM定400M更贴近极限压力但有一定风险跑挂系统。工业产测和老化场景我一般分两步走。第一步做冒烟测试用可用内存的50%左右循环1到3次验证基本功能。第二步做老化压力测试用可用内存的80%~90%循环几十次甚至上百次模拟长时间高温满负载场景。这样既保证单次测试时间可控又能真正压出平时低负载下发现不了的问题。3.2 常见的测试模式和参数组合memtester默认会跑一组测试用例包括Random Value往内存里写随机值再读出来校验覆盖数据线的翻转问题Compare XOR / SUB / MUL / DIV / OR / AND用不同算术和位运算逻辑比较写入值和回读值检测运算器和存储单元之间的干扰Sequential Increment顺序递增填充地址相邻的存储单元互相干扰很容易暴露Solid Bits全0和全1填充测试存储单元是否能保持稳定状态Block Sequential分块递增和翻转检验地址译码逻辑Checkerboard棋盘格模式交替写入0和1让相邻bit之间形成最大电压差能跑来测试片内干扰Bit Spread / Bit Flip位翻转和分散模式对DRAM行激活和预充电的时序很敏感这些测试各有针对性全部跑一遍基本能覆盖常见失效模式。基本命令memtester 256M 10这个命令表示测试256M内存循环10次。每次循环会把所有用例跑一遍输出当前的进度和结果。测试次数越多耗时越长老化测试甚至可以指定1000次。一般我建议老化测试至少跑到总时长两小时以上循环次数根据单轮耗时换算。如果需要绑定特定的物理地址范围可以用-p参数指定物理地址起始位置。不过这个参数需要root权限而且内存页要预先锁定实际操作中如果对地址映射不熟建议先让操作系统自动分配不要轻易指定。3.3 日志记录千万别让输出飘在串口上就完事跑老化测试动辄几个小时甚至通宵如果人一直盯着串口看显然不现实。一定要把memtester输出保存到文件同时打上时间戳方便事后分析。最简单的方法memtester 384M 200 /tmp/ddr_test.log 21测试结束后查看日志文件搜索关键字PASSED如果整轮测试全部通过memtester会输出类似Loop 1/200: Random Value - 16.0s Compare XOR - 8.0s ... PASSED如果出现错误会输出类似FAILURE: 0xa5a5a5a5 ! 0x5a5a5a5a at offset ... FAILURE: 0xa5a5a5a5 ! 0x5a5a5a5a at offset ...这代表某个地址的读回值和写入值不一致说明DDR存储单元出错。注意这类错误信息非常宝贵一定要把具体偏移地址记录下来结合物理内存映射可以反推是哪片颗粒、哪根数据线出问题。对于产线场景我习惯在shell脚本里做自动判断测试结束后检测返回码if [ $? -eq 0 ]; then echo DDR_TEST_PASS else echo DDR_TEST_FAIL fimemtester正常全部测试通过时返回0只要出现任何失败都会返回非0值。这个设计对自动化产测非常友好脚本可以直接根据返回码决定后续流程。3.4 实测输出样例怎么看懂测试过程下面是我在一个目标板上运行memtester 256M 2的实测输出关键信息标注一下memtester version 4.5.1 (32-bit) Copyright (C) 2001-2012 Charles Cazabon Licensed under the GNU General Public License version 2 (or later) pagesize is 4096 pagesizemask is 0xfffff000 want 256MB (268435456 bytes) got 256MB (268435456 bytes), trying mlock ... locked 256MB (268435456 bytes) successfully, testing...开头部分会显示版本、页大小4096这能判断系统是标准4K页、申请内存大小然后尝试用mlock锁定内存。锁定很重要它保证测试期间这块内存不会被交换到磁盘上交换出去测的就是硬盘了没有意义。之后是每个测试用例的输出Loop 1/2: Random Value - 5.0s Compare XOR - 4.0s Compare SUB - 4.0s Compare MUL - 4.0s Compare DIV - 12.0s Compare OR - 4.0s Compare AND - 4.0s Sequential Increment - 5.0s Solid Bits - 4.0s Block Sequential - 5.0s Checkerboard - 5.0s Bit Spread - 4.0s Bit Flip - 4.0s Loop 2/2: ... PASSED看到最后一行单独输出的PASSED就代表这轮测试通过。这里有个细节Compare DIV用例耗时明显比其他用例长这正常因为除法运算本身就比加减乘慢特别是32位ARM平台上的软浮点环境。4. 常见问题与排查技巧实录让你少踩一半的坑4.1 编译期问题工具链不匹配、符号找不到交叉编译时最常遇到的报错一是找不到头文件比如stdlib.h、string.h这说明sysroot路径没指定对或者工具链本身的头文件路径和库路径脱离。二是链接阶段报cannot find -lc说明C库没找到大概率是工具链不完整需要去下载完整版工具链不要用手头残缺的版本。还有一次遇到比较隐蔽的问题是crt1.o not found。这个文件是C运行时启动文件一般放在工具链的sysroot里。当时查了半天发现是工具链安装路径变了环境变量里旧的LIBRARY_PATH指向了不存在的目录把所有环境变量重新梳理一遍就解决了。所以建议在编译之前先echo $PATH和echo $LIBRARY_PATH确认环境变量干净。4.2 运行期问题能跑但结果不靠谱有一种情况让人非常头疼memtester在板子上跑得好好的但测试结果时好时坏或者明明有问题却输出了PASSED。这类问题多半不是工具本身的问题而是运行环境设计不合理。比如测试内存尺寸超过了可用内存操作系统开始使用swap交换空间swap设在eMMC或SD卡上测试数据被换到flash里再读回来其实是从flash读的根本测不到DDR而且耗时暴增。这时free命令看一下swap使用情况如果swap占用不为0先把swap关掉或者调整测试内存大小。再比如测试过程中其他高负载进程也在跑CPU被占满导致定时器和调度延迟这不会直接导致内存测试失败但会让测试耗时变得很不稳定。产测时建议尽量关闭图形界面、网络服务等非必要进程让memtester独占CPU和内存资源。还有个隐蔽问题是CPU的cache掩盖了错误。现代ARM处理器的L1、L2 cache命中率很高如果测试数据一直停留在cache里而没被冲刷回DDR理论上某些DDR时序问题会被掩盖。memtester的设计会通过大块内存遍历和不同数据模式尽可能降低cache的掩盖效果但如果你发现可疑问题可以尝试在更大内存块上测试或者连续多轮测试让cache清空的概率变大。4.3 结果误判到底测的是不是物理DDR有时候系统可用内存足够但memtester分配的内存在虚拟地址空间上可能映射到某段匿名内存这倒无所谓只要它在物理内存里就行。但如果系统配置了内存压缩(zram/zswap)或者内存回收机制某些页面可能被压缩或搬迁测试时就会出现莫名奇妙的失败。怎么确认memtester确实把数据写在DDR物理内存上可以查看memtester是否成功执行了mlock锁定。如果输出中出现trying mlock和locked ... successfully说明内存锁定成功。如果出现Cannot allocate memory或unable to mlock memory说明系统不允许锁定那块内存常见原因一是进程没有root权限二是ulimit -l限制太低。用root运行并把内存锁定上限调高一点ulimit -l unlimited这个动作很重要不然后面跑大内存测试时操作系统随时可能把测试内存页面换出去。4.4 测试失败后怎么快速缩小范围一旦memtester报错先整理报错信息里的偏移地址。比如报错偏移是0x34567890结合内存总线位宽可以推算具体是哪根数据线或哪颗颗粒。比如某颗DDR颗粒对应32位总线中的低8位那么报错地址偏移对应区间落在某个范围内基本能锁定是哪一片颗粒的问题。排查顺序建议是从易到难先排除电源问题。检查DDR供电电压纹波特别是负载瞬间跳变时的压降。示波器抓一下内核电压、DDR VDD、VTT等电源轨很多“随机偶发错误”都是电源纹波导致的。再排除时序问题。把DDR运行频率降一档比如从800MHz降到667MHz如果问题消失基本确定是时序裕量不够。接着怀疑走线和阻抗。看看PCB上DDR走线有没有等长、有没有跨分割这类问题往往在特定数据模式下才暴露。最后才是颗粒本身。颗粒老化或者批次不良通常测试时间越长错误率越高可以和同平台的另一块板子做对照。这个排查思路我验证过多次能省下大量盲目更换硬件的成本。4.5 嵌入式平台的进阶技巧与SDK底层工具交叉验证单纯跑memtester能验证系统级稳定性但对DDR控制器的PHY配置、眼图裕量、读写训练结果覆盖不够。很多平台SDK里会带专门的DDR测试工具运行在boot阶段或者内核启动早期可以直接访问控制器寄存器进行读写训练、眼图扫描、压力测试。我的经验是两者结合效果最好。先用底层工具确认DDR控制器初始化没问题、PHY训练通过、频率和时序配置正确。再用memtester做长时间系统级老化测试验证整个数据通路控制器、PHY、PCB、颗粒在真实负载下能稳定工作。这种两步验证法在产线测试中也很实用。底层工具测试时间短适合快速筛查memtester测试时间长适合抽检和老化。有些平台支持把memtester集成到启动脚本里开机自动跑一轮短测试通过后才加载主业务程序相当于给产品加了一道内存自检门。5. 产线与老化场景的扩展让memtester发挥更大价值如果只是开发调试跑到这步基本够了。但如果是产线大批量测板子或者产品出货前的老化筛选还得考虑自动化、批量化和数据追溯的问题。这里分享几个我实际用过的扩展方式。一个是全自动循环测试脚本。产线上不可能让人盯着每块板子的串口我的做法是写一个shell脚本开机启动后自动运行把测试结果写到指定分区并上报#!/bin/sh LOG/factory/ddr_test.log declare -i PASS0 FAIL0 while : do memtester 384M 100 /tmp/ddr_once.log 21 if [ $? -eq 0 ]; then PASS$PASS1 echo $(date) PASS $PASS $LOG else FAIL$FAIL1 echo $(date) FAIL $FAIL $LOG cp /tmp/ddr_once.log ${LOG}.fail.${FAIL} fi sleep 3 done失败时会把完整日志备份出来方便离线分析。这个脚本可以直接放在init脚本或systemd服务里开机自启完全无人值守。还有一个思路是在DDR频率临界点附近做测试。如果产品设计标称DDR跑在800MHz实际测试可以把频率设到850MHz或更高用memtester长时间跑这种情况下能通过测试的板子意味着设计裕量更足。但这种方式只适合抽检不适合全检因为高温、电压波动等因素会放大差异。对于产线上的快速通过/失败判断我一般把memtester的循环次数设置成3到5次单次256M内存大约总耗时5到10分钟。这个时长能兼顾效率和覆盖率。如果追求更快的准入门禁可以只测64M到128M内存跑1到2次循环大约1分钟出结果。不过快速筛选的代价是覆盖率低可能漏掉某些地址区域的错误需要根据产品风险等级自己权衡。另一个容易忽略的点是测试结果的标识。批量测试时建议在目标板上也记录一份环境信息比如板卡序列号、DDR颗粒批次、固件版本、测试时间这样可以追溯一批板子的故障规律。比如同一批次的颗粒如果故障率偏高就可以把批次信息和memtester报错偏移关联起来分析判断是不是颗粒供货商换料导致的。6. 最后再分享一个小技巧前面讲了不少方法和排坑经验最后聊一个实际操作中让我印象特别深的细节。有次做长时间老化测试板子跑了几个小时突然报了几个内存错误当时第一反应是DDR颗粒有问题重新跑又通过了。我一度以为是偶发干扰没太在意。后来仔细看串口日志时间戳发现出错的时刻正好是板子内部温度达到峰值的时段而且报错偏移地址都在同一片区域顺着这个线索查下去发现是给DDR供电的DC-DC转换器在高温下效率下降输出电压出现周期性跌落导致高温高负载下的写入不稳定。这件事之后我养成了一个习惯做DDR测试时一定要同步监控温度特别是在散热条件不好的密闭机壳里。跑memtester可以加一张温控表或者在脚本里读一下传感器温度把温度信息和测试结果放在同一份日志里。很多时候“说不清道不明”的内存随机报错最后都能归因到温度、电压、时序这三板斧上。说白了Memtest移植到嵌入式平台本身难度不高真正花精力的地方是让它在一个真实的硬件环境里跑出可信结论。希望这篇分享能帮你少走点弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →