Android 4.4 meminfo 实现分析

标签: 前端与客户端技术 android meminfo memtrack | 发表时间:2014-03-24 13:50 | 作者:Roger
出处:http://tech.uc.cn

Android提供了一个名为meminfo的小工具帮助应用分析自身的内存占用,并且在4.4还新增了memtrack HAL模块,SoC厂商通过实现memtrack模块,让meminfo可以获取GPU相关的一些内存分配状况。了解meminfo的实现,对我们更深入了解应用的内存占用状况是很有帮助的。而这篇文章的目的就是分析Android 4.4 meminfo的内部实现源码,让开发者通过这些信息可以更了解自己应用的内存占用状况。

在控制台输入命令”adb shell dumpsys meminfo YOUR-PACKAGE-NAME”,可以看到类似下图的结果:

** MEMINFO in pid 14120 [com.UCMobile.test] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap   187886   187872        0        0   325232   174093    38594
  Dalvik Heap    24801    24444        0        0    41476    35899     5577
 Dalvik Other      700      700        0        0                           
        Stack      508      508        0        0                           
    Other dev    33564    32600        4        0                           
     .so mmap     9019     1244     7268        0                           
    .apk mmap      101        0       16        0                           
    .ttf mmap     1330        0      696        0                           
    .dex mmap     2248        0     2248        0                           
    code mmap      985        0      188        0                           
   image mmap     1182      908       12        0                           
   Other mmap      130        4      108        0                           
     Graphics    25504    25504        0        0                           
           GL     2196     2196        0        0                           
      Unknown    32476    32476        0        0                           
        TOTAL   322630   308456    10540        0   366708   209992    44171

实际的调用代码入口在android.os.Debug.java和对应的CPP文件android_os_Debug.cpp,Debug.java的getMeminfo方法实际上调用了android_os_Debug.cpp的android_os_Debug_getDirtyPagesPid方法。

static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
        jint pid, jobject object)
{
    stats_t stats[_NUM_HEAP];
    memset(&stats, 0, sizeof(stats));

    load_maps(pid, stats);

    struct graphics_memory_pss graphics_mem;
    if (read_memtrack_memory(pid, &graphics_mem) == 0) {
        ...
    }

    ...
}

static void load_maps(int pid, stats_t* stats)
{
    char tmp[128];
    FILE *fp;

    sprintf(tmp, "/proc/%d/smaps", pid);
    fp = fopen(tmp, "r");
    if (fp == 0) return;

    read_mapinfo(fp, stats);
    fclose(fp);
}

从上面的代码可以看到,android_os_Debug_getDirtyPagesPid方法先调用了load_maps方法,而load_maps方法要做的事情也很简单,它打开/proc/PID/smaps虚拟文件,读取里面的信息,在已ROOT的设备上,我们可以通过“adb shell cat /proce/PID/smaps”直接将这个虚拟文件的信息打印在控制台上。

80ff5000-810f2000 rw-p 00000000 00:00 0          [stack:12211]
Size:               1012 kB
Rss:                   4 kB
Pss:                   4 kB
...
81100000-811a4000 rw-s 000f4000 00:0b 6285       /dev/kgsl-3d0
Size:                656 kB
Rss:                 652 kB
Pss:                 352 kB
...
811d1000-811e0000 rw-p 00000000 00:00 0          [anon:libc_malloc]
Size:                 60 kB
Rss:                  60 kB
Pss:                  60 kB
...
Name:           [anon:libc_malloc]

“adb shell cat /proce/PID/smaps”输出的信息如上图所示,它实际上是应用的userspace地址空间的内存分配表,记录了应用分配的每一块内存的地址,类别,大小等信息,而load_maps方法调用read_mapinfo方法从这个表里面读出每一块内存的分配信息,分类进行累加,得出Native Heap,Dalvik Heap等各个类别的内存占用。

但是应用所使用的全部内存里面,有一些内存块是不映射到进程的userspace地址空间的(主要是GPU所使用的内存),这些内存块的信息在smaps里面无法找到,所以在Android 4.4里面新增了一个memtrack的HAL模块由SoC厂商实现,如果SoC厂商实现了memtrack模块,meminfo则可以通过libmemtrack的调用获取一些跟GPU相关的内存使用信息。所以我们看到android_os_Debug_getDirtyPagesPid方法通过调用read_memtrack_memory方法来读取Graphics,GL这两项的内存使用信息。

