对一个程序在内存中的分析【转】

转自:http://blog.csdn.net/bullbat/article/details/7304404

bullbat  译   

  

       内存管理是操作系统的核心;它对于程序员和系统管理员都很关键。在接下来的几篇文章里面我将对内存的关键技术做谈论,但是不会远离其本质。然而概念很普通,例子多半来自32位X86系统的LINUX和Window操作系统。这第一篇文章谈论程序在内存中如何存放。

       在多任务操作系统中的每一个进程运行在他自己的内存地址空间中。这个地址空间就是虚拟地址空间,虚拟地址空间在32位模式下总是4GB大小的内存地址。这些虚拟地址用页表方式映射物理内存,页表由操作系统内核维护,由处理器访问。每个进程有自己的页表集合,但这里有个难以理解的地方。一旦虚拟地址(作者的意思也就是分页机制)开启,它应用与所有正在运行与机器上的软件,包括内核自身。这样一部分虚拟地址空间必须保留用于内核:

       这并不意味着内核使用那么多物理内存,只是他运用那部分可用的地址空间去映射他实际希望的物理内存大小。内核空间在页表中标志为特权级代码(等级小于等于2),这样当用户模式程序访问他时就会触发一个访页错误。在LINUX中,内核空间一直处于当前状态并且在所有进程中映射到相同的物理内存。内核代码和数据在任何时候总是为中断服务和系统调用做好寻址的准备。相反,用于映射用户模式的地址空间部分将会在进程切换的时候发生改变。

       蓝色区域代表已经映射物理内存的虚拟地址,白色区域为没映射部分。在上面的例子中,Firefox由于他的巨大的内存需求,已经使用了他的大部分虚拟地址空间。地址空间中不同的带对应内存段如堆、栈等等。需要注意的是这些段就是简单的内存地址范围,他和Intel汇编中的”段”不相干。下面是在LINUX进程中标准的段视图:

       当计算安全时,如上面的段所示,对于机器中的几乎每一个进程的开始虚拟地址都相同。这使得很容易远程利用安全漏洞。一个漏洞,往往需要引用绝对内存位置:栈上的地址,库函数的地址,等等。远程攻击必须盲目地选择这个内存位置,正指望这所有的地址空间都是一样的。如果真是这样,那么太容易被攻击了。故而地址空间的随机化就变得通用了。LINUX以在栈、内存映射段和堆的起始地址加上偏移的方式随机化他们。不幸的是,32位地址空间很紧缺,留下很少的空间用来做随机化从而牵制了他的有效性。

       进程地址空间中最上面的段为栈,很多语言中栈用于存储本地变量和函数参数。调用一个方法或函数时压入栈一个新的栈帧。当函数返回时,这个压入的栈帧被释放。这个简单的设个,可能是因为数据遵循严格的FIFO次序,这意味着再复杂的数据结构都无需跟踪栈内容——一个简单的栈顶指针将会做跟踪作用。这样入栈和出栈非常快速和准确。进一步,堆栈地区不断重用,往往在CPU缓存中持有活跃的栈内存,加快存取。进程中的每个线程获得他自己的栈。

        当压入操作他负载的数据时,耗尽栈的映射区域是有可能的。这将会触发一个缺页中断,在LINUX中由expand_stack()函数接手,该函数调用acct_stack_growth()来检查是否应当扩展栈。如果栈的大小在RLIMIT_STACK(通常是8MB),那么通常会扩展栈并且程序不会发觉刚才发生的这一切。这是正常的栈大小调节的机制。然而,如果栈的最大值已经达到,栈将会溢出并且程序会接手到一个段错误。当有扩展需求时,栈会扩大,而栈变小时他不会回缩。就像federal的budget,他只是扩展。

       只有一种情况会发生动态栈扩展,那就是程序进入一个没有映射的内存区域,比如上面的白色区域,可能是无效的。任何其他进入没有映射内存区间触发与一个缺页中断会导致一个段错误。有些映射区域的只读的,所以,向这些区域写数据同样会导致段错误。

       在栈下面是内存映射段。这里内核直接映射内存到文件内容。任何应用程序都可以通过mmap()系统调用(LINUX下)或CreateFileMapping()/MapViewOfFile()(windows下)获得这个映射。内存映射是文件I/O中一个方便高效的方法,所以他用于加载动态库。创建一个和任何文件不想干的匿名内存映射用于替代程序中的数据也是有可能的。在LINUX中,如果你通过malloc()申请一块很大的内存块,C标准库将创建一个匿名映射而不是用内存堆。’很大‘意味着大于MMAP_THRESHOLD个字节,默认是128KB,可以通过mallocpt()做调整。

       说到堆,我们接下来跳到地址空间中的下一个。堆提供运行时内存分配,就像栈,而和栈不一样的是数据必须持久于分配函数。大多数程序语言都为程序提供了堆管理器。满足内存需求是一个语言运行库和内核之间的联合事务。在C中,堆分配器的接口是malloc()系列函数,然而在垃圾回收语言如C#的堆分配接口是new关键字。

       如果堆里面有足够的空间满足内存需要,那么它可以完全由语言运行库处理而不需要内核环境。否则通过brk()系统调用扩展堆为请求块获得空间。堆管理器很复杂,在面对我们应用程序混乱的分配方式,需要复杂的算法来维持内存使用的速度和效率。为堆请求提供服务的时间可以有很大的差异。实时系统有特殊目地的分配器来处理这个问题。堆也变得有很多碎片,如下图所示:

        最后,我们到达了内存中最下面的段:BSS段、数据段和代码段。BSS段和数据段在C语言中存储静态和全局变量。不同的是BSS段存放的是没有被初始化的静态变量,也就是所这些静态变量在源代码中没有被程序员设置初值。BSS内存区是匿名的:他不映射任何特定的文件。例如,static int cntActiveUsers,变量cntActiveUsers的内容存放在BSS段。

       另一方面,数据段持有在源代码中已经初始化的静态变量。他的内存区域不是匿名的。他映射程序二进制镜像的一部分,这部分包含在源代码中已初始化的静态变量。例如,static int cntWorkerBees = 10, 那么cntWorkerBees存放在数据段中且初始数据为10。虽然数据段映射一个文件,但是他是一个私有内存映射,这意味着内存更新不会反映到底层文件。这一定是这样的,否则分配分配到的全局变量将改变你磁盘上的二进制镜像。后果难以想象!!

       图中数据的例子是棘手的因为他使用了一个指针。在这种情况下,gonzo指针的内容——一个4字节的内存地址——存放在数据段中,而他指向的实际数据却不是。指向的实际内容存放在代码段中,代码段是只读的并且存放你的所有代码以及字符串文字花絮。代码段也在内存中映射你的二进制文件,但是写这个区域会发生段错误。这有助于防止指针错误,虽然没有有效的避免C语言放在首要位置。这里有个图,显示这些段和我们例子里面的变量:

       在一个LINUX进程中你可以通过读/proc/pid_of_process/maps文件来检查内存区域。例如,每一个内存映射文件通常在映射段(mmap段)有他自己的区域,并且动态库有类似BSS段和数据段的额外区域。接下来的文章中会澄清什么“区域”(“area”)的真正含义。此外,有时人们说“数据段”意味着所有的数据+ bss +堆。

       你可以检查二进制镜像,运用nm和objdump命令来显示符号、他们的地址、段等等。最后,上面所述的虚拟地址布局在LINUX中是“灵活”的,这几年一直是默认的。他假定我们有一个RLIMIT_STACK的值。当并非这种情况时,LINUX恢复到如下的“经典”布局:

       这就是他的虚拟地址空间布局。接下来的文章中讨论的是内核如何保持跟踪这些内存区域。未来,我们将会看看内存映射、文件读写和这一切是怎么关联的以及内存使用计数意味这什么。

