hisi平台异常重启问题分析

Hisi平台开发之异常重启问题分析

目录

1. 问题描述...................................................................................................................... 3

2. 问题跟踪排查............................................................................................................... 3

2.1 问题分析............................................................................................................. 3

2.2 问题拆解............................................................................................................. 3

4. 问题再分析.................................................................................................................. 6

5. 后记............................................................................................................................. 9

1. 问题描述

         海思平台开发过程中加载大特征值过程中,遇到系统没有任何征兆,直接重启现象

2. 问题跟踪排查

2.1 问题分析

设备在加载过程中,将系统的内存使用情况,串口日志,业务日志,kmsg等信息都调出来,观察现象发生时有无异常信息输出。

经过多次测试,发现加载过程中,确定除了内存不停下降,无任何异常信息。系统直接重启,组内讨论最终将该问题拆解为:

  1. 系统内存为何不停的下降
  2. 系统内存在剩余55M左右,为何会重

其中问题1,又分解为两部分,a、系统在加载特征值的过程中sdk消耗内存;b、sdk回调给设备消耗的内存。(隐去公司名称)

本文仅讨论分享问题1,至于问题2本身也是一个很有意义的问题,背后涉及的技术问题也值得关注,后续有时间再写。

分析拆解问题,将一个大的问题,划分为若干小的问题,再将小问题个个击破,最终得到大问题的解,是一个常用的手段。体现了计算机算法思想中的分而治之,是我们需要不断提高的能力。将问题1分解为a/b两部分后,就可以具体分析是哪里消耗的内存了。

2.2 问题拆解

将问题拆分为两部分后,先分析我们自己的内存消耗情况,钉钉SDK回调给我们特征值后,不加入内存,查看内存的消耗情况。注:此时特征值已经从云端拉下来存db,仅几百人,通过接口让dt_sdk不停触发回调加载特征值。这种测试条件下,惊奇的发现,内存不在不停的增加了。于是我们可以断定在md加载特征值的过程中不停的消耗内存。

新的问题来了,我们加载特征值的内存时预先申请好的,理论上,加载过程中,不会再消耗内存,但是我们测试过程中,却明显看到内存再不停的下降。关于查看内存的使用情况,可以使用 cat /proc/meminfo的输出信息中的MemAvailable,这个字段描述新进程可以使用的最大内存,其他各个字段的含义可以google 或者通过 man proc查看。Proc是内核和用户交互的重要窗口,很多重要的数据都可以通过proc打印出来。问题聚焦在我们加载特征值时候的内存使用情况分析。查看相关代码,

这个m_featureTable就是我们存特征值的地址,不知道你有没有发现问题。不错,memset的长度不对,只有指针的大小,32位系统占4个字节。于是我们修改这里memset的大小,期待问题得到解决,修改代码后,再次测试。问题没有如期得到解决,还是看到内存慢慢下降。于是分析陷入停滞,没有突破口。

于是我们又分析了单独调用xx公司拉取特征值这部分接口,查看内存消耗情况,xx公司在x86上也做过同样的事情,不错,在拉特征值的时候,xx公司sdk不停的消耗内存。内存消耗基本上定位在两个阶段,第一,xx公司从云端拉取到设备会消耗大量内存。第二,xx公司sdk加载给dev,dev加载过程中也在消耗大量内存。

2.3 内存管理及分配

    对于云端到xx公司sdk消耗的内存,只能由xx公司测分析。我们只能关注我们自己这一块内存的使用情况,为什么在加载特征值过程中,可用存不停的往下掉。       简单介绍下linux的内存管理:内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,这样进程就可以很方便地访问虚拟内存。

虚拟地址空间的内部又被分为内核空间和用户空间两部分,地址空间的范围也不同。比如最常见的32位的虚拟地址空间:

简单介绍下用户态和内核态,这个描述的是,cpu运行的特权模式,不通的模式所拥有的权限是不通的,用户态模式访问的权限低,能做的事情就有限。进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。既然每个进程都有一个这么大的地址空间,那么所有进程的虚拟内存加起来,自然要比实际的物理内存大得多。所以,并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系,如下图所示:

页表实际上存储在CPU的内存管理单元MMU中,这样,正常情况下,处理器就可以直接通过硬件,找出要访问的内存。而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

内存分配与回收:

malloc()是C标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即brk()和mmap()。

对小块内存(小于128K),C标准库使用brk()来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。

而大块内存(大于128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。

这两种方式,自然各有优缺点。

brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率。不过,由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。而 mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。当这两种调用发生后,其实并没有真正分配内存。这些内存,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。对内存来说,如果只分配而不释放,就会造成内存泄漏,甚至会耗尽系统内存。所以,在应用程序用完内存后,还需要调用 free() 或 unmap() ,来释放这些不用的内存。

3 问题再分析

特征值内存申请了30k * 2KB = 60MB的内存,因此c库应该使用mmap的方式申请内存,而后调用memset初始化内存,此时内核应该会建立虚拟内存和物理内存的映射关系,再接着就可以正常使用这片内存了。但是我们看到的现象确实内存一点点往下掉,不是整片开好的内存。于是怀疑memset没有起作用,否则不会再使用的时候才分配内存。有个这个怀疑点,我们就可以写测试代码,验证刚才的逻辑了,测试代码如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

int main(int argc, char *argv[])

{

    int i = 0;

    int sum = 0;

    char *p = NULL;

    do

    {

        p = (char*)malloc (1024 * 1024 * 100);     

        if (p)

        {

            memset(p, 0, 1024* 1024 * 100);           

            //printf("malloc 1M suc, i:%d\n", i++);

        }

        else

        {

            printf("total malloc %dMB\n",i);

            break;

        }

    }while(0);

 

    for (i=0; i < 1024 * 1024 * 1024; i++)

    {

        sum += i;

    }

    printf("input someting quit\n");

    getchar();

    getchar();

    return 0;

}

 

就是一个分配内存,并memset,其他累和也是之前测试代码,不用关系。那么这段代码运行后,设备上的可用内存变化是什么情况呢?

编译如下:

arm-himix200-linux-gcc -o memTest0  mem.c

设备上运行 ./memTest0,发现分配出了内存。这个说明memset是生效的。

如果我们使用下面这个编译:

arm-himix200-linux-gcc -O2 -o memTest1  mem.c 

设备上运行 ./memTest1,发现没有分配出内存。这个说明memset是没有生效。

讲到这里结论应该很明显了,编译选项中的优化选项-O2 把memset给优化掉了。怎么确认我们的这个推测呢?我们可以使用objdump反汇编来查看最终的可执行文件。使用如下命令:

arm-himix200-linux-objdump -D memTest0 > ~/share/memTest_dump

arm-himix200-linux-objdump -D memTest1 > ~/share/memTest_dump_O2

比较dump信息:

 

很明显,O2优化后的dump没有调用memset了,所以虽然代码中调用了memset,由于开启o2编译优化,memset会被优化掉,使用时才真正分配内存。

4   后记

  1. 文章开始提到的系统直接重启原因分析
  2. 关于开启O2优化,及系统优化的问题

知识这东西就需要经常的核实和订正,尤其是从别人那听来的知识--卓克。

共勉

您的支持是最大的创作动力

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值