痞子衡嵌入式:內(nèi)存讀寫正確性壓力測試程序(memtester)
大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是內(nèi)存讀寫正確性壓力測試程序memtester。
在嵌入式系統(tǒng)中,內(nèi)存(RAM)的重要性不言而喻,系統(tǒng)性能及穩(wěn)定性都與內(nèi)存息息相關(guān)。關(guān)于內(nèi)存性能有很多個(gè)不同指標(biāo),其中最基礎(chǔ)的指標(biāo)便是訪問可靠性(即讀寫的正確性),只有穩(wěn)定可靠的內(nèi)存訪問才能確保系統(tǒng)正常運(yùn)行。很多時(shí)候簡單地內(nèi)存讀寫測試并不能發(fā)現(xiàn)隱藏的問題,因此我們需要一個(gè)完備的內(nèi)存訪問壓力測試程序,今天痞子衡就和大家詳細(xì)聊一聊memtester。
一、內(nèi)存性能測試程序集
在講memtester之前,痞子衡先給大家科普一下Linux系統(tǒng)下常用的內(nèi)存性能測試工具,它們分別是mbw、memtester、lmbench、sysbench。這幾個(gè)測試工具(程序)各有側(cè)重點(diǎn):
內(nèi)存帶寬測試工具 --mbw;
內(nèi)存壓力測試工具 --memtester;
內(nèi)存綜合性能測試工具 --lmbench;
內(nèi)存申請與讀寫速度測試工具 --sysbench;
二、memtester程序
memtester是Simon Kirby在1999年編寫的測試程序(v1版),后來由Charles Cazabon一直維護(hù)更新(v2及之后版本),主要面向Unix-like系統(tǒng),官方主頁上介紹的是“A userspace utility for testing the memory subsystem for faults.”,其實(shí)就是為了測試內(nèi)存(主要DDR)的讀寫訪問可靠性(僅正確性,與速度性能無關(guān)),這是驗(yàn)證板級硬件設(shè)備必不可少的一項(xiàng)測試。
整個(gè)memtester測試的視角就是從用戶的角度來看的,從用戶角度設(shè)立不同的測試場景即測試用例,然后針對性地進(jìn)行功能測試,注意是從系統(tǒng)級來測試,也就是說關(guān)注的不單單是內(nèi)存顆粒了,還有系統(tǒng)板級的連線、IO性能、PCB等等相關(guān)的因素,在這些因素的影響下,內(nèi)存是否還能正常工作。
2.1 獲取程序
memtester程序的最新版本是4.5.0,早期的v1/v2/v3版本目前下載不到了,2012年Charles Cazabon重寫了程序并發(fā)布了全新v4.0.0,此后一直不定期更新,v4.x也是當(dāng)前最流行的版本。
核心程序包下載后,在\memtester-4.5.0\下可找到源代碼。詳細(xì)源文件目錄如下:
\memtester-4.5.0
\memtester.h
\memtester.c --主程序入口
\sizes.h --關(guān)于系統(tǒng)位數(shù)(32/64bit)的一些定義
\types.h --所用數(shù)據(jù)類型的定義
\tests.h
\tests.c --測試算法子程序
如果是移植到ARM Cortex-M平臺下裸系統(tǒng)運(yùn)行,一般只需要簡單修改memtester.c文件即可,其他源文件就是一些頭文件包含方面的改動(dòng),memtester本身并沒有太多移植工作,其源碼本是用作在Unix-like系統(tǒng)上運(yùn)行的,而在嵌入式系統(tǒng)里運(yùn)行僅需要把一些跟系統(tǒng)平臺相關(guān)的代碼刪除即可,此外就是打印函數(shù)的實(shí)現(xiàn)。
2.2 配置參數(shù)
memtester源碼里的配置選項(xiàng)主要是如下五個(gè)宏:
/* 如下需用戶自定義 */
ULONG_MAX -- 確定系統(tǒng)是32bit還是64bit
TEST_NARROW_WRITES -- 確定是否要包含8/16 bit寫測試
/* 如下借助linux頭文件 */
_SC_VERSION -- posix system版本檢查
_SC_PAGE_SIZE -- 內(nèi)存page大小獲取
MAP_LOCKED -- Linux里mmap里的swap特性
2.3 程序解析
讓我們嘗試分析memtester主函數(shù)入口main,main()函數(shù)最開始都是一些輸入?yún)?shù)解析,其實(shí)主要就是為了獲取三個(gè)重要變量:內(nèi)存測試起始地址、內(nèi)存測試總長度、壓力測試循環(huán)次數(shù),有了這三個(gè)變量值之后便開始逐一跑tests.c文件里各項(xiàng)測試算法小函數(shù):
struct test {
char *name;
int (*fp)();
};
struct test tests[] = {
{ "Random Value", test_random_value },
{ "Compare XOR", test_xor_comparison },
{ "Compare SUB", test_sub_comparison },
{ "Compare MUL", test_mul_comparison },
{ "Compare DIV",test_div_comparison },
{ "Compare OR", test_or_comparison },
{ "Compare AND", test_and_comparison },
{ "Sequential Increment", test_seqinc_comparison },
{ "Solid Bits", test_solidbits_comparison },
{ "Block Sequential", test_blockseq_comparison },
{ "Checkerboard", test_checkerboard_comparison },
{ "Bit Spread", test_bitspread_comparison },
{ "Bit Flip", test_bitflip_comparison },
{ "Walking Ones", test_walkbits1_comparison },
{ "Walking Zeroes", test_walkbits0_comparison },
#ifdef TEST_NARROW_WRITES
{ "8-bit Writes", test_8bit_wide_random },
{ "16-bit Writes", test_16bit_wide_random },
#endif
{ NULL, NULL }
};
/* Function definitions */
void usage(char *me) {
fprintf(stderr, "\n"
"Usage: %s [-p physaddrbase [-d device]] <mem>[B|K|M|G] [loops]\n",
me);
exit(EXIT_FAIL_NONSTARTER);
}
int main(int argc, char **argv) {
ul loops, loop, i;
size_t bufsize, halflen, count;
void volatile *buf, *aligned;
ulv *bufa, *bufb;
ul testmask = 0;
// 省略若干變量定義代碼
printf("memtester version " __version__ " (%d-bit)\n", UL_LEN);
printf("Copyright (C) 2001-2020 Charles Cazabon.\n");
printf("Licensed under the GNU General Public License version 2 (only).\n");
printf("\n");
// 省略若干初始檢查代碼
// 從輸入?yún)?shù)里獲取physaddrbase計(jì)算出內(nèi)存測試起始地址aligned
// 從輸入?yún)?shù)里獲取mem及B|K|M|G計(jì)算出內(nèi)存測試總長度bufsize
halflen = bufsize / 2;
count = halflen / sizeof(ul);
bufa = (ulv *) aligned;
bufb = (ulv *) ((size_t) aligned + halflen);
// 壓力測試的重要變量, loops即重復(fù)次數(shù)
for(loop=1; ((!loops) || loop <= loops); loop++) {
printf("Loop %lu", loop);
if (loops) {
printf("/%lu", loops);
}
printf(":\n");
printf(" %-20s: ", "Stuck Address");
fflush(stdout);
// 第一個(gè)測試 stuck_address
if (!test_stuck_address(aligned, bufsize / sizeof(ul))) {
printf("ok\n");
} else {
exit_code |= EXIT_FAIL_ADDRESSLINES;
}
// 遍歷tests.c里的所有測試子程序
for (i=0;;i++) {
if (!tests[i].name) break;
if (testmask && (!((1 << i) & testmask))) {
continue;
}
printf(" %-20s: ", tests[i].name);
// 可以看到將內(nèi)存測試總空間一分為二,傳給子程序做處理的
if (!tests[i].fp(bufa, bufb, count)) {
printf("ok\n");
} else {
exit_code |= EXIT_FAIL_OTHERTEST;
}
fflush(stdout);
/* clear buffer */
memset((void *) buf, 255, wantbytes);
}
printf("\n");
fflush(stdout);
}
}
tests.c文件里才是最核心的壓力測試算法子程序,一共17個(gè)函數(shù),涉及各種內(nèi)存訪問經(jīng)驗(yàn)操作,具體可以看網(wǎng)上的一篇詳細(xì)解析文章 https://www.jianshu.com/p/ef203c360c4f。
| 測試函數(shù)名 | 測試作用 |
|---|---|
| test_stuck_address | 先全部把地址值交替取反放入對應(yīng)存儲位置,然后再讀出比較,重復(fù)n次 |
| test_random_value | 等效test_random_comparison(bufa, bufb, count):數(shù)據(jù)敏感型測試用例 |
| test_xor_comparison | 與test_random_value比多了個(gè)異或操作 |
| test_sub_comparison | 與test_random_value比多了個(gè)減法操作 |
| test_mul_comparisone | 與test_random_value比多了個(gè)乘法操作 |
| test_div_comparison | 與test_random_value比多了個(gè)除法操作 |
| test_or_comparison | 在test_random_comparison()里面合并了 |
| test_and_comparison | 在test_random_comparison()里面合并了 |
| test_seqinc_comparison | 是 test_blockseq_comparison的一個(gè)子集;模擬客戶壓力測試場景 |
| test_solidbits_comparison | 固定全1后寫入兩個(gè)buffer,然后讀出比較,然后全0寫入讀出比較;這就是Zero-One算法 |
| test_blockseq_comparison | 一次寫一個(gè)count大小的塊,寫的值是拿byte級的數(shù)填充32bit,然后取出對比,接著重復(fù)256次;也是壓力用例,只是次數(shù)變多了; |
| test_checkerboard_comparison | 把設(shè)定好的幾組Data BackGround,依次寫入,然后讀出比較 |
| test_bitspread_comparison | 還是在32bit里面移動(dòng),只是這次移動(dòng)的不是單單的一個(gè)0或者1,而是兩個(gè)1,這兩個(gè)1之間隔著兩個(gè)空位/td> |
| test_bitflip_comparison | 也是32bit里面的一個(gè)bit=1不斷移動(dòng)生成data pattern然后,每個(gè)pattern均執(zhí)行 |
| test_walkbits1_comparison | 與test_walkbits0_comparison同理 |
| test_walkbits0_comparison | 就是bit=1的位置在32bit里面移動(dòng),每移動(dòng)一次就全部填滿buffer,先是從低位往高位移,再是從高位往低位移動(dòng) |
| test_8bit_wide_random | 以char指針存值,也就是每次存8bit,粒度更細(xì); |
| test_16bit_wide_random | 以unsigned short指針存值,也就是每次存16bit,不同粒度檢測; |
2.4 結(jié)果格式
在Unix-like系統(tǒng)下使用make && make install命令進(jìn)行編譯可得到一個(gè)可執(zhí)行的memtester,可以隨便執(zhí)行memtester 10M 1,即申請10M的內(nèi)存測試1次,結(jié)果如下:
[root@as150 ~] memtester 10M 1
memtester version 4.5.0 (64-bit)
Copyright (C) 2001-2020 Charles Cazabon.
Licensed under the GNU General Public License version 2 (only).
pagesize is 4096
pagesizemask is 0xfffffffffffff000
want 10MB (10485760 bytes)
got 10MB (10485760 bytes), trying mlock ...locked.
Loop 1/1:
Stuck Address: ok
Random Value: ok
Compare XOR: ok
Compare SUB: ok
Compare MUL: ok
Compare DIV: ok
Compare OR: ok
Compare AND: ok
Sequential Increment: ok
Solid Bits: ok
Block Sequential: ok
Checkerboard: ok
Bit Spread: ok
Bit Flip: ok
Walking Ones: ok
Walking Zeroes: ok
8-bit Writes: ok
16-bit Writes: ok
Done.
至此,內(nèi)存讀寫正確性壓力測試程序memtester痞子衡便介紹完畢了,掌聲在哪里~~~
歡迎訂閱
文章會(huì)同時(shí)發(fā)布到我的 博客園主頁、CSDN主頁、知乎主頁、微信公眾號 平臺上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機(jī)上第一時(shí)間看了哦。

最后歡迎關(guān)注痞子衡個(gè)人微信公眾號【痞子衡嵌入式】,一個(gè)專注嵌入式技術(shù)的公眾號,跟著痞子衡一起玩轉(zhuǎn)嵌入式。
衡杰(痞子衡),目前就職于恩智浦(NXP)半導(dǎo)體MCU系統(tǒng)應(yīng)用部門,擔(dān)任高級嵌入式系統(tǒng)應(yīng)用工程師。
專欄內(nèi)所有文章的轉(zhuǎn)載請注明出處:http://www.rzrgm.cn/henjay724/
與痞子衡進(jìn)一步交流或咨詢業(yè)務(wù)合作請發(fā)郵件至 hengjie1989@foxmail.com
可以關(guān)注痞子衡的Github主頁 https://github.com/JayHeng,有很多好玩的嵌入式項(xiàng)目。
關(guān)于專欄文章有任何疑問請直接在博客下面留言,痞子衡會(huì)及時(shí)回復(fù)免費(fèi)(劃重點(diǎn))答疑。
痞子衡郵箱已被私信擠爆,技術(shù)問題不推薦私信,堅(jiān)持私信請先掃碼付款(5元起步)再發(fā)。
浙公網(wǎng)安備 33010602011771號