休眠唤醒cma内存泄漏

现象分析

现场排查

起因keeptesting休眠唤醒出现vmap allocation申请内存失败,log打印如下

1
2
3
4
5
6
7

[ 7499.814118] [ion_debug]In ion_buffer_destroy: release buf size 249856, phys_addr 70bc0000, process[1947:composer@2.1-se]
[ 7499.899841] vmap allocation for size 5537792 failed: use vmalloc=<size> to increase size
[ 7499.910499] platform cma: Fail to allocate buffer
[ 7499.941821] vmap allocation for size 5537792 failed: use vmalloc=<size> to increase size
[ 7499.952483] platform cma: Fail to allocate buffer
[ 7499.983029] vmap allocation for size 5537792 failed: use vmalloc=<size> to increase size

通过查看显示相关的进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
t7-p3:/ # procrank
PID Vss Rss Pss Uss Swap PSwap USwap ZSwap cmdline

2387 1050156K 141496K 43392K 32760K 0K 0K 0K 0K com.android.systemui

------ ------ ------ ------ ------ ------ ------
479314K 354572K 708K 708K 708K 534K TOTAL

ZRAM: 676K physical used for 896K in swap (754616K total swap)
RAM: 1006160K total, 50708K free, 2984K buffers, 513540K cached, 1016K shmem, 46584K slab

查看对应的dma_buf分配情况,发现个数不断的增大

procmem -p 2387 | grep dmabuf | grep 1948 | wc -l

通过在ion_map的时候将进程信息添加到对应的ion debug节点中,unmap再删除,看到异常的dmabuf对应的进程;对应的是2880 systemui和launcher

1
2
3
4
5
6
7
8
9
t7-p3:/ # cat /sys/kernel/debug/ion/heaps/cma | grep dmabuf | grep 1994
allocator@2.0-s 1966 1994752 0 1 | 2217/android.display dmabuf:172 | 3426/Binder:3413_2 dmabuf:172 |
allocator@2.0-s 1966 1994752 0 1 | 3168/Binder:2880_6 dmabuf:d3 | 9217/Binder:3413_4 dmabuf:d3 |
allocator@2.0-s 1966 1994752 0 1 | 9347/Binder:2880_9 dmabuf:13e | 3426/Binder:3413_2 dmabuf:13e |
allocator@2.0-s 1966 1994752 0 1 | 2217/android.display dmabuf:17a | 9217/Binder:3413_4 dmabuf:17a | 2894/Binder:2880_1 dmabuf:17a |
allocator@2.0-s 1966 1994752 0 1 | 2217/android.display dmabuf:16a | 2894/Binder:2880_1 dmabuf:16a | 3426/Binder:3413_2 dmabuf:16a |
allocator@2.0-s 1966 1994752 0 1 | 3426/Binder:3413_2 dmabuf:105 | 9168/Binder:2880_7 dmabuf:105 |
allocator@2.0-s 1966 1994752 0 1 | 2895/Binder:2880_2 dmabuf:a5 | 9217/Binder:3413_4 dmabuf:a5 |
allocator@2.0-s 1966 1994752 0 1 | 9347/Binder:2880_9 dmabuf:da | 9217/Binder:3413_4 dmabuf:da |

跟踪logcat中gralloc alloc的时候的打印,发现异常截图size与截图的大小一致;

1
2
3
03-26 19:34:17.096  1980  2160 D SurfaceFlinger: captureLayers here sourcecrop = 0 0 - 1920 720
03-26 19:34:17.108 2186 2212 E KernelCpuSpeedReader: Failed to read cpu-freq: /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state (No such file or directory)
03-26 19:34:17.109 1965 2107 I [Gralloc]: ion_alloc from ion_client:5 via heap type DMA(mask:16) for 1990720 Bytes cached buffer successfully, usage = 0x00000333

内存回收

首先怀疑是否为安卓内存没有回收导致

强制触发内存回收

方式一:kill -10 pid
方式二:am dumpheap com.test.test /sdcard/test.hprof
方式三:在进程onReceive中添加System.gc() ;然后在adb shell中输入 am  broadcast -a INTENT_ACTION_NAME_HERE

