一:问题现象
内存不足导致OOM,kill掉qemu等进程,导致虚拟机异常。
初步排查发现slab内存占用量非常大,达到20G
二:初步定位
通过观察对应的slab信息,发现异常情况。
使用的allocated和total差别太大了。正常情况slub是按需分配的,用多少申请多少。
正常的结构,基本在同一数量级。
所以 kmalloc-512的SLUB结构有异常。
三:数据结构分析
struct kmem_cache ffff89804c807600
{
cpu_slab = 0x2dba0,
flags = 1073741824,
min_partial = 5,
size = 512,
object_size = 512,
random = 2979236655726901199,
remote_node_defrag_ratio = 1000,
random_seq = 0xffff89804c80c800,
useroffset = 0,
usersize = 512,
node = {0xffff89804c800ec0, 0xffff8a7c4d000ec0,
kmem_cache由两个kmem_cache_node组成,两个node里面包含分配的所有内存和对象数据。
struct kmem_cache_node0 {
[ffff89804c800ec0] spinlock_t list_lock;
[ffff89804c800ec8] unsigned long nr_partial; 总数:624398
[ffff89804c800ed0] struct list_head partial;
[ffff89804c800ee0] atomic_long_t nr_slabs;
[ffff89804c800ee8] atomic_long_t total_objects;
[ffff89804c800ef0] struct list_head full;
}
struct kmem_cache_node1 {
[ffff8a7c4d000ec0] spinlock_t list_lock;
[ffff8a7c4d000ec8] unsigned long nr_partial;总数:4798
[ffff8a7c4d000ed0] struct list_head partial;
[ffff8a7c4d000ee0] atomic_long_t nr_slabs;
[ffff8a7c4d000ee8] atomic_long_t total_objects;
[ffff8a7c4d000ef0] struct list_head full;
}
其中partial表示slab中部分使用,但未用完的内存,下一次slab申请将会使用这种内存。一个slab用完才会申请新的slab(其中会有算法维持平衡)。其中nr_partial值为624398,slabsize是32K,消耗总内存=(624398 + 4798)*32k =20134272k。 接近20G,其中node0在消耗绝大部分内存。
Node0的partial数为624398,数量异常,需要看看每个slab的使用情况。
比较神奇的事情,大部分的slab的使用都是1个object, 每个slab包含64个可用的object。
对inuse的数进行部分统计(全量太大,速度太慢),inuse=1的在18709里面占了17616,94%。
所以大部分的slab都只用了一个object,意思是32K中只用了512b。
下午脚本对slub的扫描symbol:大部分值为0, 是因为slab的使用率很低,很多都没用。
目前的结论是:kmalloc-512的slab使用率非常低,就重新申请了新的slab。
和kmem_256d 对比,没有发现异常的项。
inuse=1占很大比重,还需继续定位。
四:slub分析
1:从slub NODE的角度分析:
选取一个只使用一个object的slub进行分析。
crash> kmem ffffea00130ef000
CACHE OBJSIZE ALLOCATED TOTAL SLABS SSIZE NAME
ffff89804c807600 512 980236 40472480 634465 32k kmalloc-512
SLAB MEMORY NODE TOTAL ALLOCATED FREE
ffffea00130ef000 ffff8884c3bc0000 0 64 1 63
通过slab的计算方式反推每一个object的对应关系(因为有加密的算法,手动计算耗时较多)。
反推出了第一个第二个object,以及最后一个object。发现并没有可用信息。看起来slub的数据完全正常,不清楚为什么使用时没有继续使用下一个object,而是申请新的slub。
ffff8884c3bc0000:ffff8884c3bc7800 ffff8884c3bc0200:ffff8884c3bc7000 ffff8884c3bc0400:ffff8884c3bc3e00 ffff8884c3bc0600:ffff8884c3bc7200 第二个object ...........
ffff8884c3bc4c00:2914e337a2e44830 最后一个object
ffff8884c3bc6a00:ffff8884c3bc7e00 ffff8884c3bc6c00:0 第一个object .........
2:从slub_cache_cpu方面分析
对slub_cache_cpu的page进行排查,工作都是正常的。
3:SLUB进行分析
对后面的slub进行分析,发现SLUB的结构基本都一样,暂用的内存如下面所示,内容相似。
地址分析:
读内存进行分析,找到对应的结构体为 sched_entity
ffff888171b8fc00: d65b6f04d65ca030 0000000000000000 0.\..o[.........
ffff888171b8fc10: 0000000000100000 0000000000000000 ................
ffff888171b8fd50: 0000000000000000 ffff89804cf6a6c0 ...........L....
ffff888171b8fd60: ffff888171b8e800 0000000000000000 ...q............
ffff888171b8fd70: 0000000000000000 0000000000000000 ................
ffff888171b8fd80: 0006b35240e99c00 0000000000000000 ...@R...........
ffff888171b8fd90: 0000000000000000 0000026700000000 ............g...
另一个结构体为:cfs_rq
ffff888171b8e800: d64f6f04d65ce630 0000000000000000 0.\..oO.........
ffff888171b8e810: 0000000000000000 0000000000000000 ................
ffff888171b8e820: 0000000000000000 0000000000000000 ................
ffff888171b8e830: fffffffffff00000 0000000000000000 ................
ffff888171b8e930: ffff89804cf6a640 0000000000000000 @..L............
ffff888171b8e940: 0000000000000000 0000000000000000 ................
ffff888171b8e950: ffff897816a00500 0000000000000000 ....x...........
ffff888171b8e960: 0000000000000000 0000000000000000 ................
ffff888171b8e970: 0006b3523f10f337 0000000000000000 7..?R...........
ffff888171b8e980: 0000000000000000 ffff888171b8e988 ...........q....
ffff888171b8e990: ffff888171b8e988 0000000000000000 ...q............
所以近20G的slab内存被如上所示的内存占用。sched_entity的my_q指向了cfs_rq,所以它们在内存中生对出现的,不能被释放的原因是其中有slub为空又没释放的情况。
占用slub的内存如下:
全部为0,又没有使用。也没有释放
ffff8884c3bc6c00: 0000000000000000 0000000000000000 ................ ffff8884c3bc6c10: 0000000000000000 0000000000000000 ................ ffff8884c3bc6c20: 0000000000000000 0000000000000000 ................ ffff8884c3bc6c30: 0000000000000000 0000000000000000 ................ ................. ................. ................. ffff8884c3bc6dc0: 0000000000000000 0000000000000000 ................ ffff8884c3bc6dd0: 0000000000000000 0000000000000000 ................ ffff8884c3bc6de0: 0000000000000000 0000000000000000 ................ ffff8884c3bc6df0: 0000000000000000 0000000000000000
SLAB MEMORY NODE TOTAL ALLOCATED FREE ffffea00130ef000 ffff8884c3bc0000 0 64 1 63
对比正常的kmalloc-512,涉及sched_entity和cfs_rq的内容也是相似的。
目前的疑问是为什么会产生大量的sched_entity?
更有疑问的是空slub被谁申请没有释放,才pin住了内存。无法释放内存。
4:代码分析
Slub malloc-512中内存基本被sched_entity和cfs_rq占用,对应的代码如下:
两个结构体成对申请,其中CPU数量为256,一次调用使用内存为256*512*2=256KB,
申请时有置0的操作,所以内存没使用时全0,使用后如果没有被重新使用,会保留原有信息。
全零的slub可能是这个alloc_fair_sched_group在处理异常分配时有泄露的可能,或者是其他模块的slub泄露导致内存被占用,无法释放。
还在继续查看。
int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent)
{
struct sched_entity *se;
struct cfs_rq *cfs_rq;
int i;
tg->cfs_rq = kcalloc(nr_cpu_ids, sizeof(cfs_rq), GFP_KERNEL);
if (!tg->cfs_rq)
goto err;
tg->se = kcalloc(nr_cpu_ids, sizeof(se), GFP_KERNEL);
if (!tg->se)
goto err;
tg->shares = NICE_0_LOAD;
init_cfs_bandwidth(tg_cfs_bandwidth(tg));
for_each_possible_cpu(i) {
cfs_rq = kzalloc_node(sizeof(struct cfs_rq),
GFP_KERNEL, cpu_to_node(i));
if (!cfs_rq)
goto err;
se = kzalloc_node(sizeof(struct sched_entity),
GFP_KERNEL, cpu_to_node(i));
if (!se)
goto err_free_rq;
init_cfs_rq(cfs_rq);
init_tg_cfs_entry(tg, cfs_rq, se, i, parent->se[i]);
init_entity_runnable_average(se);
}
return 1;
err_free_rq:
kfree(cfs_rq);
err:
return 0;
五:终章
经历的一周的挣扎还是找到了原因。
1:调用的kmalloc-512最多的是?
调用最多的进程是real_run。
2:对大量的调用栈进行分析
虽然real_run进程只有一个,但是调用slub的堆栈非常多。对大量栈进行分析,发现了比较异常的memcg_alloc_page_obj_cgroups,
因为这个函数会调用kmalloc-512非常奇怪。
对函数memcg_alloc_page_obj_cgroups的源码进行分析。
int memcg_alloc_page_obj_cgroups(struct page *page, struct kmem_cache *s,gfp_t gfp)
{
unsigned int objects = objs_per_slab_page(s, page);
void *vec;
vec = kcalloc_node(objects, sizeof(struct obj_cgroup *), gfp,page_to_nid(page));
//这里sizeof(struct obj_cgroup *)是指针大小 8byte,objects=64 =512byte
//所以会指向kmalloc-512
if (cmpxchg(&page->obj_cgroups, NULL,
(struct obj_cgroup **) ((unsigned long)vec | 0x1UL)))
kfree(vec);
else
kmemleak_not_leak(vec);
return 0;
}
这里就矛盾点就很直观了,slub page中的obj_cgroup指向的内容也是在这一类的slub中。
灵机一动,之前的空的object,应该就是指向了obj_cgroup。所以去内存里查一下那些结构看看。
我们查看kmem_cache_node的最后一个slub:ffffea00f0cc5800 来查看。它的obj_cgroups是
0xffff88bc33167200就在它自己的object列表中。
由此可见:SLUB新分配时自己就占用了一个object,所以一直都无法释放。
由于intel的机型中kmalloc-512的slub是16KB,所以obj_cgroup的分配不会冲突。所以目前只会在AMD机器上发现。
3:解决办法
memcg_alloc_page_obj_cgroups中分配数组vec的slub(vec = kcalloc_node
),不能是它自己。这个bug应该一直都有,只是极少触发,满足触发条件,同时大量申请slub才会比较明显的发现。