来源:微信公众号:cpp软件架构狮
要想回答这个问题,就得刨根问底,内存到底是怎样分配的?
在内核态的角度来看,进程需要分配内存的方式有两种:brk和mmap这两个系统调用 。
- brk是数据段的最高地址指针_edata往高地址增长;
- mmap是建立了页到用户进程的虚拟空间映射,在进程的虚拟地址空间中,堆和栈之间的大空间中找一块空闲的地址 。
但在用户态申请动态内存,是不会直接调用这两个系统调用接口 。一般情况下是通过C库的malloc/free来申请和释放 。而C库的实现也正是基于这两个系统调用接口,进行上层的封装和管理 。
当内核发生缺页中断的时候,到底会做哪些事情呢?
首先进程会从用户态陷入到内核态运行,会执行以下步骤:
- 检查需要访问的虚拟地址是否合法;
- 查找,分配物理页;
- 填充物理页的内容;
- 建立映射关系(虚拟地址->物理地址);
如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt 。
注意:
用ps -o majflt,minflt -C program命令查看 。
majflt代表major fault,中文名叫主错误,minflt代表minor fault,中文名叫次错误 。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数 。
下面我们用实例来解释下内存申请的原理
情况一、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图:
文章插图
1、进程启动的时候,其(虚拟)内存空间的初始布局如第一幅图所示 。其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件 。_edata指针(glibc里面定义)指向数据段的最高地址 。
2、进程调用A=malloc(30K)以后,内存空间如第二幅图:malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配 。你可能会问:只要把_edata+30K就完成内存分配了?
事实是这样的,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页 。也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的 。
3、进程调用B=malloc(40K)以后,内存空间如第三幅图 。
情况二、malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图:
文章插图
4、进程调用C=malloc(200K)以后,内存空间第一幅图:默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存 。
这样子做主要是因为:brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放 。
当然,还有其它的好处,也有坏处,再具体下去,有兴趣的同学可以去看glibc里面malloc的代码了 。
5、进程调用D=malloc(100K)以后,内存空间如图5;
6、进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放 。
文章插图
7、进程调用free(B)以后,如第一幅图所示:B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?
当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了 。
推荐阅读
- 电脑为什么会死机?这几点原因需要了解一下
- 腾讯会员怎么取消自动续费
- 说话和对话有什么区别
- 箭毒木为什么叫见血封喉 箭毒木是一种剧毒植物是我们常说的哪种树呢
- 翻译|北大院长姚洋:十年内有3类职业或许会被淘汰,选专业要考虑未来
- 苏亚雷斯|对比一下职业生涯的轨迹,你会发现苏亚雷斯要比莱万精彩得多!
- 企业微信|当你离开办公室时,远程办公真的会影响工作效率吗
- 机械|职场上,为什么工作明明很努力,却得不到重用?因为你不懂这四点
- 杯茶看出你的人际关系
- 冬天开窗户湿度会增加还是降低 梅雨季节开窗会增加湿度吗