从前面的介绍已经看出,这两个函数所分配的内存都处于内核空间,即从3GB~4GB;但位置不同,kmalloc()分配的内存处于3GB~high_memory之间,而vmalloc()分配的内存在VMALLOC_START~4GB之间,也就是非连续内存区。一般情况下在驱动程序中都是调用kmalloc()来给数据结构分配内存,而vmalloc()用在为活动的交换区分配数据结构,为某些I/O驱动程序分配缓冲区,或为模块分配空间,例如在include/asm-i386/module.h中定义了如下语句: #define module_map(x) vmalloc(x) 其含义就是把模块映射到非连续的内存区
kernel space 使用的memory 通常包括 kernel stack, slub, page table, vmalloc, shmem 等
内核栈大小80M,每个进程占用8K的内核栈,占用8M的用户栈。
从进程的角度来讲, 通常情况下进程所使用的memory, 都会通过mmap 映射到进程空间后访问使用
native /proc/pid/maps memory maps
1. malloc
/proc/pid/maps看到多出很多[anon:libc_malloc] 打开android malloc debug机制复现问题
2.mmap
coredump adb shell procrank -u > procrank.txt
3.ashmem
4.thread泄漏
活线程泄漏:大量创建线程,而且这些线程都是活着的。这种情况要避免出现,需要优化。
僵尸线程泄漏:线程创建时没设定deteched状态,完成任务退出后没有其他线程回收(调用pthread_jion函数)
kernel
1.触发OOM会看到是kernel stack,slab泄漏
2.kmemleak
创建一个进程每10分钟(默认)扫描系统内存然后打印泄漏的内存的信息。
cat sys/kernel/debug/kmemleak
3.buddy内存泄漏
page owner会记录每个物理页的分配调用栈等信息。通过这些信息,可以排查buddy内存泄漏或碎片化问题。通过对调用栈进行合并排序,可以列出分配总量大的调用栈,然后剔除不是泄漏的信息,基本能看出泄漏点。
buddy system是kernel初始化完成后,最基础的分配器,如果出现leak,可以打开page owner跟踪每个物理页的使用情况。
4.slab 用途:inode_cache、dentry
cat /proc/slabinfo 查看所有kmem_cache的信息,
slab_unreclaimable:717776kB kernel_stack:1160080kB
slub占用了717M,超出正常水平,kernel_stack / 16KB = 72505个线程,也远远超出正常水平。
刚好这题有mrdump,查看nr_threads,值为1000,和上面算出来的值不一致。那么必然存在内存泄漏。
问题性质变成:内存泄漏,而且是kernel stack和slub内存泄漏。
检查下kernel stack内存哪里来的,是直接从buddy system要的。而kernel stack只有在进程退出才会释放。是否存在进程没有退出?不可能,因为nr_threads正常。
3. 猜测
经过代码分析,怀疑task_struct里的usage计数在进程退出后不为0导致泄漏。而task_struct内存来自slub,这就解释了为何slub也存在内存泄漏了。
如何排查task_struct泄漏呢?task_struct里有usage引用计数。通过get_task_struct/put_task_struct来控制。那么这题一定是有人get_task_struct后没有put_task_struct了。
4. 如何验证这个猜测?
因为这题有mrdump,如果有task_struct泄漏,那么一定有很多task_struct在里面,并且状态是僵尸。
task_struct都是通过task_struct_cachep来申请的,只要遍历所有的属于task_struct_cachep的内存,观察里面的内容,就能知道是否存在僵尸,有多少个僵尸,是否和上面推导的内存泄漏吻合。
总共有70000多个线程处于TASK_DEAD状态,基本上和上面的推导是吻合的。接下来要审核代码,查看哪里get_task_struct却没有put_task_struct
6. 审核代码
仔细审查最近提交的代码,终于找出有一处添加的代码去get_pid_task,这个函数会去get_task_struct,代码提交者没有注意用完后需要put_task_struct,从而导致了内存泄漏。
并且这处代码会被频繁执行到。
7. 修复代码验证
临时先回退代码,然后再次复现抓取mrdump,用上面的cmm脚本复验,发现已没有TASK_DEAD线程了,表示问题就是这一处引起。
根本原因
添加的新功能代码有问题,调用了get_pid_task,这个函数会去get_task_struct,但用完后没有put_task_struct。