Linux进程内存布局(翻译)
Anatomy of a Program in Memory
在一个多任务OS中,每个进程都运行在它自己的内存沙箱中。这个沙箱就是虚拟地址空间,在32位下就是一块容量为4GB的内存地址。内核将这些虚拟地址按页表(page table)映射为物理内存,并交由CPU访问。每个进程有自己的页表集,但有一点要注意。虚拟地址一旦被启用,就会应用到机器上所有运行的程序上,也包括内核自己。因此虚拟地址空间必须为内核预留一部分(否则就没办法和内核交互了):

给内核预留那么多空间,并不是说内核真的使用了那么多物理内存,只是内核可以这部分虚拟地址自由的映射到某片物理内存上(而不用和其它虚拟地址遵循相同的规则)。内核空间在页表中被标记为专属的特权代码(ring 2或更低),用户态的程序访问它时就会产生页错误(page fault)。Linux的内核空间在所有进程中都映射到相同的物理内存上。内核代码和数据总是可寻址的(总是可以找到的),任意时刻都可处理中断和系统调用。作为对比,当进程切换时,用户态地址空间的映射就会发生改变:

蓝色区域表示映射到物理内存的虚拟地址空间,而白色区域则表示未映射的空间。在这个例子中,Firefox惊人的内存需求让它使用的虚拟地址远远超过了其自身的地址空间。内存地址空间是按堆、栈这样的内存段进行管理的。要记住内存段就是简单的一个内存地址范围,而且与Intel风格的段没有任何关系。下面是一个Linux进程的标准内存段布局:

如果计算过程轻松愉快、准确无误,那么上图显示的内存段起始虚拟地址在几乎每个进程中都是一样的。这导致了远程利用安全漏洞变得非常容易。一次漏洞探测通常需要引用内存的绝对地址:一个栈上地址,一个库函数的地址,等等。远程攻击者只能盲目的选择这样的地址,指望地址空间都是一样的。如果这种情况真发生了,用户就悲剧了。因此地址空间随机化变的很流行。Linux会给栈、mmap段、和堆的起始地址一个随机偏离。不幸的是,32位地址空间实在是太紧凑了,只给随机化很小的空间,妨碍了它的效果。
进程地址空间的最上面是栈,栈里保存了局部变量,以及大多数编程语言中的函数参数。一次方法或函数调用就会向栈增加一个栈帧(stack frame)。当函数返回时栈帧就会被销毁。因为数据遵循严格的“后入先出”顺序,这种简单的设计意味着不需要复杂的数据结构就能追踪到栈的上下文——栈顶的一个指针就搞定。栈的Push和pop操作因此变得快速且确定。同时,栈区域的稳定重用也有助于栈内存在CPU缓存中保持活跃,加快了访问速度。进程中的每个线程都有自己的栈。
如果推入栈的数据过多,可能会耗尽栈映射的地址区域。这会导致一次页错误,Linux将其处理为一次expand_stack()调用,实际上是调用acct_stack_growth()检查当前是否可以增加栈大小。如果栈大小小于RLIMIT_STACK(通常8MB)就可以继续增长,程序会正常继续,不会察觉到什么。这是栈大小调整的默认处理。但是,如果栈大小达到了上限,就会发生栈溢出,程序会接到一次段错误(Segmentation Fault)。相对的,当栈变小时,不会缩减栈大小。这就像联邦预算,只增不减(笑)。
在访问到未映射内存区(上图中的白色部分)时,只有动态栈增长可能是合法的。其它方式访问到未映射区域时都会引发一次页错误,进而导致段错误。一些映射区是只读的,对它们的写操作也会导致段错误。
在栈的下面就是mmap段,内核在这里将文件内容映射为内存。任何应用都可以通过Linux下的mmap()或Windows下的CreateFileMapping()/MapViewOfFile()申请一片mmap区域。mmap是一种高效便捷的文件I/O方式,被用于加载动态链接库。我们同样可以创建一块与文件没有关系的mmap区,用来存放程序的数据。在Linux中,如果你通过malloc()申请一大块内存,glibc会返回一块匿名的mmap内存块,而不是用堆内存。“大”意思是比MMAP_THRESHOLD大,通常是128KB,可以调用mallopt()修改。
接下来我们开始说堆。堆提供了运行时的内存分配,这点和栈类似;数据的生命期与执行分配的函数生命期不一致,这点与栈不同。大多数语言都提供了堆的管理。因此满足内存请求需要语言的运行环境和内核协作完成。C语言中分配堆的接口是malloc()族函数,而在有gc的语言(例如C#)其接口则是关键字new。
如果堆内存足够,语言运行时环境就可以处理内存请求,不需要内核介入,或者可以通过brk()系统调用去增大堆内存。堆的管理很复杂,在面对程序杂乱的分配方式时,需要使用在速度和内存使用率上努力做平衡的微妙算法。满足一次堆请求的时间差异可以非常大。实时系统需要特殊用途的分配器来处理这个问题。使用时堆也会变的很碎片化,见下图:

最后,我们看一下最下面的内存段:BSS、data、代码段。在C里面BSS和data段都是存储静态变量(全局)的数据。区别是BSS段存储没有初始化的静态变量,即在代码中没有初始值的静态变量。BSS区是匿名的:不映射自任何文件。如果代码中有static int cntActiveUsers;,那么cntActiveUsers就在BSS段。
而data段则存放代码中显示初始化了的静态变量。这块内存区不是匿名的,它映射自程序镜像中包含对应静态变量的文件。这种映射是私有映射,即内存中的变化不会反映到文件中。如果不这样,改变一个全局变量的值就会修改你磁盘中的镜像文件,这就太荒谬了!
下图中的data示例有些取巧,使用了一个指针。指针gonzo的内容——4字节的地址——就存在data段。而它指向的字符串则不是,字符串存放在text段。text段是只读的,存放字符串字面值等不会执行的代码。text段也会映射程序文件,但对它的写操作会引发段错误。这会避免一些指针bug,但不像第一时间在C代码中避免指针bug那么有效。下图显示了这些段和示例变量:

你可以读/proc/pid_of_process/maps来检查一个Linux进程的内存区。一个内存段可能包含多个内存区。例如,每个mmap映射的文件都会在mmap段有一个单独的内存区,而动态库还会有在BSS和data段的内存区。下篇文章会剖析“内存区”意味着什么。有时人们也会用“data段”代指整个data + BSS + 堆。
你可以用nm和objdump命令检查镜像文件中的符号、它们的地址、所属的段,等等。最后,上面说的虚拟地址布局是Linux的“灵活”布局,也是近年来Linux的默认布局。它假设RLIMIT_STACK有值。否则Linux会回到下图的“经典”布局:

Linux进程内存布局(翻译)的更多相关文章
- linux进程内存布局
一个程序本质上都是由 BSS 段.data段.text段三个组成的.这样的概念在当前的计算机程序设计中是很重要的一个基本概念,而且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统运行时的内存大小分 ...
- Linux进程地址空间 && 进程内存布局[转]
一 进程空间分布概述 对于一个进程,其空间分布如下图所示: 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码. 初始化过的数据(Data):在程序运行初已经对变量进行初始 ...
- Linux进程内存用量分析之堆内存篇
https://mp.weixin.qq.com/s/a6mLMDinYQGUSaOsGYCEaA 独家|Linux进程内存用量分析之堆内存篇 姬晨烜 58技术 2019-12-06 导语 本文将介绍 ...
- Linux C进程内存布局
当程序文件运行为进程时,进程在内存中获得空间.这个空间是进程自己的内存空间.每个进程空间按照如下方式分为不同区域: 进程内存空间布局图 text:代码段.存放的是程序的全部代码(指令),来源于二进制可 ...
- linux 进程内存解析【转】
转自:http://blog.csdn.net/lile269/article/details/6460807 之前我所了解的linux下进程的地址空间的布局的知识,是从APUE第2版的P430得来的 ...
- 查看LINUX进程内存占用情况
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...
- 查看LINUX进程内存占用情况(转)
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...
- Linux进程内存分析和内存泄漏定位
在Linux产品开发过程中,通常需要注意系统内存使用量,和评估单一进程的内存使用情况,便于我们选取合适的机器配置,来部署我们的产品. Linux本身提供了一些工具方便我们达成这些需求,查看进程实时资源 ...
- 转 linux进程内存到底怎么看 剖析top命令显示的VIRT RES SHR值
引 言: top命令作为Linux下最常用的性能分析工具之一,可以监控.收集进程的CPU.IO.内存使用情况.比如我们可以通过top命令获得一个进程使用了多少虚拟内存(VIRT).物理内存(RES). ...
随机推荐
- [py][mx]django模板继承-课程列表页
课程列表页分析 1,机构类型 2,所在地区 3.排序 学习人数 先分析下 纵观页面,页头页脚都一样. django提供了模板继承. 至少 不同页面的title 面包屑路径 content内容不一致,以 ...
- Py-lamda表达式学习【转载】
转自:https://blog.csdn.net/zjuxsl/article/details/79437563 1.语法定义 在Python中,lambda的语法是唯一的.其形式如下: lambda ...
- PAT 1068 Find More Coins[dp][难]
1068 Find More Coins (30)(30 分) Eva loves to collect coins from all over the universe, including som ...
- Hadoop集群安装-CDH5(5台服务器集群)
CDH5包下载:http://archive.cloudera.com/cdh5/ 架构设计: 主机规划: IP Host 部署模块 进程 192.168.254.151 Hadoop-NN-01 N ...
- [lr] 常用快捷键
界面基本操作 F5 : 隐藏/显示上部面板 F6 : 隐藏/显示下部面板 F7 : 隐藏/显示左部面板 F8 ...
- animation-fill-mode
animation-fill-mode: none:默认值.不设置对象动画之外的状态 forwards:结束后保持动画结束时的状态,但当animation-direction为0,则动画不执行,持续保 ...
- Ignite初探
Guava是一个很方便的本地缓存工具,但是在多节点处理的过程中,本地缓存无法满足数据一致性的问题.分布式缓存Ignite很好的解决了数据一致性,可靠性,事务性等方面的问题. Ignite支持分区方式和 ...
- VMWare中桥接、NAT、Host-only
1.概述 2.bridged(桥接模式) 3.NAT(网络地址转换模式) 4.host-only(主机模式) 5.replicate physical network connection state ...
- 安全测试===sqlmap(壹)转载
六.优化 这些参数可以优化Sqlmap的性能. 1.一键优化 参数:-o 添加此参数相当于同时添加下列三个优化参数: --keep-alive --null-connection --threads= ...
- Wireshark图解教程(简介、抓包、过滤器)(转)
本文转自:http://www.cnblogs.com/observer/archive/2011/11/04/2235219.html 下面是一张地址为192.168.1.2的计算机正在访问&quo ...