C++性能优化(八)——内存分配机制
一、操作系统内存布局
1、32位系统经典内存布局
Linux Kernel 2.6.7前版本采用的默认内存布局形式如下:
(1)32操作系统中,loader将可执行文件的各个段次依次载入到从0x80048000(128M)位置开始的空间中。应用程序能够访问的最后地址是0xbfffffff(3G)的位置,3G以上的位置是给内核使用的,应用程序不能直接访问。
(2)内存布局从低地址到高地址依次为:txet段、data段、bss段、heap、mmap映射区、stack栈区。
(3)heap和mmap是相对增长的,heap只有1G的虚拟地址空间可供使用。
(4)stack空间不需要映射,用户可以直接访问栈空间,因此是利用堆栈溢出进行的基础。
起始1GB地址为内核空间,随后是向下增加的栈空间和由0x40000000向上增加的MMAP地址;堆空间从底部开始,去除ELF、数据段、代码段、常量段后的地址,并向上增长。缺点是容易遭受溢出,堆地址空间只有不到1GB。
2、32位系统默认内存布局
Linux Kernel 2.6.7版本后32位操作系统的默认内存布局方式如下:
在经典内存布局基础上增加了Random offset随机偏移,不容易遭受溢出***;堆地址向上增长,但MMAP向下增长,栈空间不是动态增长的,会受到限制;内存地址利用率较高。
栈自顶向下扩展,但栈有边界,因此栈大小有限制(ulimit -s查看)。堆自底向上扩展,mmap映射区自顶向下扩展,mmap和heap是相对扩展,直至消耗尽虚拟地址空间中的剩余区域。
3、64位系统内存布局
64位操作系统的寻址空间比较大,沿用32位操作系统的经典内存布局,增加随机MMAP地址,防止溢出***。
二、操作系统内存管理机制
1、操作系统内存管理简介
内存管理自底向上分为三个层次:
(1)操作系统内核的内存管理。
(2)glibc层使用系统调用维护的内存管理算法。
(3)应用程序从glibc动态分配内存后,根据应用程序本身的程序特性进行优化,比如使用引用计数std::shared_ptr,内存池方式等等。
应用程序可以直接使用系统调用从内核分配内存,根据程序特性自己维护内存,但会大大增加开发成本。
2、操作系统内存管理机制
Linux Kernel内存管理的基本思想是内存延迟分配,即只有在真正访问一个地址的时候才建立地址的物理映射。Linux Kernel在用户申请内存的时候,只分配一个虚拟地址,并没有分配实际物理地址,只有当用户使用内存时,Linux Kernel才会分配具体的物理地址给用户使用。
对于大内存,通常不同的内存分配方式都是直接MMAP;对于小数据,则通过向操作系统申请扩大堆顶,操作系统会把内存分页映射到进程堆空间,再由malloc管理内存堆块,减少系统调用;free内存时,不同内存分配方式有不同策略,不一定会将内存还给操作系统,因此如果访问释放的内存并不会立即Run Time Error,只有访问的地址没有对应的内存分页才会。
对于heap操作,操作系统提供brk系统调用,c运行库提供sbrk库函数;对于mmap映射区操作,操作系统提供了mmap和munmap系统调用。
3、heap操作系统调用接口
#include
int brk(void *addr);
void *sbrk(intptr_t increment);
当参数increment为0时,sbrk返回进程当前的brk值,increment为正数时扩