/*
 * Uses libmemtrack to retrieve graphics memory that the process is using.
 * Any graphics memory reported in /proc/pid/smaps is not included here.
 */
static int read_memtrack_memory(struct memtrack_proc* p, int pid,
        struct graphics_memory_pss* graphics_mem)
{
    int err = memtrack_proc_get(p, pid);
    ...

    ssize_t pss = memtrack_proc_graphics_pss(p);
    ...
    graphics_mem->graphics = pss / 1024;

    pss = memtrack_proc_gl_pss(p);
    ...
    graphics_mem->gl = pss / 1024;

    pss = memtrack_proc_other_pss(p);
    ...
    graphics_mem->other = pss / 1024;

    return 0;
}

read_memtrack_memory方法的实现如上图所示,它读取了Graphics,GL,Other这三类内存信息,而这三个类别的定义在hardware/memtrack.h里面。

/*
 * The Memory Tracker HAL is designed to return information about device-specific
 * memory usage.  The primary goal is to be able to track memory that is not
 * trackable in any other way, for example texture memory that is allocated by
 * a process, but not mapped in to that process' address space.
 * A secondary goal is to be able to categorize memory used by a process into
 * GL, graphics, etc.  All memory sizes should be in real memory usage,
 * accounting for stride, bit depth, rounding up to page size, etc.
 *
 * A process collecting memory statistics will call getMemory for each
 * combination of pid and memory type.  For each memory type that it recognizes
 * the HAL should fill out an array of memtrack_record structures breaking
 * down the statistics of that memory type as much as possible.  For example,
 * getMemory(, MEMTRACK_TYPE_GL) might return:
 * { { 4096,  ACCOUNTED | PRIVATE | SYSTEM },
 *   { 40960, UNACCOUNTED | PRIVATE | SYSTEM },
 *   { 8192,  ACCOUNTED | PRIVATE | DEDICATED },
 *   { 8192,  UNACCOUNTED | PRIVATE | DEDICATED } }
 * If the HAL could not differentiate between SYSTEM and DEDICATED memory, it
 * could return:
 * { { 12288,  ACCOUNTED | PRIVATE },
 *   { 49152,  UNACCOUNTED | PRIVATE } }
 *
 * Memory should not overlap between types.  For example, a graphics buffer
 * that has been mapped into the GPU as a surface should show up when
 * MEMTRACK_TYPE_GRAPHICS is requested, and not when MEMTRACK_TYPE_GL
 * is requested.
 */

enum memtrack_type {
    MEMTRACK_TYPE_OTHER = 0,
    MEMTRACK_TYPE_GL = 1,
    MEMTRACK_TYPE_GRAPHICS = 2,
    MEMTRACK_TYPE_MULTIMEDIA = 3,
    MEMTRACK_TYPE_CAMERA = 4,
    MEMTRACK_NUM_TYPES,
};

Graphics对应了MEMTRACK_TYPE_GRAPHICS,GL对应了MEMTRACK_TYPE_GL,而Other实际上是MEMTRACK_TYPE_OTHER,MEMTRACK_TYPE_MULTIMEDIA,MEMTRACK_TYPE_CAMERA这三项之和。memtrack是由SoC厂商实现的,在AOSP的源码里面我们可以找到高通的实现源码,在msm8974/libmemtrack/kgsl.c里面。

