`
xinklabi
  • 浏览: 1591772 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

程序在内存中运行的奥秘

阅读更多

内存管理是操作系统的核心功能,无论对于开发者还是系统管理员内存管理的重要性都是不言而喻的。我会在接下来的几篇文章通过计算机的实际运行过程谈谈内存管理,当然在必要的时候我也会从底层原理去阐释这个问题。我们提到的概念是不局限于平台特性的通用概念,不过为了阐述这些概念我们选取的实例大多来源于Linux和基于x86架构的32Windows操作系统。这篇文章,我们首先来看看程序是如何使用内存的。

多任务操作系统中,每一个进程都有它自己的内存“沙盒”。所谓“沙盒”,是指虚拟地址空间,在32位模式下,虚拟地址空间最多能表示4GB容量。通过页表机制,虚拟地址空间能够映射到物理内存。页表由操作系统内核来管理,并可被处理器访问。每个进程有着属于自己的页表,不过进程也不能随心所欲。因为虚拟地址一旦投入使用,所有在计算机中运行的软件都会占用虚拟地址空间,包括操作系统内核自身。也就是说,操作系统内核将保留一部分虚拟地址空间。

                        

这并不意味着系统内核能够肆无忌惮的使用物理内存,系统内核只能使用其管辖的虚拟地址空间所对应的物理内存。系统内核所使用的内存空间通过特权码(privileged code2级或者更低)来标记,以防止用户模式的程序访问到内核空间而发生页面错误。在Linux中,内核始终占用着一定空间,并且每个内核进程映射的物理内存地址是固定的。因此,内核代码与数据在内存中的地址总是能够被准确定位,从而为时刻处理中断以及系统调用做好了准备。与此相反,只要用户进程状态发生变化,其映射的地址空间也随即改变。

图中蓝色区域表示虚拟地址中映射到物理内存的部分,白色区域则是未映射。在这个例子中,Firefox惊人的内存需求让它使用的虚拟地址远远超过了其自身的地址空间。内存地址空间是由诸如堆、栈等段式内存管理方式进行管理的。需要指出的是,这里段的概念只不过是表示了一段内存地址,它和Intel段表机制(Intel-style segments)没有任何关系。总的来说,我们在这里讨论的是Linux系统进程标准的段式内存管理方法。

如果运行过程轻松愉快、准确无误,那么上图显示的段式虚拟地址管理启用过程对于计算机内几乎所有进程都完全一致。而这种机制为远程攻击带来了安全隐患。远程攻击往往需要参考绝对内存地址:诸如栈地址、库函数地址等等。而远程攻击者们知道了这些地址空间是固定的,他们闭着眼睛都能找到他们需要的位置。倘若真的如此,那么人们毫无疑问就会被黑客攻击了。正因为这样,随机地址空间已经成为流行的内存地址管理方式。Linux随机为栈(stack)、内存映射段(memorymapping segment)以及堆(heap )的起始地址添加偏移量。不幸的是,32位地址空间非常吃紧,限制了随机分配地址的范围和效率(hamperingits effectiveness)。

进程地址空间的首段地址便是栈,它储存了局部变量以及大多数编程语言的函数参数。当调用方法或者函数时,会有一个新的元素进栈。一旦函数返回了值,那么该元素就会被销毁。这种简单的设计,很有可能是考虑到数据操作都符合后进先出(LIFO )规则,这意味着访问栈的内容并不需要复杂的数据结构,一个简单的栈顶指针就能搞定一切。进栈和出栈的操作方便快捷,不需要过多判断。另外,栈的反复使用能够使栈驻留在CPU缓存(cpu caches)中,从而加快数据存取。每个进程中的每个线程都有属于自己的栈。

如果映射的栈地址空间被压入了超过栈容量的数据,那么栈便无法继续工作了。这种情况会导致一个由expand_stack()函数处理的页面错误,这个函数会调用acct_stack_growth() 函数去检查是否应该为这个栈增加容量。如果这个栈的容量低于RLIMIT_STACK (通常为 8MB)限定的值,那么栈的容量会正常增加,程序也会继续正常运行,并且程序不会知道刚刚发生了什么。当然,这是根据实际需要来调整栈大小的一般机制,如果栈的容量达到了最大值上限,那么栈就会溢出,程序也会收到一个段出错的信息。虽然在程序需要的时候映射的栈空间会增加,但是栈使用的空间减少时,栈却不会释放多余的空间。这就好像联邦政府预算,只可能越来越多。

程序存取上图所示的未映射区域,是唯一正常实现动态增加栈空间的情况,程序访问其他未映射内存访问将会出现页面错误最终导致段错误。有些映射区域是只读的,程序试图写入这些区域同样会导致这种错误。

说到堆,我们就不得不提它的内存使用机制。堆支持运行时内存分配,和栈不同,大多数语言都允许程序使用堆管理内存。满足内存需求是语言运行时和C语言核心之间的联结点,而堆的内存管理接口是通过malloc()及其友元函数来实现的,在C#这样支持垃圾回收机制的语言中,其接口是新定义的关键字。

当堆的空间能够满足程序的内存请求时,那么请求的处理过程就可以直接由语言运行时来负责,而不必有系统内核参与。但是如果堆的空间不能满足程序的内存申请,那么brk()函数会执行系统调用(implementation)来增加堆的内存空间以满足程序的请求。堆管理的实现过程十分复杂,,面对程序内存分配变化莫测的情况,堆管理需要成熟的算法去提升请求的响应速度与内存利用率。系统响应堆的内存请求花费的时间往往变化很大。实时操作系统解决这个问题的方法是采用专用内存分配器( special-purposeal locators)。堆在内存中的分布情况和其他内存管理机制一样充满了碎片,如下图所示:


最后,我们来聊聊刚才图中位置最下方的几个内存段:BSS段、数据段和程序段。在C语言中,BSS段和数据段存储的都是静态(全局)变量。这几个段的不同之处在于BSS段存储的静态变量没有初始化——程序员在源代码中没有为这些静态变量赋值。由于BSS段并没有映射任何文件,所以BSS段在内存中是以匿名形式存在的。举个例子,假设你定义了变量static int cntActiveUsers,那么cntActiveUsers 的数据就保存在BSS段中。

BSS段不同的是,数据段储存了在源代码中经过了初始化的静态变量。因此,数据段的内存区域并不是匿名的。数据段映射了程序二进制映像中源代码给出静态变量初值的部分。所以,如果你定义了static int cntWorkerBees = 10,那么cntWorkerBees变量会赋以初值10并在数据段中保存下来。尽管数据段映射了文件,但这种内存映射是私有的,也就是说,数据段的内存更新不会在其映射的文件中生效。这样造成的结果就是,虽然全局变量的改变应用到了文件在内存中的二进制映像,但是文件本身却不能作出相应的变化!

下面图表中的示例由于使用了指针所以看起来不那么明了。在这个示例中,指针在数据段中占用了4个字节,但是指针所指向的字符串则不在数据段中。对于字符串,内存为它们准备了专门的文本段,文本段以只读的形式存储程序中诸如字符串类型等不会被直接执行的代码。文本段同样会将二进制文件映射到内存,但文件映射区域的写入操作只能以程序收到段错误而告终。这种机制能有效防止指针的错误指向而导致的误操作,不过也不得不承认这种做法显然没有直接在C语言代码中进行保护来的效率高。下面的图表显示了刚刚我们讨论到的段以及变量示例:

如果你想了解Linux中的进程是如何使用内存的,可以读读源代码文件/proc/pid_of_process/maps。值得一提的是,一个内存段往往由多个区域组成。例如,每个正确映射到内存的文件都有属于自己的段,动态库文件则拥有另外的段,这些段类似于BSS段与数据段。下一篇文章我们将进一步探讨“区域”的含义。另外,也会谈谈我个人对“数据段就是数据、BSS以及堆的总和”这种观点的看法。

使用nm  objdump命令能够显示二进制映像的标识,映像的地址、段等等信息都可以查阅。最后要指出的是,上文讨论的Linux虚拟地址管理机制是“灵活”的,该机制在Linux中作为首选已经沿用了几年。使用这种机制要求程序为RLIMIT_STACK变量赋值,如果没有,那么Linux则退回到“传统”方式管理内存,如下图所示:


该图呈现了虚拟地址空间的管理方式。下一篇文章我们将讨论系统内核是如何跟踪这些内存区域的。进而我们会看看内存映射原理、与之相关的文件读写机制以及内存使用情况图表所揭示的含义。

分享到:
评论

相关推荐

    高效程序的奥秘.pdf

    标题:“高效程序的奥秘.pdf” 描述:“如果在编程上有了难题,如果你找不到最简单的方法,下载吧。” 从这份文件的标题与描述中,我们可以推断出这是一份旨在揭示如何编写高效程序的秘密资料,主要面向遇到编程...

    高效程序的奥秘

    4. **内存管理**:书中讨论了内存分配和释放的策略,如如何有效地管理堆栈和堆,以及如何避免内存泄漏,这些都是影响程序性能和稳定性的关键因素。 5. **字符串处理**:字符串操作是C语言编程中的常见任务,书中...

    6502编程大奥秘_6502编程大奥秘_游戏机_

    通过阅读6502编程大奥秘的CHM文件,读者可以深入学习以上这些概念,并逐步掌握6502编程的精髓,从而有能力开发出运行在家用游戏机上的精彩游戏。CHM文件是一种微软的 Compiled HTML Help 格式,它将多个HTML页面和...

    内存管理中malloc和free的奥秘

    内存管理是程序设计中至关重要的一个环节,尤其是在C语言中,程序员需要手动处理内存的分配与释放。本文主要探讨了两个关键的内存管理函数:malloc和free。 malloc函数是C语言标准库提供的一种动态内存分配方式。在...

    Windows 95 系统程式设计大奥秘(简体中文版)

    - **Modules**:模块是 Windows 95 系统中用来组织代码的基本单位,本书详细介绍了模块的加载机制、链接方式及其在内存中的布局。 - **Processes**:进程作为独立运行的程序实例,本书揭示了 Windows 95 如何管理和...

    编码的奥秘二

    5. **内存管理**:在“编码的奥秘二”中,可能会涵盖内存分配、释放和内存模型的理解,这对于避免内存泄漏和提高程序效率至关重要。 6. **指针与引用**:底层编程常常涉及指针和引用的概念,它们是直接操作内存地址...

    6502编程大奥秘

    在文件中提到的“LDA$3000”或“STA$3001”等指令,很可能是在监控程序中输入,用以加载或存储特定内存地址中的数据。同样,对于NES平台,文件中提及的指令集和寻址模式同样适用,因为NES也是基于6502处理器设计的。...

    编码的奥秘 pdf

    6. **程序执行过程**:讲解程序的编译、链接和加载过程,解释程序如何从源代码变为可执行文件,并在内存中运行。 7. **内存管理**:涵盖虚拟内存、堆栈、堆的分配和回收,以及内存泄漏等问题,这些都是软件开发中的...

    编码的奥秘.pdf下载

    在计算机科学中,这通常涉及到字符编码、数据编码和程序编码等多个层面。例如,ASCII编码系统将英文字母、数字和符号映射为特定的二进制数字,使得计算机能够识别和处理文本。而更现代的Unicode(如UTF-8)则支持...

    电子-高效程序的奥秘美.rar

    高效程序的奥秘不仅在于代码的运行速度,还包括内存管理和资源优化。在嵌入式系统中,内存通常是有限的,因此,合理地分配和管理RAM和Flash空间至关重要。这可能涉及到堆栈与堆的使用策略,以及避免内存泄漏。此外,...

    结构化内存监视器(查找偏移基址)下载绿色版.rar

    《结构化内存监视器——探寻程序内存奥秘的利器》 在编程和游戏修改的世界里,内存监视工具扮演着至关重要的角色。结构化内存监视器,正如其名,是一款专门用于观察、分析和修改程序内存的高效工具。它能够帮助...

    Linux奥秘.pdf

    - **内存分配器**:用于在内存中分配和释放数据结构的空间。Linux 内核采用了多种内存分配策略,如伙伴系统等。 #### 三、进程管理 进程是操作系统中执行的基本单位。Linux 内核通过以下机制管理进程: - **进程...

    windows95系统程序设计大奥秘.pdf

    - **Win32s**:这是一个为运行在DOS环境下的Windows应用程序提供32位扩展功能的组件。虽然它使得应用程序能够利用更多的内存空间,但其性能仍然受到DOS的限制。 - **OS/2 Warp**:这是IBM与微软合作开发的一个操作...

    计算机开机奥秘

    3. **系统自举装载程序**:自检成功后,BIOS会加载硬盘的第一个扇区(MBR,主引导记录)的引导程序,然后由引导程序负责加载操作系统到内存中。 4. **I/O设备驱动和中断服务**:BIOS提供对硬件设备的基本驱动和支持...

    英特尔超线程技术现代高性能CPU运行的奥秘.pdf

    在多线程应用中,每个线程执行特定的功能,共同完成整个程序的运行。 **英特尔NetBurst微架构**是英特尔为Pentium 4系列CPU设计的一种处理器架构,它强调了流水线深度和指令级并行度,旨在提供高速的运算性能。 ##...

    LINUX 奥秘.docx

    在Linux中,每个运行中的应用程序都对应一个进程。进程管理包括进程创建、销毁以及状态转换等操作。Linux通过时间片轮转的方式实现进程调度,确保每个进程都能获得一定的CPU时间,从而实现多任务并发执行。 #### 六...

    6502编程大奥秘CHM版

    GVBASIC是一种高级语言,但它在底层运行时仍需转换为6502汇编代码,因此理解和掌握6502编程对于在文曲星上进行高效编程至关重要。 《疯狂的程序员》(1).txt可能是与6502编程相关的经验分享、心得记录或者是教程的一...

    blackmagic,一个安全内存修改库

    blackmagic是一款高效且安全的内存修改库,它允许开发者在不破坏程序运行的情况下,对目标程序的内存进行读写操作。这个库的主要特点在于其提供的安全机制,能够有效防止因不当内存操作导致的错误,确保程序的稳定性...

Global site tag (gtag.js) - Google Analytics