malloc申请内存一定会引起进程的Rss变化吗?
前言
linux的开发人员,大概都知道可以通过 cat /proc/pid/smaps 来查看进程的memory占用情况,以确定线程占用memory是否过大:
可以看到每个大的字段都有size、Rss、Pss等不同的小的字段,它们可以各自理解为:
size:该字段所占用的虚拟内存大小
Rss/Pss:该字段所占用的物理内存大小;两者的区别在于,举例来说,libc-2.17.so被进程A和进程B都加载了,但实际上,这种共享的so所占用的物理内存是一份,而不是占用2份,那么假设libc-2.17.so的size为10KB,并且有且只有进程A和B加载这个so,那么进程A和B中的 libc-2.17.so 字段的Rss就是10KB,而Pss就是5KB,就是Rss是单独计算每一份所占用的内存,哪怕这一份是共享的;而Pss会更合理一些,会除以加载这一份的进程的数目;还有一个Uss,就不说了。
问题
当前我的工作就是把用户层面的虚拟内存和物理内存给对应起来,在验证已完成的方案时,发现一个问题,或者说不止一个问题。
测试1:
while(1)
{
printf("xxxx");
sleep(1);
}
测试结果:该线程的smaps信息里没有heap段
是否符合预期:是
测试2:
num = 0;
while(1)
{
if(0 == num)
{
void* buff = (void*)malloc(1024*1024*10);
*((int*)buff) = 1;
}
else
{
printf("XXXXX");
}
sleep(1);
num++;
}
测试结果:该线程的smaps信息里没有heap段
是否符合预期:不符合预期,应该有heap字段
测试3:
num = 0;
while(1)
{
void* buff = (void*)malloc(1024);
if(0 == num)
{
*((int*)buff) = 1;
}
else
{
printf("XXXXX");
}
sleep(1);
num++;
}
测试结果:
该线程的smaps信息里有heap段,但是heap里的RSS的值不断增大;并且通过自己的测试代码可以把申请的每个buff(虚拟地址)都对应到物理页面上
是否符合预期:
不符合预期,heap里的RSS应该维持不变;应该没有物理页面对应
下面一个一个来说:
测试2中,malloc申请了内存,但smaps里为啥没有呢?
在这里,首先上一张图,比较经典,但很多人看到了可能也没思考其中的细节:
声明:上面的图不是原创,侵权的话请通知我来删除
注意上面里的2个不同的字段:Heap、Memory Mapping Segment
其中,Heap就是咱们平时所说的堆内存了,也是我之前所理解的malloc 所对应的——后面的理解是错的
在这里感谢微信认识的大神朱志远,感谢他的指导和帮助!
直接说结果吧,定位过程不复杂,通过strace跟踪一下进程的系统调用就可以看到,malloc对应的系统调用时mmap,不是brk!
而mmap申请的内存对应的是Memory Mapping Segment,brk申请的内存对应的才是Heap段;
第二个问题,测试3里malloc申请的内存,如果没有被改写,那么是不会分配物理内存的,也就是进程的RSS实际上是不会增加的——但实际上是一直不停的增加!
经过多轮不同的测试,也没有发现什么问题,后来突然之前看到的关于malloc源码剖析的文章,里面提到malloc申请buff(大小为size)时,实际上是申请了sizeof(header) + size 大小的buff,然后把前面的header偏移过去,返回给用户的是size大小所对应的;前面的header里存放了buffer的size和下一个空闲的buff等信息,也就是说,header所对应的buff是会被改写的,这就会引发缺页中断,而被分配物理页面,所以Rss的size会不断增加;
后面写了另外的测试代码来验证这个想法,测试结果符合预期;
结论
1.malloc下面可能会调用brk,也可能会调用mmap;其中,brk所对应的是heap,而mmap对应的是Memory Mapping Segment;这两者在进程的smaps信息里也不是不同的:heap对应的就是heap,但是Memory Mapping Segment 对应的是没有字段说明的;
2.malloc申请的内存会涉及到header信息的填充,可能会引起缺页中断(注意是可能,不是一定),导致smaps信息里统计到的Rss值的增加;