大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是内存读写正确性压力测试程序memtester。

 

在嵌入式系统中,内存(RAM)的重要性不言而喻,系统性能及稳定性都与内存息息相关。关于内存性能有很多个不同指标,其中最基础的指标便是访问可靠性(即读写的正确性),只有稳定可靠的内存访问才能确保系统正常运行。很多时候简单地内存读写测试并不能发现隐藏的问题,因此我们需要一个完备的内存访问压力测试程序,今天痞子衡就和大家详细聊一聊memtester。

 

一、内存性能测试程序集

在讲memtester之前,痞子衡先给大家科普一下Linux系统下常用的内存性能测试工具,它们分别是mbw、memtester、lmbench、sysbench。这几个测试工具(程序)各有侧重点:

 

内存带宽测试工具            --mbw;
内存压力测试工具            --memtester;
内存综合性能测试工具        --lmbench;
内存申请与读写速度测试工具   --sysbench;

 

二、memtester程序

memtester是Simon Kirby在1999年编写的测试程序(v1版),后来由Charles Cazabon一直维护更新(v2及之后版本),主要面向Unix-like系统,官方主页上介绍的是“A userspace utility for testing the memory subsystem for faults.”,其实就是为了测试内存(主要DDR)的读写访问可靠性(仅正确性,与速度性能无关),这是验证板级硬件设备必不可少的一项测试。

 

整个memtester测试的视角就是从用户的角度来看的,从用户角度设立不同的测试场景即测试用例,然后针对性地进行功能测试,注意是从系统级来测试,也就是说关注的不单单是内存颗粒了,还有系统板级的连线、IO性能、PCB等等相关的因素,在这些因素的影响下,内存是否还能正常工作。

 

2.1 获取程序

memtester程序的最新版本是4.5.0,早期的v1/v2/v3版本目前下载不到了,2012年Charles Cazabon重写了程序并发布了全新v4.0.0,此后一直不定期更新,v4.x也是当前最流行的版本。

 

核心程序下载:http://pyropus.ca/software/memtester/

 

核心程序包下载后,在\memtester-4.5.0\下可找到源代码。详细源文件目录如下:

 

\memtester-4.5.0
                \memtester.h
                \memtester.c        --主程序入口
                \sizes.h            --关于系统位数(32/64bit)的一些定义
                \types.h            --所用数据类型的定义
                \tests.h
                \tests.c            --测试算法子程序

 

如果是移植到ARM Cortex-M平台下裸系统运行,一般只需要简单修改memtester.c文件即可,其他源文件就是一些头文件包含方面的改动,memtester本身并没有太多移植工作,其源码本是用作在Unix-like系统上运行的,而在嵌入式系统里运行仅需要把一些跟系统平台相关的代码删除即可,此外就是打印函数的实现。

 

2.2 配置参数

memtester源码里的配置选项主要是如下五个宏:

 

/* 如下需用户自定义 */
ULONG_MAX             -- 确定系统是32bit还是64bit
TEST_NARROW_WRITES    -- 确定是否要包含8/16 bit写测试

/* 如下借助linux头文件 */
_SC_VERSION           -- posix system版本检查
_SC_PAGE_SIZE         -- 内存page大小获取
MAP_LOCKED            -- Linux里mmap里的swap特性

 

2.3 程序解析

让我们尝试分析memtester主函数入口main,main()函数最开始都是一些输入参数解析,其实主要就是为了获取三个重要变量:内存测试起始地址、内存测试总长度、压力测试循环次数,有了这三个变量值之后便开始逐一跑tests.c文件里各项测试算法小函数:

 

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]] [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");

    // 省略若干初始检查代码
    // 从输入参数里获取physaddrbase计算出内存测试起始地址aligned
    // 从输入参数里获取mem及B|K|M|G计算出内存测试总长度bufsize

    halflen = bufsize / 2;
    count = halflen / sizeof(ul);
    bufa = (ulv *) aligned;
    bufb = (ulv *) ((size_t) aligned + halflen);

    // 压力测试的重要变量, loops即重复次数
    for(loop=1; ((!loops) || loop <= loops); loop++) {
        printf("Loop %lu", loop);
        if (loops) {
            printf("/%lu", loops);
        }
        printf(":\n");
        printf("  %-20s: ", "Stuck Address");
        fflush(stdout);

        // 第一个测试 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);
            // 可以看到将内存测试总空间一分为二,传给子程序做处理的
            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个函数,涉及各种内存访问经验操作,具体可以看网上的一篇详细解析文章 https://www.jianshu.com/p/ef203c360c4f。

 

测试函数名 测试作用
test_stuck_address 先全部把地址值交替取反放入对应存储位置,然后再读出比较,重复n次
test_random_value 等效test_random_comparison(bufa, bufb, count):数据敏感型测试用例
test_xor_comparison 与test_random_value比多了个异或操作
test_sub_comparison 与test_random_value比多了个减法操作
test_mul_comparisone 与test_random_value比多了个乘法操作
test_div_comparison 与test_random_value比多了个除法操作
test_or_comparison 在test_random_comparison()里面合并了
test_and_comparison 在test_random_comparison()里面合并了
test_seqinc_comparison 是 test_blockseq_comparison的一个子集;模拟客户压力测试场景
test_solidbits_comparison 固定全1后写入两个buffer,然后读出比较,然后全0写入读出比较;这就是Zero-One算法
test_blockseq_comparison 一次写一个count大小的块,写的值是拿byte级的数填充32bit,然后取出对比,接着重复256次;也是压力用例,只是次数变多了;
test_checkerboard_comparison 把设定好的几组Data BackGround,依次写入,然后读出比较
test_bitspread_comparison 还是在32bit里面移动,只是这次移动的不是单单的一个0或者1,而是两个1,这两个1之间隔着两个空位/td>
test_bitflip_comparison 也是32bit里面的一个bit=1不断移动生成data pattern然后,每个pattern均执行
test_walkbits1_comparison 与test_walkbits0_comparison同理
test_walkbits0_comparison 就是bit=1的位置在32bit里面移动,每移动一次就全部填满buffer,先是从低位往高位移,再是从高位往低位移动
test_8bit_wide_random 以char指针存值,也就是每次存8bit,粒度更细;
test_16bit_wide_random 以unsigned short指针存值,也就是每次存16bit,不同粒度检测;

 

2.4 结果格式

在Unix-like系统下使用make && make install命令进行编译可得到一个可执行的memtester,可以随便执行memtester 10M 1,即申请10M的内存测试1次,结果如下:

 

[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.

 

至此,内存读写正确性压力测试程序memtester痞子衡便介绍完毕了,掌声在哪里~~~