安卓系统内存回收可以分为三种情况:

第一,用户程序调用StartAcitity(),使当前的活动的Activity被覆盖;
第二,用户按BACK键,退出当前应用程序;
第三:启动一个新的应用程序。这些能够触发内存回收的时间最终调用的函数接口就是activityIdleInternal

通过kill -10 pid 的方式,触发安卓内存进行回收,发现内存没有释放;

从内核debug节点也看到此问题是上层systemui和launcher映射的fd没有释放导致;

休眠截屏流程

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
services/core/java/com/android/server/wm/TaskSnapshotController.java
/**
* Called when screen is being turned off.
*/
void screenTurningOff(ScreenOffListener listener) {
if (shouldDisableSnapshots()) {
listener.onScreenOff();
return;
}

// We can't take a snapshot when screen is off, so take a snapshot now!
mHandler.post(() -> {
try {
synchronized (mService.mWindowMap) {
mTmpTasks.clear();
mService.mRoot.forAllTasks(task -> {
if (task.isVisible()) {
mTmpTasks.add(task);
}
});
snapshotTasks(mTmpTasks);
}
} finally {
listener.onScreenOff();
}
});
}

在休眠老化过程中,将shouldDisableSnapshots默认配置为true,没有出现内存泄漏,更加确定与截屏流程有关;

进一步确认:
配合dumpsys命令,传入虚拟内存地址和长度,最终调用如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void native_dumpAddr(JNIEnv* /* env */, jobject /* clazz */, jint addr, jint len) {
std::stringstream ss;
ss << "/storage/emulated/0/" << std::hex << addr;
const char * path = ss.str().c_str();

int fd = open(path, O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND, 0666);
if (fd < 0) {
ALOGI("error opening: %s: %s", path, strerror(errno));
return;
}
write(fd, (void *)addr, len);

close(fd);
}

直接在泄漏进程中直接dump虚拟内存内容,转换成图片格式后,确认为截图;

测试场景脚本化

在出现异常的现场时,如果能简化到某些行为,可以使debug的范围很大程度的缩小;在验证问题是否解决的时,也更容易判断

1
2
3
4
5
6
7
8
9
10
11
12
13
while true;
do
am start -n com.android.calculator2/com.android.calculator2.Calculator
sleep 0.5
am start -n com.android.settings/com.android.settings.Settings
sleep 0.5
done

while true;
do
input keyevent POWER
sleep 1
done

怀疑方向

从泄漏问题来看,有两点有疑问,一: 主界面为什么不泄露,设置界面泄漏?二:为什么与截屏流程有关,且影响到systemUI和launcher?

主界面没有泄漏原因

从log上看主界面之所以没有泄漏,是因为主界面不走截屏的流程;那么下一步的动作就是看为什么主界面没有截屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Activity type is currently not defined. */
public static final int ACTIVITY_TYPE_UNDEFINED = 0;
/** Standard activity type. Nothing special about the activity... */
public static final int ACTIVITY_TYPE_STANDARD = 1;
/** Home/Launcher activity type. */
public static final int ACTIVITY_TYPE_HOME = 2;
/** Recents/Overview activity type. There is only one activity with this type in the system. */
public static final int ACTIVITY_TYPE_RECENTS = 3;
/** Assistant activity type. */
public static final int ACTIVITY_TYPE_ASSISTANT = 4;

int getSnapshotMode(Task task) {
final AppWindowToken topChild = task.getTopChild();
if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
return SNAPSHOT_MODE_NONE;//主界面
} else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
return SNAPSHOT_MODE_APP_THEME;
} else {
return SNAPSHOT_MODE_REAL;//设置界面
}
}

结合打印和代码可以发现,由于主界面activity属性为ACTIVITY_TYPE_HOME,故不截屏;而设置界面中,activity属性为ACTIVITY_TYPE_STANDARD;故进行截屏;

dumpsys window

window

并无出现截图数量递增的情况;

安卓P上默认无论是否为低内存设备不再提供禁止截屏的接口,把截屏的第一帧动画当做第一帧进行显示;低内存只能更改第一帧的采样率

