Android 4.4 meminfo 实现分析
Android提供了一个名为meminfo的小工具帮助应用分析自身的内存占用,并且在4.4还新增了memtrack HAL模块,SoC厂商通过实现memtrack模块,让meminfo可以获取GPU相关的一些内存分配状况。了解meminfo的实现,对我们更深入了解应用的内存占用状况是很有帮助的。而这篇文章的目的就是分析Android 4.4 meminfo的内部实现源码,让开发者通过这些信息可以更了解自己应用的内存占用状况。
在控制台输入命令”adb shell dumpsys meminfo YOUR-PACKAGE-NAME”,可以看到类似下图的结果:
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方法。
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”直接将这个虚拟文件的信息打印在控制台上。
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这两项的内存使用信息。
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里面。
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里面。
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”将这张内存分配表的信息打印到控制台上,如下图所示:
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的内存块的大小之和。