一 进程空间分布概述linux
程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。程序员
初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。算法
未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。编程
栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操做方式相似于数据结构中的栈。安全
堆 (Heap):存储动态内存分配,须要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式相似于链表。数据结构
Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每一个进程有各自的私有用户空间(0~3G),这个空间对系统中的其余进程是不可见的,最高的1GB字节虚拟内核空间则为全部进程以及内核所共享。
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不论是内核空间仍是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每一个虚拟空间中的最高1GB字节,但映射到物理内存却老是从最低地址(0x00000000),另外,使用虚拟地址能够很好的保护内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操做系统和CPU共同完成(操做系统为CPU设置好页表,CPU经过MMU单元进行地址转换)。多线程
上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。能够看出,Firefox使用了至关多的虚拟地址空间,由于它占用内存较多。app
进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。因为栈中数据严格的遵照FIFO的顺序,这个简单的设计意味着没必要使用复杂的数据结构来追踪栈中的内容,只须要一个简单的指针指向栈的顶端便可,所以压栈(pushing)和退栈(popping)过程很是迅速、准确。进程中的每个线程都有属于本身的栈。编程语言
经过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增加。若是栈的大小低于RLIMIT_STACK(一般为8MB),那么通常状况下栈会被加长,程序继续执行,感受不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,若是达到了最大栈空间的大小,就会栈溢出(stack overflow),程序收到一个段错误(segmentation fault)。函数
你能够经过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。好比,每一个内存映射文件在mmap段中都有属于本身的区域,动态库拥有相似BSS和数据段的额外区域。有时人们提到“数据段”,指的是所有的数据段+BSS+堆。
你还能够经过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后须要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,并且做为默认方式已经有些年头了,它假设咱们有值RLIMT_STACK。可是,当没有该值得限制时,Linux退回到“经典布局”,以下图所示:
以前一直在分析栈,栈这个东西的做用也介绍得差很少了,可是栈在哪儿尚未搞清楚,以及堆、代码、全局变量它们在哪儿,这都牵涉到进程的内存分布。
内存分布随着操做系统的更新换代,愈来愈科学合理,也愈来愈复杂,因此咱们仍是先了解一下早期操做系统的典型 linux 0.01 的进程的内存分布:
linux 0.01 的一个进程固定拥有64MB的线性内存空间(ACM竞赛中单个程序的最大内存占用限制为64MB,这确定有猫腻O(∩_∩)O~),各个进程挨个放置在一张页目录表中,一个页目录表可管理4G的线性空间,所以 linux0.01 最多有 64个进程。每一个进程的内存分布以下:
.text .rodata .data .bss 是常驻内存的,也就是说进程从开始运行到进程僵死它们一直蹲在那里,因此访问它们用的是常量地址;而栈是不断的加帧(函数调用)、减帧(函数返回)的,帧内的局部变量只能用相对于当前 esp(指向栈顶)或 ebp(指向当前帧)的相对地址来访问。
栈被放置在高地址也是有缘由的: 调用函数(加帧)是减 esp 的,函数返回(减帧)是加 esp 的,调用在前,因此栈是向低地址扩展的,放在高地址再合适不过了。
认识了 linux 0.01 的内存分布后,再看看现代操做系统的内存分布发生了什么变化:
首先,linux 0.01 进程的64MB内存限制太过期了,如今的程序都有潜力使用到 2GB、3GB 的内存空间(每一个进程一张页目录表),固然,机器有硬伤的话也没办法,个人电脑就只有 2GB 的内存,想用 3GB 的内存是没期望了。但也不是有4GB内存就能够用4GB(32位),由于操做系统还要占个坑呢!现代 linux 中 0xC0000000 以上的 1GB 空间是操做系统专用的,而 linux 0.01 中第1个 64MB 是操做系统的坑,因此别的进程彻底占有它们的 64MB,也不用跟操做系统客气。
其次,linux 0.01只有进程没有线程,可是现代 linux 有多线程了(linux 的线程实际上是个轻量级的进程),一个进程的多个线程之间共享全局变量、堆、打开的文件…… 但栈是不能共享的:栈中各层函数帧表明着一条执行线索,一个线程是一条执行线索,因此每一个线程独占一个栈,而这些栈又都必须在所属进程的内存空间中。
根据以上两点,进程的内存分布就变成了下面这个样子:
再者,若是把动态装载的动态连接库也考虑进去的话,上面的分布图将会更加"破碎"。
若是咱们的程序没有采用多线程的话,通常能够简单地认为它的内存分布模型是 linux 0.01 的那种。