services/core/java/com/android/server/wm/TaskSnapshotController.java
    final float scaleFraction = isLowRamDevice ? 1f : 1f;

截屏作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
./services/core/java/com/android/server/am/ActivityRecord.java showStartingWindow
showStartingWindow
addStartingWindow //添加启动窗口
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
false /* restoreFromDisk */, false /* reducedResolution */);//休眠的时候系统会通过TaskController进行截图

final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
allowTaskSnapshot, 、activityCreated, fromRecents, snapshot);
获取启动窗口的类型,如果启动串口的类型是STARTING_WINDOW_TYPE_SNAPSHOT时,使用snapshot内容作为启动窗口内容
一般我们从recents界面进入应用时,或者应用被压入后台再次点击桌面图标进入应用时走该流程;
窗口类型:
STARTING_WINDOW_TYPE_NONE:不添加starting window
STARTING_WINDOW_TYPE_SNAPSHOT:使用任务快照作为starting window的显示内容
STARTING_WINDOW_TYPE_SPLASH_SCREEN:默认行为,该界面显示的内容搜应用主题相关属性设置的影响

一般情况下我们可以通过设置theme的windowDisablePreview的属性为true禁止显示starting window,如果我们设置了应用窗口背景透明或者使用了float属性为true类型的窗口时,这个时候启动应用时不会添加starting window的。
对于启动窗口的内容何时使用snapshot内容合适受应用的theme影响。我们可以以应用启动方式不同来做一判断,一般应用冷启动和温启动的时候staring window的显示容易受theme影响,应用通过Recent启动或者热启动时,starting window会显示snapshot的内容
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
return createSnapshot(snapshot);
}

即截图的图片在启动动画的时候会用来做第一帧动画;如果有keyguard的时候,唤醒启动的时候则会去那休眠前截好的图作为第一帧动画显示;没有则不用走到这里来;T7没有keyguard,故休眠唤醒的流程也不会经过这里;

SystemUi截屏相关流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
启动流程:
startOtherServices()
startSystemUi(context, windowManagerF);
intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SystemUIService"));
context.startServiceAsUser(intent, UserHandle.SYSTEM);

src/com/android/systemui/SystemUIService.java
onCreate()
((SystemUIApplication) getApplication()).startServicesIfNeeded();
mServices[i].start();
这时候就会去起recent的服务

截图相关流程
getThumbnail 只是提供缩略图的作用
snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
return task.getSnapshot(reducedResolution);
task = mStackSupervisor.anyTaskForIdLocked(taskId,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
return task.getSnapshot(reducedResolution);

在休眠唤醒的流程里面,添加打印,并未发现有systemui进入到getSnapshot的相关操作。至此在应用排查阶段一直没有进展;另外烧录gsi同样能发现内存泄漏的问题;怀疑还是自己平台修改导致的可能性比较大;故在目光转移至gralloc分配流程;

进程的地址空间

进程的地址空间(address space)由允许进程使用的全部线性地址(memery region,其含义通常所指的虚拟内存的一个区间,可以称为虚存区 VMA Virtual Memory Area )组成。每个进程所看到的线性地址集成是不同的,一个进程所使用的地址和另外一个进程所使用的地址之间没有什么关系。后面我们还会看到,内核可以通过增加或删除某些线性地址区间来动态地修改进程的地址空间;

内存描述符:与进程地址空间有关的全部信息都包含在一个叫做内存描述符(memory descriptor)的数据结构中,这个类型为mm_struct,进程描述符的mm字段就指向这个内存结构;

ION基本概念

ION作用:用于用户空间的进程之间或者内核空间的模块之间进行内存共享;而且这种共享是0拷贝的;

对于ION的基本概念可以看下篇文章;

内存管理–ION

free时机

一般我们认为当ref_count = 0的时候就会去release这块内存

1
2
3
4
5
6
7
8
9
10
drivers/staging/android/ion/ion.c
ion_buffer_put(struct ion_buffer *buffer)
return kref_put(&handle->ref, _ion_handle_destroy);
return kref_sub(kref, 1, release);
if (atomic_sub_and_test((int) count, &kref->refcount)) {
release(kref);
ion_buffer_destroy
buffer->heap->ops->free(buffer);
vfree(buffer->pages);
kfree(buffer);

可以看到ion buffer的free动作是在在引用计数为0的时候去清理的;实际此时当不同进程都map到同一个buffer的时候,这时候还有一个引用计数会决定是否真正的close;

通过ioct free的时候

Object Operations Ion buffer ref count Ion buf status
allocator@2.0-s(alloc_device_alloc) 1.ion_alloc 1 allocated
2.ion share:dmabuf fd1 2
surfaceflinger gralloc_register_buffer
(map)
2
surfaceflinger gralloc_lock 2
surfaceflinger gralloc_unlock 2
surfaceflinger gralloc_unregister_buffer
(unmap)
2
allocator@2.0-s(alloc_device_free) 3.close(ion buffer个数不减少) 2
4.ion_free 1(2 -> 1) using
systemUI gralloc_register_buffer
(map)
1 using
systemUI FinalizerDaemon
gralloc_unregister_buffer
(unmap)
0(1 -> 0) freed

内存映射

mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和虚拟内存

mmap流程

mmap三个阶段:

一:进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域;

二:调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

三:进程发起对这片映射空间的访问,引发缺页异常;实现文件内容到物理内存(主存)的拷贝

内存映射类型:

私有内存映射:多个进程会创建一个新的映射,各个进程不共享,也不会反应到物理文件中,比如linux.so动态库就是采用这种形式映射到各个进程虚拟地址空间中;

私有匿名映射:mmap会创建一个新的映射,各个进程不共享,这种使用主要用于分配内存

共享文件映射:多个进程通过虚拟内存技术共享同样的物理内存空间,对内存文件会反映到实际物理文件中,他也是进程通信的机制

共享匿名映射:这种机制在fork的时候不会采用写时复制,父子进程完全共享同样的物理内存,这也就实现了父子间通信(IPC)

匿名的意思是需不需要一个fd,正常来讲,当需要进行内存映射的时候,我们都需要依赖一个文件才能实现;通常需要open一个temp文件,穿件后unlink,close掉,比较麻烦;可以直接使用匿名映射来代替,linux也提供了这么一套机制给我们;无需依赖文件即可创建。:

如:int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

映射流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DPATH="/sys/kernel/debug/tracing"
echo > $DPATH/trace
echo nop > $DPATH/current_tracer


echo > $DPATH/set_ftrace_filter
echo "SyS_mmap_pgoff SyS_old_mmap SyS_munmap SyS_open filp_open " >> $DPATH/set_ftrace_filter
echo "do_brk elf_map load_elf_binary" >> $DPATH/set_ftrace_filter
echo "do_mmap do_munmap get_unmapped_area mmap_region vm_mmap vm_munmap vm_mmap_pgoff" >> $DPATH/set_ftrace_filter
echo "__split_vma unmap_region split_vma" >> $DPATH/set_ftrace_filter

echo function_graph > $DPATH/current_tracer
echo 2542 > $DPATH/set_ftrace_pid

echo 1 > $DPATH/tracing_on
cat $DPATH/trace > /data/trace.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 mappedAddress = (unsigned char *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, hnd->share_fd, 0);

1) | SyS_mmap_pgoff() {
1) | vm_mmap_pgoff() {
--> | do_mmap_pgoff
1) | do_mmap() {
1) + 27.334 us | get_unmapped_area();
1) + 15.792 us | mmap_region();
1) + 53.583 us | }
1) + 60.958 us | }
1) + 66.250 us | }

munmap(base, size)

1) | SyS_munmap() {
1) | do_munmap() {
1) + 85.084 us | unmap_region();
1) ! 105.667 us | }
1) ! 111.458 us | }

do_map

1
2
3
4
5
6
7
8
9
10
11
12
do_mmap
//获取未映射的vma
addr = get_unmapped_area(file, addr, len, pgoff, flags);
//先检查vma分配后是否超过进程限制,之后再rb_tree中找到满足本次申请条件vma的前区vma区(内核有很多红黑树存储的数据结构,尤其在内存中)
//如果两个vma能合并就合并成一个,否则就新申请的vma插入rb_tree中。
//在整个过程中,我们能看到的只有vma的申请和磁盘文件的关联
addr = mmap_region(file, addr, len, vm_flags, pgoff);
vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX, NULL);
vma->vm_file = get_file(file);
atomic_long_inc(&f->f_count);
error = file->f_op->mmap(file, vma);通过fd进行内存映射

unmap流程

1
2
3
4
5
6
7
8
9
10
11
12
do_munmap
vma = find_vma(mm, start); //找到起始地址落在哪个vma内
unmap_region(mm, vma, prev, start, end);
error = __split_vma(mm, vma, start, 0); //上面有讲到如果两个vma能合并就合并,解映射的时候分离出来
remove_vma_list(mm, vma);
vma = remove_vma(vma);
fput(vma->vm_file);
atomic_long_dec_and_test(&file->f_count)
schedule_delayed_work
delayed_fput
__fput
file->f_op->release(inode, file);

释放时的log

1
2
3
4
5
6
7
8
9
10
11
12
13
[ 1689.870445] ion_buffer_put count:0 phys_adr 72100000 
[ 1689.877035] CPU: 2 PID: 3353 Comm: FinalizerDaemon Tainted: G O 4.9.118 #2
[ 1689.885982] Hardware name: sun8iw17
[ 1689.889931] [<c0111184>] (unwind_backtrace) from [<c010cd1c>] (show_stack+0x20/0x24)
[ 1689.898604] [<c010cd1c>] (show_stack) from [<c0478c5c>] (dump_stack+0x78/0x94)
[ 1689.906703] [<c0478c5c>] (dump_stack) from [<c06fb5f4>] (ion_buffer_put+0xc0/0xd4)
[ 1689.915177] [<c06fb5f4>] (ion_buffer_put) from [<c06fb7fc>] (ion_dma_buf_release+0x1c/0x20)
[ 1689.924520] [<c06fb7fc>] (ion_dma_buf_release) from [<c0585b60>] (dma_buf_release+0x64/0x17c)
[ 1689.934049] [<c0585b60>] (dma_buf_release) from [<c02843cc>] (__fput+0xf4/0x1d0)
[ 1689.942319] [<c02843cc>] (__fput) from [<c0284518>] (____fput+0x18/0x1c)
[ 1689.949806] [<c0284518>] (____fput) from [<c013f574>] (task_work_run+0xc8/0xd8)
[ 1689.957976] [<c013f574>] (task_work_run) from [<c010c458>] (do_work_pending+0xac/0xcc)
[ 1689.966819] [<c010c458>] (do_work_pending) from [<c0107f60>] (slow_work_pending+0xc/0x20)

只有当映射同一块进程都解除映射后,file->f_count,为0;才会调到ion_buffer_put,此时ion_buffer计数为0,释放该buffer

userspace 使用

mali-utgard/gralloc/src/gralloc_module.cpp

进程创建buffer的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
static struct hw_module_methods_t gralloc_module_methods =
{
.open = gralloc_device_open
};

gralloc_device_open
alloc_device_open
m->ion_client = ion_open();
alloc_device_alloc
gralloc_alloc_buffer
ret = aw_ion_alloc(&(m->ion_client), size, 0, heap_mask, flags, &(ion_hnd), usage);
ret = ion_share(m->ion_client, ion_hnd, &shared_fd);
cpu_ptr = mmap(NULL, size, map_mask, MAP_SHARED, shared_fd, 0);

结构体gralloc_module_t定义在文件hardware/libhardware/include/hardware/gralloc.h中,它主要是定义了四个用来操作图形缓冲区的成员函数,如下所示:

1
2
3
4
5
6
7
8
9
private_module_t::private_module_t()
{
```
base.registerBuffer = gralloc_register_buffer;
base.unregisterBuffer = gralloc_unregister_buffer;
base.lock = gralloc_lock;
base.unlock = gralloc_unlock;
```
}

