赞
踩
在开发高性能软件领域,对于一名系统工程师而言,比较重要的要求是要懂内存优化。因为内存是系统宝贵的资源,软件性能跟内存是否充足是密切相关的。
所以尤其是这种嵌入式终端系统中,内存有限场景下,更要熟悉系统的整机内存分布状况。知道一旦出内存问题时,该怎么去辨识和分析系统哪块内存占用有异常。
并熟悉怎么去优化系统各个模块的内存占用,来为某些很耗内存资源的多媒体软件(比如相机)的性能优化工作服务。
所以下面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被跨进程,跨设备共享使用。
网上有一篇/PROC/MEMINFO之谜的文章,详细讲了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内存占用有时候也会冲到快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函数的注释:
/* 188 * Uses libmemtrack to retrieve graphics memory that the process is using.
189 * Any graphics memory reported in /proc/pid/smaps 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文件读出来。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。