MEMINFO_SHMEM + MEMINFO_SLAB_UNRECLAIMABLE + MEMINFO_PAGE_TABLES + MEMINFO_VM_ALLOC_USED + MEMINFO_KERNEL_STACK(Optional) + ionUnmapped/dmabufUnmapped(After Android S) + gpuPrivateUsage(After Android T)
读取:Used RAM's kernel from dumpsys.meminfo
cat /proc/meminfo =>Shmem
被各个进程共享的内存页的数量,Shmem统计的内容包括:shared memory
注:所有tmpfs类型的文件系统占用的空间都计入共享内存,devtmpfs是/dev文件系统的类型,/dev/下所有的文件占用的空间也属于共享内存。可以用ls和du命令查看。
如果文件在没有关闭的情况下被删除,空间仍然不会释放,shmem不会减小。
cat /proc/meminfo => SUnreclaim
Slab中不可回收的量,同process数量有关,可考虑减少process or task,如果无法定位,参考slab debug的方法进行拆解,分析slab占用内存细节以及slab leak。
如下方法可用:
CONFIG_SLUB_DEBUG_ON=y (eng版本默认开启)
CONFIG_MTK_MEMCFG=y (默认都有开启)
若需要查看4k, 8k大小的slab的backtrace,需要额外开启以下宏:
CONFIG_PAGER_OWNER=on
CONFIG_PAGER_OWNER_SLIM=on
参考后面的如何查询内核所有page的使用情况?
注意a : ago project需移除以下红字部分
/kernel-xx/init/Kconfig
config SLUB_DEBUG
default y
bool "Enable SLUB debugging support" if EXPERT
depends on SLUB && SYSFS
- depends on !MTK_ENABLE_AGO
注意b : user/userdebug 需移除以下红字部分
/kernel-xx/drivers/misc/mediatek/mem/mtk_memcfg.c
-#ifdef CONFIG_MTK_ENG_BUILD
/* memblock reserved */
entry = proc_create("memblock_reserved", 0644,
mtk_memcfg_dir,
&mtk_memcfg_memblock_reserved_operations);
if (!entry)
pr_info("create memblock_reserved proc entry failed\n");
pr_info("create memblock_reserved proc entry success!!!!!\n");
#ifdef CONFIG_SLUB_DEBUG
/* slabtrace - full slub object backtrace */
entry = proc_create("slabtrace",
0400, mtk_memcfg_dir,
&proc_slabtrace_operations);
if (!entry)
pr_info("create slabtrace proc entry failed\n");
#endif
-#endif /* end of CONFIG_MTK_ENG_BUILD */
2.2.1.2.1.cat /proc/slabinfo“ >> slabinfo1.txt
查看各种slab分配方式占用大小,如下图
2.2.1.2.2.cat /proc/mtk_memcfg/slabtrace" >> slabtrace1.txt
查看各个slab使用者backtrace,如下图
第一个数字表示allocate 次数,乘上slab obj size即为占用内存, 越大表示占用越多。
注意kmalloc-4096以上为page单位,backtrace信息需要通过page owner debug方式查看,参考下面的如何查询内核所有page的使用情况?
持续关注slabtrace 是否有明显增长的backtrace, 即可能为泄露点。
page owner用于跟踪每个页面的分配者,可用来调试内存泄漏或查找内存占用。
当分配发生时,有关分配的信息(如调用堆栈和页面顺序)存储在每个页面的特定存储中。
当需要了解所有页面的状态时,就可以获取并分析这些信息。
Page owner功能开启:
CONFIG_PAGE_OWNER=y
增加page_owner=on和stack_depot_disable=off两个配置,参考如下:
diff --git a/arch/arm64/boot/dts/mediatek/mt68XX.dts b/arch/arm64/boot/dts/mediatek/mt68XX.dts
index 498596497314..3d579c069a38 100644
--- a/arch/arm64/boot/dts/mediatek/mt68XX.dts
+++ b/arch/arm64/boot/dts/mediatek/mt68XX.dts
@@ -603,6 +603,7 @@ chosen: chosen {
loglevel=8 \
8250.nr_uarts=4 \
androidboot.hardware=mt6855 \
+ page_owner=on stack_depot_disable=off \
initcall_debug=1 transparent_hugepage=never \
vmalloc=400M swiotlb=noforce allow_file_spec_access \
firmware_class.path=/vendor/firmware pelt=8 \
查看cmdline 中是否包含 ”page_owner=on” 信息
adb shell "cat /proc/cmdline"
抓取page owner 数据
adb shell "cat sys/kernel/debug/page_owner" > page_owner_full.txt
解析 page_owner信息
通过 tool 快速解析出申请内存大的top进程pid,tool release需要提case给MTK去申请 page_owner_analysis_tool
// --- end 如何查询内核所有page的使用情况?
kmemleak可以追踪kmalloc(), vmalloc(), kmem_cache_alloc()等函数引起的内存泄漏,一般用于slab内存泄漏。
开启如下kernel宏控:
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=n
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=40000
然后在.config中确认下是否已开启。
修改内核配置后adb shell,看是否存在sys/kernel/debug/kmemleak这个节点,如果存在表明已经开启kmemleak。
第一次scan:
echo scan > sys/kernel/debug/kmemleak // 开始扫描
然后cat sys/kernel/debug/kmemleak // 会得到很多backtrace,但是这其中有些是误抓的(kmemleak存在误报情况)
然后echo clear > sys/kernel/debug/kmemleak // 清除log
第二次scan:echo scan > sys/kernel/debug/kmemleak //开始扫描
过段时间等待leak的积累,然后 cat sys/kernel/debug/kmemleak
很多第一次误报的backtrace没有了,会得到很多重复的backtrace
重复的backtrace会越来越多,不断增长,一般这里就是内存泄露的点
如下是一个测试的驱动,在驱动的init函数中通过kmalloc分配256字节内存而不释放,按照步骤2.1.3.3. 操作,可以捕获如下疑似的内存泄露,具体是否有内存泄漏还需要结合代码来确认。
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffffffc164ac0900 (size 256):
comm "insmod", pid 7382, jiffies 4312832123 (age 1532.396s)
hex dump (first 32 bytes):
34 12 00 00 c1 ff ff ff 18 00 00 00 00 00 00 00 4...............
ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00 ................
backtrace:
[<000000004c509bf2>] kmem_cache_alloc_trace+0x278/0x33c
[<0000000006a54715>] 0xffffffaad42b1054
[<000000000320f832>] do_one_initcall+0x124/0x2b0
[<000000006c1849e9>] do_init_module+0x5c/0x260
[<00000000c68acdc4>] load_module+0x3074/0x3854
[<00000000e88a046b>] __arm64_sys_finit_module+0xec/0x11c
[<000000009384af7d>] el0_svc_common+0xa0/0x170
[<000000001d388de9>] el0_svc_handler+0x68/0x84
[<0000000083267d56>] el0_svc+0x8/0x300
[<00000000da66104e>] 0xffffffffffffffff
cat /proc/meminfo =>VmallocUsed
vmalloc已经使用的内存量,使用cat /proc/vmallocinfo进一步拆分(EasyMemory Parse Tool解析表格中“Vmalloc”页面)。可参考上面如何查询内核所有page的使用情况?
cat /proc/meminfo =>PageTables
同process or task 数量有关,数据太大,则可考虑减少process or task。
cat /proc/meminfo => KernelStack
每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或异常(exception)进入内核态的时候才会用到,也就是说内核栈是给kernel code使用的。
Kernel stack(内核栈)是常驻内存的,既不包括在LRU lists里,也不包括在进程的RSS/PSS内存里,是属于kernel消耗的内存。
(Currently the CONFIG_VMAP_STACK of the project is enabled, so it does not need to be counted in the kernel space)
ION unmapped or DMABUF unmapped from dumpsys.meminfo.txt (After Android S)
参考后面的 DMABUF/EGL/ION 进一步拆分,找到用量较大的process owner进行针对优化。
GPU private from dumpsys.meminfo.txt (After Android T)
和显示屏size、density以及刷新率相关,在对齐这些设定的基础上参考后面的GPU/GL track进一步拆分。
使用EasyMemory Parse Tool中的showinfo.bat脚本进行抓取,后续使用EasyMemory Parse Tool进行解析,参考EasyMemory Parse Tool使用方法。重点查看Summary页面"Kernel used"部分中各个子项的差别。除了上面步骤2中所说优化的方法外,再针对SUnreclaim和VmallocUsed进一步拆分优化的方法进行说明。
对/proc/slabinfo的信息按照<num_objs>*<objsize>的大小进行排序(单位是byte,可以 <num_objs>*<objsize>/(1024*1024)换算成MB);
上面排序的slab,只需要比较unreclaim部分,这个需要在代码中确认,可以通过搜索kmem_cache_create("XXX"
比如inode_cache:
inode_cachep = kmem_cache_create("inode_cache",
sizeof(struct inode),
0,
(SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
SLAB_MEM_SPREAD|SLAB_ACCOUNT),
init_once);
带flag SLAB_RECLAIM_ACCOUNT的就是可回收的slab,不带的就是要排查的unreclaim slab。
kernel-5.10及之后开启slab backtrace的方法如下
前置条件:user/userdebug load,开启root+mount权限
a. 确认config是否有打开:
zcat proc/config.gz | grep SLUB
CONFIG_SLUB_DEBUG=y
c. 确认特定slab的alloc_traces,这部分需要对应的owner去评估是否可以缩减。
cat /sys/kernel/debug/slab/kmalloc-128/alloc_traces
此步中的kmalloc-128是根据之前拆分出来需要排查的差异项。
d. (If need)Enable /sys/kernel/debug/slab/ to show allocate backtrace of kmalloc-2k, kmalloc-4k, kmalloc-8k
Set higher_order_disable = false @ parse_slub_debug_flags in kernel-5.10/mm/slub.c
常见VmallocUsed差异项厘清见下表
Item | 优化说明 |
---|---|
Load module | lsmod 命令查看载入module数量及大小 优化方向:考虑disable不需要的module or module优化 |
__cfi_check [cqhci] | CONFIG_CFI_CLANG=y @gki_defconfig,disable可优化9MB,关掉后没有call flow完整性check,会有security风险和漏洞 |
f2fs_build_segment_manager | ROM size不同会影响此项大小,建议配置对齐进行比较,如需进一步厘清需请storage owner确认 |
apummu_mem_alloc | 可以改为动态开关,不必开机使用,具体可咨询AI owner |
dup_task_struct / _do_fork + fork_idle | kernel thread数量 |
pcpu_balance_workfn mmprofile_init_buffer | kernel升版导致,具体差异量可提交eService咨询 |
disksize_store | 跟proc/meminfo的SwapTotal正相关,会跟DRAM大小(12GB or 16GB) & ZRAM Swap比例(55% or 75%)相关,建议配置对齐进行比较 |
如不在上述范围内,需要进一步debug,需要打开page_owner进行细项拆分:
diff --git a/arch/arm64/boot/dts/mediatek/mt6855.dts b/arch/arm64/boot/dts/mediatek/mt6855.dts
index 498596497314..3d579c069a38 100644
--- a/arch/arm64/boot/dts/mediatek/mt6855.dts
+++ b/arch/arm64/boot/dts/mediatek/mt6855.dts
@@ -603,6 +603,7 @@ chosen: chosen {
loglevel=8 \
8250.nr_uarts=4 \
androidboot.hardware=mt6855 \
+ page_owner=on stack_depot_disable=off \
initcall_debug=1 transparent_hugepage=never \
vmalloc=400M swiotlb=noforce allow_file_spec_access \
firmware_class.path=/vendor/firmware pelt=8 \
adb shell "cat /proc/cmdline" 中是否有包含 page_owner 信息。
adb shell "cat /sys/kernel/debug/page_owner" > page_owner
Kernel-5.10之前EGL统计的是ION内存部分,之后统计的是DMABUF部分。
MTK提供的统计节点在对EGL统计时,统计了进程使用DMABUF的PSS或RSS。
如果仅统计到PSS会无法统计到进程占用的所有DMABUF;而统计到的RSS则会统计了unmapped 和 mapped 的DMABUF用量。由于在不同的机型上使用了PSS或RSS统计方式,会导致出现EGL用量统计为0或过大的情况。
后续优化方向,采取使用fd_cnt均摊方式进行统计,让DMABUF用量平均分摊在相关process身上,预计kernel-6.x会有改进版。
kernel-5.10后版本EGL采用RSS的统计方式
因dmabuf rss_pid的PSS栏位,会去判断gralloc dmabuf的mmap or unmmap状态。
如link 中的介绍:
由于目前Mali gralloc policy和IMG gralloc policy都改成需要使用dmabuf才去mmap,会导致EGL mtrack读取PSS栏位(depends on mmap gralloc)沒有值。
修正方式为EGL mtrack读取RSS栏位,就不会受到gralloc mmap or unmmap影响。
影响:
单个process的 EGL memory 统计会偏大, 同样total EGL 也会偏大。
但是EGL mtrack过大,不会影响整体used RAM的计算 , 因为在计算used RAM 时会把EGL mtrack减掉:
Total EGL mtrack = sum (process EGL mtrack)
不建议直接改回 PSS 的计算方式,否则在检查单个process EGL mtrack去检查leak测试时,会有检测不到的问题。
但因采用RSS的方式,导致total EGL mtrack 偏大,导致失真严重。所以可以用DMA - BUF的值来做简单的参考,但是因为该值有包含kernel driver分配的dmabuf,所以也会比实际的EGL PSS 计算的要偏大一点。
采用PSS计算和 RSS 计算的两种修改方法:
获取DMABUF的指令
adb root
adb shell cat /proc/memtrack > proc.memtrack.EGL.txt
adb shell "dmabuf_dump" > dmabuf_dump.txt
adb shell cat /proc/dma_heap/all_heaps > dmaheap_all_heaps.txt
此部分指令释义可以看后面ION对应指令说明。
使用EasyMemory Parse Tool中的showinfo.bat脚本进行抓取,后续使用EasyMemory Parse Tool进行比对解析,参考EasyMemory Parse Tool使用方法。
主要查看“DMABUF summary”和“DMABUF”页面的数据进行分析。
“DMABUF summary”页面用于check 各process的总量:
DMABUF”页面可以查看每个process中DMABUF用量的具体细节:
可根据表格中的差异,找到需要优化的进程及差异点,再找对应进程owner进一步确认优化。
为了避免内存碎片化或者为一些有着特殊内存需求的硬件,比如GPUs、display controller以及camera等,在系统启动的时候,会为他们预留一些memory pools,这些memory pools就由ION来管理。 通过ION就可以在硬件以及user space之间实现zero-copy的内存share.
adb shell dumpsys meminfo
该指令可以获取整个系统的memory使用状态,其中EGL部分代表的ION使用量。可以通过查看该部分的使用状态来确认ION是否使用异常。
如果EGL使用量较多,则可以进入第5.2.2步继续确认。
adb shell cat /d/ion/ion_mm_heap > ion_mm.log
或者查看aee 里面的sys_ion_mm_heap。
ion_mm_heap有几个段,每个段有不同的用途,下面一一讲解格式:
client( dbg_name) pid size address ---------------------------------------------------- ndroid.settings( gralloc) 1638 4816896 0xffffffc02c813c00 ndroid.systemui( gralloc) 1448 7016448 0xffffffc02e2e3000 droid.launcher3( gralloc) 1969 29884416 0xffffffc0330dda00 system_server( gralloc) 1186 4816896 0xffffffc035fcb500
从这部分可以了解ION的各个client的使用状况。从中可以找出size异常多的module owner来分析,client之间能share同一块buffer;如果要了解buffer的详细状况,可以通过如下步骤继续分析:
mtk_ion_test, 是最近访问这个buffer的user
2598是user pid, 8392704是buffer size
最后的total orphaned是总共leakage size
orphaned allocations (info is from last known client): mtk_ion_test 3009 1048576 0 1 ---------------------------------------------------- total orphaned 1048576
total 70365184
了解ION total值
请先确认后面的5.2.2.7中是否有orphaned的buffer。如果没有, 且该部分的total比较大, 可以从前面的5.2.2.1中找size比较大, 再从后面的5.2.2.8 的区间看它的client中去看哪个buffer alloc/share 的时间比较久。
下面的cam-va2mva是camera heap和va map mva这两个heap的size
cam-va2mva total 0 0 deferred free 0
---------------------------------------------------- 0 order 4 highmem pages in pool = 0 total, dev, 0xffffffc03c7c2f00, heap id: 10 469 order 4 lowmem pages in pool = 30736384 total
buffer size kmap ref hdl mod mva sec flag pid comm(client) v1 v2 v3 v4 dbg_name
0x187462bb 139264 0 4 3 0 1 25165824( 0) 0( 0) 0x0, 0x0, 478( 478) allocator@2.0-s 0x500 0x4 0xa5ffba1c 0x2bc StatusBar#0
0x46bb5f4f 278528 0 4 3 0 1 33554432( 0) 0( 0) 0x0, 0x0, 478( 478) allocator@2.0-s 0x500 0x8 0xa5ffbe14 0x284 NavigationBar0#0
0x8798e15c 16384 0 2 1 -1 0 0( 0) 0( 0) 0x0, 0x0, 478( 478) allocator@2.0-s 0x0 0x0 0x0 0x0 nothing
从该部分中了解buffer的size, alloc buffer的pid ("alloc_pid"字段), 最近访问的pid和进程名("pid"和"comm(client) "字段)。
最后一列一般指向是谁使用了该buffer。
若为nothing表示无debug信息,需要添加debug信息参考如下:
在create ION调用接口上都在第一个参数传入buffer所属的owner name,如"fpipe.async"(具体由实际调用者传入其name),
mBufferPool = ImageBufferPool::create("fpipe.async", mInputInfo.mSize, mInputInfo.mFormat, ImageBufferPool::USAGE_HW);
ion set debug name的方式参考如下. 其中dbg name是字符数组,最长有效长度为 48 - 1
// #define ION_MM_DBG_NAME_LEN 48
value1 – value 4都是unsigned int,这四个值也可以作为详细区分参考。
-----orphaned buffer list:------------------ buffer alloc_pid alloc_client pid tgid process fd 0xffffffc022b29e00 3007 ion_test 3007 3007 mtk_ion_test 4
从该部分可以了解是否有orphaned buffer, 这个buffer是谁alloc的, leakage的pid是多少, buffer的fd是多少, 然后从上面5.2.2.6的pid(alloc_pid) comm(client) 这几个字段可以了解到最近access这个buffer的user的pid和process name; 可以提供这个信息给上面5.2.2.6中fd leakage的process进行分析。
泄漏判断
基本上一块ion内存都有对应的fd,因此ion泄漏伴随着fd泄漏,可以通过查看:
cat /proc/$pid/file_state
db里的PROCESS_FILE_STATE
搜索anon_inode:dmabuf关键字,应该可以看到非常多,比如:
Search "anon_inode:dmabuf" (943 hits in 1 file)
D:\PROCESS_FILE_STATE (943 hits)
Line 5: lrwx------ 1 cameraserver audio 64 2016-01-01 00:48 100 -> anon_inode:dmabuf
Line 6: lrwx------ 1 cameraserver audio 64 2016-01-01 02:32 1000 -> anon_inode:dmabuf
上面的例子可以看到anon_inode:dmabuf fd有943个,存在泄漏。
另外看/proc/$pid/maps或db里的PROCESS_MAPS,搜索anon_inode:dmabuf,一样可以搜索到很多:
Search "anon_inode:dmabuf" (880 hits in 1 file)
D:\PROCESS_MAPS (880 hits)
Line 22: c6c43000-c7102000 r--s 00000000 00:0a 8524 anon_inode:dmabuf
Line 23: c7102000-c75c1000 r--s 00000000 00:0a 8524 anon_inode:dmabuf
client(0xffffffc02c813c00) ndroid.settings(gralloc) pid(1638) ================> handle=0xffffffc031469e80,buffer=0xffffffc02f310900, heap=10,fd= 53, ts: 16120ms
从该部分看到各个client buffer的详细状况, seach client下各个buffer的address(如上面示例中的0xffffffc02f310900), 就可以看到这个buffer被哪些client在share同一块使用,另外还可以在上面5.2.2.6中确认该buffer真正的owner是谁。
current time 38252 ms, total: 70365184!!
可以用来比较aee里面的时间,如果差别比较远,这个dump参考价值可能不大,需要看sys_ion_client_history里面client aee对应时间点的信息。
adb shell cat /d/ion/clients/clients_summary
作用:summary of all clients。
adb shell cat /d/ion/client_history > client_history
作用:This is a thread to record the allocation and free event of all kind of heap type, from kernel boot up;Ion_alloc() and ion_free() will kick the thread named “ion_history” to update client_history
在第5.2步中定位使用ION过大或异常的client之后,再在该文件中搜索client name,看其ion占用是否有变化。
ion share产生的file都调用ion_share_close关闭了,但是还有以下一些可能会导致buffer指向的share fd无法关闭,这部分必须要保证也关闭,buffer才可以被回收:
以上这几种情况都需要review代码来确认是否有相关流程,这部分流程对ion来讲是无感的,ion无法review到这种情况。
进一步分析dumpsys meminfo中的GL mtrack信息
注意:在进行GPU memory比对测试的时候需要先对齐以下几项
1. adb shell wm size
2. adb shell wm density (此数据需要在设定wm size后重启再查看)
3. adb shell "dumpsys SurfaceFlinger | grep refresh-rate"
查看整机gpu memory用量
adb shell cat /proc/mtk_mali/gpu_memory
查看用量较大的PID, 如下图所示,中间列的数据是gpu_memory,实际size 需* 4k, 右边列为pid。
如GPU部分存在差异需要进一步厘清。
因篇幅问题不能全部显示,请点此查看更多更全内容