您的位置:首页 > 移动开发 > Android开发

Android4.4 meminfo 实现分析

2015-12-15 16:21 162 查看

Android 4.4 meminfo 实现分析

Posted by
Roger
on 2014 年 3 月 24 日

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

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

C

** 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

**
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方法。

C

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);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

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”直接将这个虚拟文件的信息打印在控制台上。

C

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]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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这两项的内存使用信息。

C

/*
* 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;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

/*

* 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里面。

C

/*
* 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,
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

/*

* 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里面。

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;
}
}

...
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

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”将这张内存分配表的信息打印到控制台上,如下图所示:

C

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

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的内存块的大小之和。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: