博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android相机场景下整机内存分析
阅读量:2351 次
发布时间:2019-05-10

本文共 7871 字,大约阅读时间需要 26 分钟。

背景

在开发高性能软件领域,对于一名系统工程师而言,比较重要的要求是要懂内存优化。因为内存是系统宝贵的资源,软件性能跟内存是否充足是密切相关的。

所以尤其是这种嵌入式终端系统中,内存有限场景下,更要熟悉系统的整机内存分布状况。知道一旦出内存问题时,该怎么去辨识和分析系统哪块内存占用有异常。

并熟悉怎么去优化系统各个模块的内存占用,来为某些很耗内存资源的多媒体软件(比如相机)的性能优化工作服务。

所以下面wiki, 还有另外一篇: 相机场景下整机内存优化 会重点讲这些东西。

系统整机内存分析

一个操作系统的整机内存分布是比较复杂的,系统的各个模块都会占用内存。在讲述这方面时,最好先把握整体,避免陷入单个模块的技术细节和泥潭。

出内存问题时,先从整体上分析,看跟下面3大块中具体哪块的内存占用相关,然后再具体细致深挖下。

在手机android系统里,相机使用场景下,整机内存整体分为3大块:

一 进程用户态管理的内存

1 特点和具体包含哪些

这些内存大都是进程在用户态工作需要而申请的,都会被map到系统所有进程的地址空间里面,供用户使用,具体通过cat /proc/pid/{maps, smaps},都可以看到这些内存包含在一个个已映射的vma里面。

这些内存都会算到进程的pss内存统计里面, 具体大小应该是进程pss值 - gpu部分。

想查看具体包含哪些内存时,可以通过dumpsys meminfo pid命令看到,包含进程栈,堆内存,ashmem mapped部分等。

2 系统对它的管理特色

1) 内存分配方面:

该部分是延迟分配和写时复制,系统认为该部分内存分配需求,和内核态分配相比,并不是很紧急,可以缓缓。

所以分配时只是分配出虚拟地址空间,然后等到该进程对该虚拟地址进行读写访问时,才会通过系统缺页异常,陷入到内核态去分配实际的物理内存页。

2) 内存回收方面:

典型的特点是该内存可以在系统有内存压力时,随时会被系统回收掉。

具体回收思路就是:

该内存分配后,会被挂到内核的Lru链表上,然后文件页有脏的会被回写,干净的直接被抛弃掉,匿名页被压缩写到zram中。另外该内存还可以被通过杀进程方式直接释放掉的。

所以该部分内存分配过多,导致相机出内存性能问题时,系统还可以帮忙释放内存压力,解决该问题。

二 内核管理的内存

1: 特点和具体包含哪些

该部分内存都不是进程在用户态申请的,而是进程陷入到内核态申请的,或者是内核自身为了管理工作需要而申请的。

比如内核态中断上下文申请,或者文件系统做io操作自身申请的内存,还有进程fork时需要内核申请的内存等这些都是属于内核管理的内存。

android里面列出了这部分内存的统计方式:

总大小=KernelCached + KernelUsed

KernelCached = kreclaimable           KernelUsed = shmem + slab_unreclaimable + vm_alloc_used + page_tables + zram

上面具体的kreclaimable,shmem等可以通过cat /proc/meminfo查看到具体它们的大小。

2 系统对它的管理特色

1) 内存分配方面

这些内存在分配时,就会搞到实际的物理内存。

2) 内存回收方面

kernelcached部分可以在有系统内存压力时,被回收掉(通过那个shrink_slab函数来做).

KernelUsed部分则无论怎么有系统压力,也不能被回收掉了。

所以内核态不要出现内存泄露问题,否则系统很难纠正。用户态出现内存泄露了,系统可以纠正错误的,直接oom或者通过Lru链表被匿名页回收了。

三 驱动自身申请的内存

1: 特点和具体包含哪些

上面两部分内存,内核态的内存管理都能cover到,memcg也能负责管理到。但是这一部分内存,由于位于内核的driver/stanging目录,非内核主线分支,内核的内存管理不一定能照顾的周到。

这部分内存在相机场景下,指的就是Ion和gpu驱动自身申请的内存。

ion内存=已经used + page pool缓存,两个大小可以通过/sys/kernel/ion/{total_heaps_kb, total_pools_kb}文件得知.

gpu内存大小可以累加进程的Gfx dev和GL mtrack大小,便可以得知。

2 系统对它的管理特色

1: 内存回收方面

和内核管理的内存一样,缓存部分可以在有内存压力时被释放,used部分任何时候都不能被回收,所以要注意used部分不能出现内存泄漏。

2: 经常会被mapped到用户空间,供进程使用

这个是这部分内存管理的特色,是处于内核内存管理和用户态内存管理之间交织地带的管理。

具体表现为(高通平台上的):

ion和gpu内存都是进程在分配时陷入到内核态主动申请的,然后可以被mapped到进程虚拟地址空间,在/proc/pid/{maps, smaps}里面的vma区域中都可以找到它们。

gpu内存由于还实现了pagefault的内存映射管理,所以自己的内存大小还算到了进程pss统计里面。

但ion由于在mapped到用户态时,带了VM_PFNMAP flag,是raw pfn映射,所以虽然在用户态有虚拟地址空间和它对应,但是其大小并未算到进程pss统计里面。

并且ion内存还可以借助dmabuf被跨进程,跨设备共享使用。

系统整机内存统计

一 整机内存分布统计

网上有一篇的文章,详细讲了linux系统的整机内存分布特点和计算公式,但是这个是针对互联网服务器场景的, 针对嵌入式android场景的,这篇文章不太适合。

下面文件里面的公式算是比较贴近android场景的整机内存分布方面计算的:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

memInfo.getTotalSizeKb() = (totalPss - totalSwapPss) + memInfo.getFreeSizeKb() + memInfo.getCachedSizeKb() + kernelUsed + memInfo.getZramTotalSizeKb() + lostRAM.

memInfo.getTotalSizeKb()为proc/meminfo里面MemTotal部分,代表着手机总共能被使用的内存大小,它的具体组成如下:

1) totalPss - totalSwapPss

这个是用户态进程占用内存大小,这部分再加上下面的proc/meminfo的cached部分都是在上面讲的一 用户态进程管理的内存里面的。

进程占用在安卓里面用pss表示是比较合理的,这样能精确计算出每个进程,还有总共所有进程占用内存大小值。

totalpss是系统所有进程占用内存大小,因为已经swap出去的那部分要单独算到zram的内存统计占用统计里面,所以这里要减去totalSwapPss。

2) memInfo.getFreeSizeKb()

这部分是proc/meminfo下面的free部分,代表着系统的free内存大小。

3) memInfo.getCachedSizeKb()

这个是proc/meminfo下面的buffer + cached + kReclaimable - mapped部分,代表着系统的cached内存大小。

1> buffer是磁盘块设备的直接缓冲区,里面存放的是磁盘文件系统元数据缓存,比如inode节点,group里面bitmap等这些在内存里的缓存。

2> cached是pagecache,也可以说是file cache, 和上面文件系统元数据相比,这部分是文件系统文件自身数据在内存里面对应的缓存。

3> kReclaimable是slab_reclaimable + 驱动缓存,驱动缓存是诸如ion和gpu这类自己为了加速访问而提前占有的缓存。

4> mapped部分这个复杂了,原则上只包含已经mapped到用户态进程地址空间的文件大小,但是android下面还包含了其他部分,

    所以会造成上面getCachedSizeKb有时候为负值,下面会详细讲的。

4) kernelUsed

这部分是proc/meminfo下面的shmem + slab_unreclaimable + vmalloc_used + page_tables + kernel_stack + ion_heap。

ion_heap是ion驱动已经使用的内存大小,包括mapped + unmapped,具体通过/sys/kernel/ion/total_heaps_kb可以看到其总大小,和上面讲的ion缓存是不相关的。

这一部分除了ion_heap之外,再带上下面5) 都是在上面讲的二 内核管理的内存里面的,代表着内核管理的内存大小。

5) memInfo.getZramTotalSizeKb()

即为执行dumpsys meminfo后,下面字段里的xxx, 为zram实际占用的物理内存大小。

ZRAM: xxxK physical used

6) lostram为剩余内存大小

这部分由于android里面整机内存分布的复杂性,所以除非是一个比较大的正数或者负数,需要引起关注,否则没有意义,具体原因待会会讲。

二 相机场景下gpu内存统计

之所以把这部分单独列出来,是因为相机场景下gpu内存占用有时候也会冲到快1G左右,而目前上面的整机内存计算公式里面并未直接列出这部分计算,