int kgsl_memtrack_get_memory(pid_t pid, enum memtrack_type type,
                             struct memtrack_record *records,
                             size_t *num_records)
{
    ...

    sprintf(tmp, "/d/kgsl/proc/%d/mem", pid);
    fp = fopen(tmp, "r");
    ...

    if (type == MEMTRACK_TYPE_GL) {
        sprintf(tmp, "/proc/%d/smaps", pid);
        smaps_fp = fopen(tmp, "r");
        ...
    }

    while (1) {
        unsigned long uaddr;
        unsigned long size;
        char line_type[7];
        int ret;

        if (fgets(line, sizeof(line), fp) == NULL) {
            break;
        }

        /* Format:
         *  gpuaddr useraddr     size    id flags       type            usage sglen
         * 545ba000 545ba000     4096     1 ----p     gpumem      arraybuffer     1
         */
        ret = sscanf(line, "%*x %lx %lu %*d %*s %6s %*s %*d\n",
                     &uaddr, &size, line_type);
        if (ret != 3) {
            continue;
        }

        if (type == MEMTRACK_TYPE_GL && strcmp(line_type, "gpumem") == 0) {
            bool accounted = false;
            /*
             * We need to cross reference the user address against smaps,
             *  luckily both are sorted.
             */
            while (smaps_addr <= uaddr) {
                unsigned long start;
                unsigned long end;
                unsigned long smaps_size;

                if (fgets(line, sizeof(line), smaps_fp) == NULL) {
                    break;
                }

                if (sscanf(line, "%8lx-%8lx", &start, &end) == 2) {
                    smaps_addr = start;
                    continue;
                }

                if (smaps_addr != uaddr) {
                    continue;
                }

                if (sscanf(line, "Rss: %lu kB", &smaps_size) == 1) {
                    if (smaps_size) {
                        accounted = true;
                        accounted_size += size;
                        break;
                    }
                }
            }
            if (!accounted) {
                unaccounted_size += size;
            }
        } else if (type == MEMTRACK_TYPE_GRAPHICS && strcmp(line_type, "ion") == 0) {
            unaccounted_size += size;
        }
    }

    ...
}

kgsl_memtrack_get_memory是memtrack的getMemory方法的具体实现,我们可以看到它实际上是读取一张内部的GPU内存分配表的信息(虚拟文件/d/kgsl/proc/PID/mem),在已ROOT的设备上,我们可以通过“adb shell cat /d/kgsl/proc/PID/mem”将这张内存分配表的信息打印到控制台上,如下图所示:

gpuaddr useraddr     size    id flags       type            usage sglen
7565e000 00000000     4096     1 ----p     gpumem      arraybuffer     1
756bc000 00000000    65536     2 -r--p     gpumem          command    16
756cd000 00000000    65536     3 -r--p     gpumem          command    16
756de000 00000000    65536     4 -r--p     gpumem          command    16
756fb000 00000000     4096     5 ----p     gpumem               gl     1
75fe2000 00000000   262144     6 ----p     gpumem               gl    64
76023000 00000000     8192     7 ----p     gpumem               gl     2
76026000 00000000     8192     8 ----p     gpumem               gl     2
76029000 00000000     4096     9 ----p     gpumem          texture     1
...
94d71000 00000000   131072   362 ----p     gpumem  vertexarraybuff    32
94da0000 00000000   667648   176 --l-p     gpumem          texture   163
94e44000 00000000   131072   363 ----p     gpumem           any(0)    32
94e65000 00000000   131072   364 ----p     gpumem           any(0)    32
c0000000 00000000 17268736    31 --L--        ion        egl_image  4216
c1100000 00000000  8257536    36 --L--        ion      egl_surface    21
c1900000 00000000  8257536   164 --L--        ion      egl_surface    21
c2100000 00000000  8257536   175 --L--        ion      egl_surface    21

其中ion类型(由ION内存分配器分配的内存)的内存块统计到Graphics类别里面,从上图我们可以看到有三块egl_surface,它们对应应用所使用的窗口的三个Buffer,还有一个egl_image暂时不清楚用途,这些都是应用启动后Android自动分配的。gpumem类型的内存块统计到GL类别里面,包括GL里面的纹理(texture),各种shader,vertex buffer等等。另外,因为有些内存块映射到了userspace,有些则没有映射,所以映射到userspace的内存块会被标记为accounted,避免meminfo重复计数,meminfo最终显示的Graphics和GL的内存值是哪些没有映射到userspace的内存块的大小之和。

相关 [android meminfo 分析] 推荐:

Android 4.4 meminfo 实现分析

- - UC技术博客
Android提供了一个名为meminfo的小工具帮助应用分析自身的内存占用,并且在4.4还新增了memtrack HAL模块,SoC厂商通过实现memtrack模块,让meminfo可以获取GPU相关的一些内存分配状况. 了解meminfo的实现,对我们更深入了解应用的内存占用状况是很有帮助的. 而这篇文章的目的就是分析Android 4.4 meminfo的内部实现源码,让开发者通过这些信息可以更了解自己应用的内存占用状况.