原文地址:http://duartes.org/gustavo/blog/

时间: 2024-09-17 22:52:31

对一个程序在内存中的分析【转】的相关文章

开机出现服务器正在运行中由于另一个程序正在运行中怎么办?

  为什么开机后就出现"服务器正在运行中由于另一个程序正在运行中,此操作无法完成.请选择"切换到"来激活正在运行中的程序,并更正问题",需要点击多次"切换到"才能使这个对话框消失,电脑也不能进入正常的运行,奇怪了,今天小编就自己的亲身经历来交大家如何解决. 1.crtl+alt+del调用windows任务管理器 2.然后,文件-新建任务运行 3.输入msconfig.msc 4.然后禁用里面的所有开机启动项 5.接着重启一下电脑,启动起来时候就

求教android中如何控制一个activity在内存中的实例个数

问题描述 求教android中如何控制一个activity在内存中的实例个数 项目中有个需求,一个activity中有个按钮可以打开自己的另一个实例,那么这样就会出现不停的点,不停的创建这个activity实例的情况出现,有没有方法可以控制这个activity实例的个数啊,比如只保留最近打开的三个这个activity实例,有没有方法可以实现的啊,求教大神们 解决方案 android 让自己的Activity只创建一个实例 解决方案二: 机器人的回答没有用啊,需要是保存3个实例,不是一个啊 解决方

