下图是一张32位x86架构上运行的Linux中进程标准的内存段布局, 通过该图从上至下的简要分析Linux中进程的内存分布情况和各自的大致用途:
一、内核空间
对于32位X86架构上运行的Linux进程而言, 其虚拟地址空间的寻址范围从0 ~ 4G,内核将这块空间划分为两个部分,将最高的1G字节0xC0000000 ~ 0xFFFFFFFF称为“内核空间”, 顾名思义是提供给内核使用;而将较低3G字节0x00000000 ~ 0xBFFFFFFF称为“用户空间”,即提供给各个运行的进程使用。
用户进程无法直接访问内核的虚拟内存空间,仅能通过系统调用来进入内核态,从而来访问指定的内核空间地址。
另外,用户进程也是无法访问0x00000000 ~ 0x08048000
这一段虚拟内存地址的,在这段地址上有诸多例如C库,动态加载器如ld.so和VDSO等的映射地址。 如果用户进程访问到该区间会返回段错误。
二、用户空间
1、栈
在用户空间的最顶部的部分被叫做栈空间,它一般用于存放函数参数或局部变量。例如:调用一个函数会将函数参数压入到栈空间中,在函数返回时,参数会被栈弹出清理。进程中的每一个线程都有属于自己的栈。
2、mmap
在栈的低一段便是mmap,mmap是一种高效便捷的文件I/O方式,内核将文件内容映射在此段内存中,例如加载动态链接库。另外,在Linux中,如果你通过malloc函数申请一块大于MMAP_THRESHOLD(默认为128KB)大小的堆空间时, glibc会返回一块匿名的mmap内存块而非一块堆内存。
3、堆
在mmap段下面便是堆了,堆同栈一样,都是为进程运行提供动态的内存分配,但其和栈的的一个很大区别在于堆上内存的生命期和执行分配的函数的生命期不一致,堆上分配的内存只有在对应进程通过系统调用主动释放或进程结束后才会释放。
4、BSS段
堆段再往下便是BSS段这个静态内存区域,它是用来存储静态局部或静态全局变量的,其在编译期间便决定了虚拟内存的消耗,BSS段存放的是未初始化的变量。另外根据C语言标准规定,未初始化的静态成员变量的初始值必须为0,所以内核在加载二进制文件后执行程序前会将BSS段清0。
5、DATA段
BSS段再往下便是DATA段,它也是个静态内存区域,也是用来存储静态局部或静态全局变量。区别是DATA段存放的是已经初始化的变量,其映射自程序镜像中包含对应静态变量的文件。
6、代码段
DATA段再往下便是代码段(TEXT),这段中存有程序的指令代码。Text段是通过只读的方式加载到内存中的,他在多个进程中是可以被安全共享的。
三、堆和栈的区别
1、管理方式不同:
栈是由操作系统分配和管理的,无需人工控制,例如:函数的参数值、返回值、局部变量等。
而堆的申请、释放都是有程序员人工控制的,也因此容易产生内存泄漏。
2、空间大小不同
栈是向低地址扩展,是一块连续的内存区域。即栈顶的地址和栈的最大容量是系统预先规定好的,当申请的空间超过栈的剩余空间时,将出现栈溢出错误。
而堆是高地址扩展,是不连续的内存区域。因为系统是用链表来存储空闲内存地址的,且链表的遍历方向是由低地址向高地址扩展。
3、产生碎片不同
对于堆来说,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的内存碎片,程序的运行效率降低。而对于栈来说,分配的一定是连续的内存空间。
4、分配方式不同
堆都是程序中由malloc/new函数动态申请分配,由free/delete函数释放的;而栈的分配和释放是由操作系统完成的。栈的动态分配可有alloc()函数手动完成,但一般都无需手动操作,而是交给编译器自动进行申请和释放的。
5、分配效率不同
堆的内存分配效率比栈要低得多。因为栈是有操作系统提供的,会在底层堆栈提供支持,分配专门的寄存器存放栈的地址,包括压栈出栈也都有专门的指令执行,所以执行效率很高。而堆则是由C函数库提供支持,它的机制相对复杂,例如分配一块内存,库函数会按照一定的算法在堆内存空间中搜索可用的足够大的内存空间,如果没有足够大的连续空间,则需要操作系统来重新整理堆内存,这样才有机会分到足够大小的空间,然后才返回。