Android应用性能 分析

- - CSDN博客推荐文章
  其实主要是内存方面,内存管理是个永恒的话题. 1.从工具DDMS中,在Sysinfo的tab栏里面有一个Memory usage的选项,通过USB连接Android设备以后很容易抓到图. 在图中可以看到系统随时可以用的内存是Free和Buffers两项,因为我抓图的系统只有128M的内存,所以看上去这部分可用内存已经很少了.

Android 系统架构分析

- - CSDN博客移动开发推荐文章
Android:开源的 Linux + Google 的封闭软件 + 私有的基带 + 运营商锁定 = 开放的 Android 手机. iPhone:开源的 BSD + 苹果的闭源软件 + 私有的基带 + 运营商锁定 = 封闭的苹果 iPhone. 一个平庸的应用商店,开发者依靠广告赚钱,商店并非独此一家,用户找不到好软件.

Android木马分析简介

- - 极客范 - GeekFan.net
本文介绍基于Android的手机恶意软件,是一个基础性的介绍,给新入门的人提供一个分析和工具指引. 要分析的木马是一个2013年的syssecApp.apk,这个木马的分析能对Android恶意软件有个大概了解. Android是google开发基于Linux内核的开源的手机操作系统,应用程序使用JAVA语言编写并转换成了Dalvik虚拟机,而虚拟机则提供了一个抽象的真实硬件,只要和操作系统的API符合程序都可以在其上运行.

Android OOM案例分析

- - 美团点评技术团队
在Android(Java)开发中,基本都会遇到 java.lang.OutOfMemoryError(本文简称OOM),这种错误解决起来相对于一般的Exception或者Error都要难一些,主要是由于错误产生的root cause不是很显而易见. 由于没有办法能够直接拿到用户的内存dump文件,如果错误发生在线上的版本,分析起来就会更加困难.

Android 4.0应用界面设计分析

- - 所有文章 - UCD大社区
我总是觉得,直到谷歌在2011年10月19日上午十点,公布了有关Android 4.0系统信息的时候,Android与IOS的火拼才算正式开始.  虽然苹果对待这个眼中的“私生子”始终贯彻着严厉的专利打压政策,但Android在全球的市场份额却保持着快速增长. 势如破竹的表象背后,是潜藏已久的、巨大的中低端移动市场.

Linux 和 Android 系统性能分析

- - CSDN博客综合推荐文章
作为一名Linux 或 Android 平台的系统工程师,在开发系统新功能外,主要工作就是优化系统性能,使系统上以最优的状态运行,但是由于硬件问题、软件问题、网络环境等的复杂性和多变性,导致对系统的优化变得异常复杂,如何定位性能问题出在哪个方面,是性能优化的一大难题, 从系统入手,阐述由于系统软、硬件配置不当可能造成的性能问题,并且探讨检测系统故障和优化性能的一般方法和流程.

Android短信蠕虫XXshenqi分析

- - 极客范 - GeekFan.net
今天从余弦大大微博上看到了这款Android短信蠕虫的信息,于是自己下载了一款研究,看到网上很多报告的md5值不同,不知道是否是变种. 接下来就分析一下,这款病毒究竟做了些什么. 病毒分为两个部分,一个是XXshenqi.apk,另一个是com.android.Trogoogle.apk. 后者存在于前者解压文件下的assets目录中.

Android推送方案分析(MQTT/XMPP/GCM)

- - 移动开发 - ITeye博客
本文主旨在于,对目前Android平台上最主流的几种消息推送方案进行分析和对比,比较客观地反映出这些推送方案的优缺点,帮助大家选择最合适的实施方案. 方案1、使用GCM服务(Google Cloud Messaging). 简介:Google推出的云消息服务,即第二代的C2DM. 优点:Google提供的服务、原生、简单,无需实现和部署服务端.

外媒分析称惠普webOS死于iOS和Android枪下

- 洞箫 - cnBeta.COM
据美国媒体报道,8月18日,惠普宣布停止运营基于webOS系统的平板电脑和智能手机,此举令业界震惊. 硅谷人士纷纷评论指出,webOS其实是“死在 了安卓(Android)和iOS的枪下”,也有人士认为,对于那些不满谷歌涉足手机硬件的厂商,webOS也将会是一个“倒戈”选项.