registerBuffer和unregisterBuffer分别用来注册和注销一个指定的图形缓冲区,这个指定的图形缓冲区使用一个buffer_handle_t句柄来描述。所谓注册图形缓冲区,实际上就是将一块图形缓冲区映射到一个进程的地址空间,注销图形缓冲区则是相反的过程;成员函数lock和unlock分别用来锁定和解锁一个缓冲区,例如向一块图形缓冲写入内容的时候,需要将图形缓冲区锁定。用来避免访问冲突;

gralloc 流程debug

对于systemui来讲,在此过程并不会去申请buffer,而是通过内存映射,所以我们主要关注register和unregiser的流程

添加引用计数:

gralloc_register_buffer
     if (size == 1990720) {
                   getNameByPid(hnd->pid,task_name);       
                   count = count + 1;
                   AERR(" 0x%p process %d  task_name:%s size:%d share_fd:0x%x count:%d ", hnd, hnd->pid, task_name, size, hnd->share_fd, count);
     }

gralloc_unregister_buffer        
if (size == 1990720) {
                   getNameByPid(hnd->pid,task_name);       
                   count= count -1;
                   AERR(" 0x%p process %d  task_name:%s size:%d share_fd:0x%x count:%d ", hnd, hnd->pid, task_name, size, hnd->share_fd, count);
}

应用计数log:

1
2
3
gralloc_unregister_buffer:345  0x0xa96c52c0 process 2327  task_name:ndroid.systemui size:1990720 share_fd:0x5b count:2
gralloc_unregister_buffer:345 0x0xaeca7400 process 2327 task_name:ndroid.systemui size:1990720 share_fd:0x59 count:1
gralloc_unregister_buffer:345 0x0xaeccd7f0 process 2327 task_name:ndroid.systemui size:1990720 share_fd:0x4c count:0

通过打印发现,systemUI的register动作和unregister的动作是匹配的,count也能清零;但依然进程中的dmabuf异常存在泄漏

对应的dmabuf如下:

1
2
t7-p3:/ # procmem  -p 2327 | grep dmabuf
1948K 0K 0K 0K 0K 0K 0K 0K anon_inode:dmabuf-c3

且kill -10 后系统也没有完成回收,说明此时已经存在内存泄漏

查看unregister相关代码发现

1
2
3
4
5
6
7
8
9
10
gralloc_unregister_buffer
....
/* if handle is still locked, the unmapping would not happen until unlocked*/
if (!(hnd->lockState & private_handle_t::LOCK_STATE_WRITE))
{
unmap_buffer(hnd);
}

hnd->lockState |= private_handle_t::LOCK_STATE_UNREGISTERED;
....

当存在LOCK_WRITE标识的时候,此时不会被unmap,那么什么时候这个标志是什么时候被赋值的呢?surfaceFlinger在创建这块内存时,对于surfaceFlinger而言;由于截图的动作比较频繁,会调用lock为buffer添加锁保护,当写完之后unlock;这时候systemUI恰好映射到这块内存恰好会将其标志拷贝;对于systemUI而言,本身不会有写的操作,故更不会有unlock的动作;所以就发生了泄漏;解决这个问题的方法。就是在进程映射内存的时候;将这个标志清除掉;

1
2
gralloc_register_buffer
hnd->lockState &= ~(private_handle_t::LOCK_STATE_WRITE);

安卓O同样存在同样的问题,但由于项目一开始为了减少设备的内存使用,所以禁止了相关截图的功能,在androidP上,谷歌没有提供相关的全局禁止功能,故问题就暴露出来;

补丁如下:

1
2
3
4
5
在方案下面的:venus_a1.mk
Disable the task snapshots feature
PRODUCT_PROPERTY_OVERRIDES += \

persist.enable_task_snapshots = false

补充(代办)

上层的gralloc流程

systemUI如何map到surfaceFlinger

参考资料

gralloc流程

安卓性能优化:关于内存泄漏的知识都在这里了

Activity启动窗口和TaskSnapshot

Android操作系统的内存回收机制

信号处理

Android SurfaceFlinger学习之路

认真分析mmap:是什么 为什么 怎么用

Linux内存管理(9)mmap补充

匿名映射