为了方便排查gup内存是否存在泄漏,得单独分析下这个gpu内存占用统计。

1) gpu内存分类

相机场景下,gpu内存使用分为3大类:

EGL mtrack <=> /sys/class/kgsl/kgsl/proc/[pid]/imported_mem

GL mtrack   <=> /sys/class/kgsl/kgsl/proc/[pid]/gpumem_unmapped

Gfx dev        <=> /proc/pid/smaps 里面的  /dev/kgsl-3d0 项,应该是算到gpumem_mapped里面的。

egl就是Graphics mem使用,gl和gfx dev是其他方面的gpu内存使用。

2) gpu内存使用计算

其实进程的pss包含了进程的gpu内存使用,也可以说系统所有的gpu内存使用(不管是mapped还是unmapped)都算到了pss里面.

1> Gfx dev计算

Debug.getPss → android_os_Debug_getPssPid → proc_mem.SmapsOrRollup(里面包含并计算了Gfx dev的值)。

2> gl和egl mtrack(smaps unaccounted)内存计算

因为egl涉及到ion内存的使用,gl是unmapped内存,所以这两项的占用内存计算都在/proc/pid/smaps文件找不到的,所以

android里面借助android_os_Debug_getPssPid → read_memtrack_memory计算了egl和gl的内存使用。

详见read_memtrack_memory函数的注释:

/*  * Uses libmemtrack to retrieve graphics memory that the process is using.

 * Any graphics memory reported in /// is not included here.  */

所以通过android_os_Debug_getPssPid计算进程pss大小时,已经把上面3项gpu内存占用算到了pss里面。

3) 所以再次验证结论:

gpu的内存使用是包含在进程pss内存统计里面的。

另外android场景下gpu内存使用数据也可以通过dumpsys meminfo看到:

Total PSS by category:

127,432K: EGL mtrack

93,124K: GL mtrack

28,768K: Gfx dev

三 整机内存分布统计的复杂性

上面讲到android场景下,如果lost ram不是比较大或者比较小,那么分析它的组成部分是没有意义的,下面说说原因。

因为android的内存统计有一些重合的地方。

1) memInfo.getCachedSizeKb()为负值的问题

有时候会出现这个问题,系统的cached内存为负值,具体是因为:mapped内存明显大于buffer + cached + kReclaimable,

再进一步原因是mapped内存不再是cached内存的子集,里面不仅仅有已经映射到进程用户态地址空间的文件页大小,还包含了用户进程使用的gpu mem mapped大小。

这个gpu mem mapped内存在相机场景下会膨胀很多,导致mapped内存过大。

具体原因如下:

用bcc工具可以观察到:

1> gpu mem mapped内存并不是file cache.

cached内存是具体文件的file cache, 是NR_FILE_PAGES内存的一部分,而进程打开文件进行读写时,都会调用到add_to_page_cache_lru → __inc_node_page_state(page, NR_FILE_PAGES),

这个会算到文件cached内存里面。而gpu mem mapped部分并未算到NR_FILE_PAGES内存里面。

2 > gpu分配完物理内存,再做到用户态虚拟内存映射时,会调用kgsl_mmap函数,里面具体做:

kgsl_mmap --> vm_insert_page --> insert_page(不出错的话,会调它) --> page_add_file_rmap,这样gpu mem mapped内存会算到了proc/meminfo的mapped字段里面,但并未算到上面cached字段里面。

所以结论:

mapped内存过大,cached内存只是文件页(file cache),有区别于匿名页,在系统有内存压力时,和kReclaimable一样,都会变得比较小。而buffer只是文件系统元数据占内存统计,在相机场景下一般也会固定比较小。

这样就会出现有时候看到memInfo.getCachedSizeKb()为比较大的负数。

2) ashmem内存统计重复,还有部分统计不明确

1>统计重复

相机场景下,android的ashmem内存也会使用的比较多,但这部分内存在上面说的进程pss内存和kernel used的shmem内存里会有重复计算。

ashmem这部分内存是进程打开/dev/ashmem设备文件后,做完mmap到进程用户态地址空间映射后,在具体读写产生缺页异常时,才会实际分配物理内存,具体调用:

shmem_fault -> shmem_getpage_gfp -> shmem_add_to_page_cache -> __mod_node_page_state(page_pgdat(page), NR_SHMEM, nr)这样分配实际物理page后,该page大小会加到/proc/meminfo里面的shmem字段统计里面,同时也会加到进程pss内存的ashmem字段里面,当作进程pss内存的一部分。

android里面会精确划分各个区域内存的统计界限,比如系统cached部分必须得用cached - mapped,

那是因为cached里面包含了一部分映射到用户进程地址空间的文件页,但这部分文件页已经在进程pss里面计算过了,所以要减去这个mapped内存。

所以内核used内存和进程pss内存也是不相关,没有任何交织的内存占用统计,但这个地方,还是都会统计包含这个ashmem内存。

2>部分统计不明确

这个主要是针对之前发现的zram内存泄漏问题讲的,之前发现系统的totalSwapPss远小于zram中存储的实际数据大小,按理totalSwapPss里面东西就是代表着被交换到zram的数据,两个应该相等的。

这个问题当时分析主要是因为当时系统有ashmem fd句柄泄漏,系统中有很多已经unmapped,但是未close的ashmem内存。

ashmem内存是shmem内存的一部分,而shmem这块内核里面是基于tmpfs文件系统做的内存页,然而它们背后并不存在真正的硬盘文件,

所以它们是挂到内核anon lru list上,一旦内存不足的时候,它们是需要交换到zram中的。

所以这些unmapped,但是未close的ashmem内存并未算到进程pss里面,就不会包含在totalSwapPss里面了。但是这些内存会被交换到zram中,包含在zram存储的数据里面。

3) 最终结论 !!!

因为系统在计算整机内存分布时,会有些不正确和重合的地方,所以单纯关注和分析lost ram没有意义。

所以关注系统整机内存占用,只需要先关注最上面讲的系统的3大块内存,进程用户态管理管理内存,内核管理内存和驱动自身申请内存。

手机相机场景下出现内存不足问题,在排查系统占用内存时,只需要首先查看这3大块,看看哪块占内存多(这3大块都是独立的,不重复计算的),然后再具体细致看3大块内存里面的具体占用情况。

四 整机内存占用简单分析

往往有时候出问题时,不能第一时间就执行dumpsys meminfo,dump出系统的整机内存占用现场信息。

所以还有另外一个手法,利用系统的/proc/meminfo文件也可以进行整机内存占用分析。

1 MemAvailable代表系统的可用内存大小.

新版本的内核上该项不仅包含free+cached部分,还包含了KReclaimable的大小。

KReclaimable相机场景下有时候会很大,因为里面包含了很多相机暂时不用的ion缓存。

2 cached + AnonPages指的最上面的一 进程用户态管理内存。

Cached代表未映射和已经映射到进程用户态地址空间的文件页大小,Active(file) + Inactive(file)是它的子集。

AnonPages代表着进程用户态管理的匿名页内存大小,一般情况下,AnonPages约等于 Active(anon) + Inactive(anon)。

但是cached可以被算到系统可用内存里面,AnonPages不能,它只能被交换到zram中,如果有内存压力时。

3 SwapTotal - SwapFree为zram里面free存储空间大小。

4 Shmem和SUnreclaim能看出内核态是否有内存泄漏

一般有slab或者ashmem内存泄漏时,会在这两项表现出来。

5 ion内存占用可以通过最上面讲的sys目录下面的total_heaps_kb文件读出来。

转载地址:http://durvb.baihongyu.com/

你可能感兴趣的文章
如何使用Git上传和更新项目至Github
查看>>
选择排序(分析+代码调优)
查看>>
Docker
查看>>
代码优化建议,44条代码优化细节
查看>>
快速排序(图解分析+代码调优)
查看>>
Java基础面试总结
查看>>
HashMap遍历几种方式比较(传统的Map迭代方式对比JDK8的迭代方式)
查看>>
Java面试& HashMap实现原理分析
查看>>
PS修改动图字幕
查看>>
八大基础排序总结
查看>>
Linux下安装使用FastDFS
查看>>
后台管理系统之品牌管理
查看>>
后台管理系统之商品规格管理
查看>>
后台管理系统之商品管理
查看>>
商品详情及Thymeleaf静态化
查看>>
如何安装最纯净的Windows系统,玩转重装操作系统
查看>>
RabbitMQ安装使用及数据同步
查看>>
用户中心
查看>>
授权中心
查看>>
乐优商城—购物车
查看>>