java-ANDROID程序有没有办法读取另外一个程序的界面中的内容?

问题描述 ANDROID程序有没有办法读取另外一个程序的界面中的内容? 简要介绍一下相关技术即可 即有没有办法访问到另外一个包下的Textview与ImageView? 解决方案 不能直接读取,到可以间接实现,比如通过service实现进程间通信,两种方式AIDL与Messager

探讨:程序在内存中的分配(常量,局部变量,全局变量,程序代码)问题_C 语言

一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.- 程序结束释放4.另外还有一个专门放常量的地方.- 程序结束释放 在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上.在所有函数体外定义的是

剪切板是一个安全问题 - 在 Linux 中你可以用 xclip 和 cron 修复它

在你的操作系统上复制/粘贴的能力是必不可少的.无论你写的是代码还是剧本,这两个功能是在计算机上处理文本的核心.当你复制文本时,它会进入内存驻留的剪贴板.除非安装了可以容纳多个条目的剪贴板管理器,否则剪贴板默认情况下只会处理一个复制事件,当你复制其他东西的时候,它之前的条目才会消失.在标准 Linux 设置中,剪贴板内容存储在控制它的程序的内存中(通常是 Xorg). 剪贴板应该有所限制,因为任何程序都可以读取其内容,如果放任它,它保存的东西就会一直呆在那里.此外,现代浏览器允许恶意网站以多种方式

linux shell,将数据流重定向作为下一个程序的输入,由于有缓冲机制,数据流无法实时进行处理

问题描述 linux shell,将数据流重定向作为下一个程序的输入,由于有缓冲机制,数据流无法实时进行处理 上述问题可以简化为以下问题: python脚本如下: #coding=utf-8 import sys import os import time if __name__ == '__main__': while True: print time.strftime('%Y-%m-%d %H:%M:%S') time.sleep( 5 ) 然后通过linux命令行:python produ

如何从内存中抓取大智慧的全推送数据

问题描述 大智慧的所有最新行情数据是全推送的,即连接网络后就会把所有股票的最新数据加载到本机内存的,如果能获取到,相当于一个效率极高的行情接口,使用.NET可以做到,但目前还没思路. 解决方案 解决方案二:这个得大量用到WindowsAPI,通过程序注入,获取另一个程序的内存内容.最关键的是怎么找到哪段内容是你需要的数据,而且里面格式的问题,说不定还有加密.解决方案三:加密是必須的解决方案四:哪天楼主搞出来了,第一个购买!解决方案五:不知道用钩子能注入不不过我想没这么简单了解决方案六:大智慧是什

Windows Server中的 WINS 服务器远程内存损坏漏洞分析

本文讲的是Windows Server中的 WINS 服务器远程内存损坏漏洞分析, 漏洞概要 在2016年12月,FortiGuard Labs发现并报告了Microsoft Windows Server中的WINS Server远程内存损坏漏洞.在2017年6月,微软向FortiGuard实验室答复说:"要修复程序漏洞需要对代码进行全面彻底的检查,WINS所提供的功能会被DNS所取代,微软已经建议客户将其迁移出去.也就是说,由于修复漏洞所需要的工作量,Microsoft不会修补此漏洞.相反,M

C/C++的浮点数在内存中的存储方式分析及实例_C 语言

C/C++的浮点数在内存中的存储方式分析 任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100.则在Intel CPU架构的系统中,存放方式为  10000100(低地址单元) 00000100(高地址单元),因为Intel CPU的架构是小端模式.但是对于浮点数在内存是如何存储的?目前所有的C/C++编译器都是采用IEEE所制定的标准浮点格式,即二进制科学表示法.        在二进制科学表示法中,S=M*2^N 主