内存管理--分发您的程序存储器

在一个多任务操作系统的每个进程在其自己的沙箱的存储器执行。沙盒是一个虚拟地址空间(virtual address space)。

1 32位虚拟内存布局

在32下部模式虚拟地址空间始终是一个4GB内存地址块。这些虚拟地址的页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。

每个进程拥有一套属于它自己的页表,可是另一个隐情。

仅仅要虚拟地址被使用,那么它就会作用于这台机器上执行的全部软件。包含内核本身。因此一部分虚拟地址必须保留给内核使用:

图 1

这并不意味着内核使用了那么多的物理内存,仅表示它可支配这么大的地址空间,可依据内核须要。将其映射到物理内存。

内核空间在页表中拥有较高的特权级(ring 2或下面)。因此仅仅要用户态的程序试图訪问这些页,就会导致一个页错误(page fault),用户程序不可訪问内核页。在Linux中,内核空间是持续存在的,而且在全部进程中都映射到相同的物理内存。内核代码和数据总是可寻址的。随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化:

图 2

图2中,蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。在上面的样例中,Firefox使用了相当多的虚拟地址空间。由于它是传说中的吃内存大户。

地址空间中的各个条带相应于不同的内存段(memory segment),如:堆、栈之类的。记住。这些段仅仅是简单的内存地址范围,与Intel处理器的段没有关系。

1.1 32位经典内存布局

图 3

32位经典内存布局,程序起始1GB地址为内核空间,接下来是向下增长的栈空间和由0×40000000向上增长的mmap地址。而堆地址是从底部開始,去除ELF、代码段、数据段、常量段之后的地址并向上增长。

可是这样的布局有几个问题,首先是easy遭受溢出攻击。其次是,堆地址空间仅仅有不到1G有木有?假设mmap内存比較少地址非常浪费有木有?所以后来就有了还有一种内存布局

1.2 32位默认内存布局

图 4

当计算机开心、安全、可爱、正常的运转时。差点儿每个进程的各个段的起始虚拟地址都与图4全然一致,这也给远程发掘程序安全漏洞打开了方便之门。一个发掘过程往往须要引用绝对内存地址:栈地址。库函数地址等。远程攻击者必须依赖地址空间布局的一致性,摸索着选择这些地址。假设让他们猜个正着,有人就会被整了。因此,地址空间的随机排布方式逐渐流行起来。

Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。不幸的是。32位地址空间相当紧凑,给随机化所留下的空当不大,削弱了这样的技巧的效果。

进程地址空间中最顶部的段是栈。大多数编程语言将之用于存储局部变量和函数參数。

调用一个方法或函数会将一个新的栈桢(stack frame)压入栈中。栈桢在函数返回时被清理。或许是由于数据严格的遵从LIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈的内容,仅仅须要一个简单的指针指向栈的顶端就可以。

因此压栈(pushing)和退栈(popping)过程很迅速、准确。另外,持续的重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速訪问。进程中的每个线程都有属于自己的栈。

通过不断向栈中压入的数据,超出其容量就有会耗尽栈所相应的内存区域。这将触发一个页故障(page fault)。并被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。假设栈的大小低于RLIMIT_STACK(一般是8MB)。那么普通情况下栈会被加长。程序继续愉快的执行,感觉不到发生了什么事情。这是一种将栈扩展至所需大小的常规机制。然而,假设达到了最大的栈空间大小,就会栈溢出(stack overflow),程序收到一个段错误(Segmentation
Fault)。当映射了的栈区域扩展到所需的大小后。它就不会再收缩回去,即使栈不那么满了。这就好比联邦预算,它总是在增长的。

动态栈增长是唯一一种訪问未映射内存区域(图中白色区域)而被同意的情形。

其他不论什么对未映射内存区域的訪问都会触发页故障,从而导致段错误。一些被映射的区域是仅仅读的,因此企图写这些区域也会导致段错误。

内存映射段

在栈的下方。是我们的内存映射段。此处,内核将文件的内容直接映射到内存。不论什么应用程序都能够通过Linux的mmap()系统调用(实现)或Windows的CreateFileMapping() / MapViewOfFile()请求这样的映射。内存映射是一种方便高效的文件I/O方式。所以它被用于载入动态库。创建一个不正确应于不论什么文件的匿名内存映射也是可能的。此方法用于存放程序的数据。在Linux中。假设你通过malloc()请求一大块内存,C执行库将会创建这样一个匿名映射而不是使用堆内存。

‘大块’意味着比MMAP_THRESHOLD还大,缺省是128KB,能够通过mallopt()调整。

说到堆,它是接下来的一块地址空间。

与栈一样。堆用于执行时内存分配。但不同点是,堆用于存储那些生存期与函数调用无关的数据。

大部分语言都提供了堆管理功能。因此,满足内存请求就成了语言执行时库及内核共同的任务。在C语言中,堆分配的接口是malloc()系列函数,而在具有垃圾收集功能的语言(如C#)中。此接口是newkeyword。

假设堆中有足够的空间来满足内存请求,它就能够被语言执行时库处理而不须要内核參与。否则,堆会被扩大,通过brk()系统调用(实现)来分配请求所需的内存块。堆管理是非常复杂的。须要精细的算法。应付我们程序中杂乱的分配模式,优化速度和内存使用效率。

处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决问题。堆也可能会变得零零碎碎,例如以下图所看到的:

图 5

BSS 数据段 代码段

最后。我们来看看最底部的内存段:BSS,数据段,代码段。在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。差别在于BSS保存的是未被初始化的静态变量内容,它们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的:它不映射到不论什么文件。

假设你写static int cntActiveUsers。则cntActiveUsers的内容就会保存在BSS中。

还有一方面。数据段保存在源码中已经初始化了的静态变量内容。这个内存区域不是匿名的。

它映射了一部分的程序二进制镜像,也就是源码中指定了初始值的静态变量。所以。假设你写static int cntWorkerBees = 10,则cntWorkerBees的内容就保存在数据段中了,并且初始值为10。虽然数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响到被映射的文件。也必须如此。否则给全局变量赋值将会修改你硬盘上的二进制镜像,这是不可想象的。

下图中数据段的样例更加复杂,由于它用了一个指针。在此情况下,指针gonzo(4字节内存地址)本身的值保存在数据段中。而它所指向的实际字符串则不在这里。这个字符串保存在代码段中。代码段是仅仅读的,保存了你所有的代码外加零零碎碎的东西,比方字符串字面值。代码段将你的二进制文件也映射到了内存中。但对此区域的写操作都会使你的程序收到段错误。这有助于防范指针错误,尽管不像在C语言编程时就注意防范来得那么有效。下图展示了这些段以及我们样例中的变量:

图 6

你能够通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住一个段可能包括很多区域。

比方,每一个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。

下一篇文章讲说明这些“区域”(area)的真正含义。有时人们提到“数据段”,指的就是所有的数据段+ BSS + 堆。

2 64位虚拟内存布局

64位系统的寻址空间比較大,所以仍然沿用了32位的经典布局,可是加上了随机的mmap起始地址,以防止溢出攻击。反正一时半会是用不了这么大的内存地址了。所以至少N多年不会变了。

首先, 眼下大部分的操作系统和应用程序并不须要16EB( 264 )如此巨大的地址空间, 实现64位长的地址仅仅会添加系统的复杂度和地址转换的成本, 带不来不论什么优点. 所以眼下的x86-64架构CPU都遵循AMD的Canonical form, 即仅仅有虚拟地址的最低48位才会在地址转换时被使用, 且不论什么虚拟地址的48位至63位必须与47位一致(sign extension). 也就是说, 总的虚拟地址空间为256TB( 248 ).

图 7

然后, 在这256TB的虚拟内存空间中, 0000000000000000 - 00007fffffffffff(128TB)为用户空间, ffff800000000000 - ffffffffffffffff(128TB)为内核空间. 这里须要注意的是, 内核空间中有非常多空洞, 越过第一个空洞后, ffff880000000000 - ffffc7ffffffffff(64TB)才是直接映射物理内存的区域, 也就是说默认的PAGE_OFFSET为ffff880000000000. 从这里我们也能够看出,
这么大的直接映射区域足够映射全部的物理内存, 所以眼下x86-64它不会在高内存架构存在, 那是,ZONE_HIGHMEM这个地区(参考部分).

版权声明:本文博客原创文章,博客,未经同意,不得转载。

时间: 2024-10-14 09:27:06

内存管理--分发您的程序存储器的相关文章

linux内存管理内幕

原文地址:http://blog.csdn.net/wangyuling1234567890/article/details/39609863 忽然想起前几天在公司看到一篇关于内存管理的文章,但当时由于别的事情给打断了.今天想起来,就又在网上找了一下,与大家分享一下. 虽然自己现在从事内核模块开发,对内存池和引用计数也有所了解,但由于理解深度及文笔,不能自己娓娓道来,所以就和大家一起来瞻仰一下大师给我们的讲解. 以下内容来自于http://www.ibm.com/developerworks/c

linux 内存管理模块

什么是MMU MMU(Memory Management Unit)主要用来管理虚拟存储器.物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权.多任务多进程操作系统.(来自百度百科,对其几个点不熟悉,因此可以只考虑加粗部分) 发展历史 注意:学习一个知识点,很重要的一步是了解其为什么而存在?它的存在是为了解决什么问题?然后,在学习的过程中带着这些问题去理解.去思考. 在许多年以前,还是使用DOS或一些古老的操作系统时,内存很小,同时,应用程序也很小,将程序存储

MMU内存管理单元

arm-linux学习-(MMU内存管理单元) 什么是MMU MMU(Memory Management Unit)主要用来管理虚拟存储器.物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权.多任务多进程操作系统.(来自百度百科,对其几个点不熟悉,因此可以只考虑加粗部分) 发展历史 注意:学习一个知识点,很重要的一步是了解其为什么而存在?它的存在是为了解决什么问题?然后,在学习的过程中带着这些问题去理解.去思考. 在许多年以前,还是使用DOS或一些古老的操作系

IOS内存管理

原文链接:http://blog.csdn.net/weiqubo/article/details/7376189 1.  内总管理原则(引用计数)    IOS的对象都继承于NSObject,   该对象有一个方法:retainCount ,内存引用计数. 引用计数在很多技术都用到: window下的COM组件,多线程的信号量,读写锁,思想都一样.       (一般情况下: 后面会讨论例外情况)    alloc      对象分配后引用计数为1    retain    对象的引用计数+1

Linux的内存管理机制

内存管理的一些基本概念: 地址 1)逻辑地址:指由程序产生的与段相关的偏移地址部分.在C语言指针中,读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址. 2)线性地址:段中的偏移地址(逻辑地址),加上相应段的基地址就生成了一个线性地址. 3)物理地址:放在寻址总线上的地址. 4)虚拟地址:保护模式下段和段内偏移量组成的地址,而逻辑地址就是代码段内偏移量,或称进程的逻辑地址. 内存管理主要解决以下问题: 进程的地址空间隔离: 提高内存的使用效率: 程序运行时重

Objective-C基础2:内存管理基础

1.内存存储区域 C.C++里面.栈区:存储临时变量和对象.堆区域:存储动态分配对象.静态变量存储区:存储静态变量和常量对象. OC里面的内存存储区域跟C.C++一样. 2.为什么要进行内存管理 写过C.C++程序的都知道,内存管理永远是C++程序的一大痛点,项目当中崩溃全部来自于内存相关的操作,尤其是指针操作和内存操作,稍不注意就会产生内存访问违规造成程序崩溃.那么如何进行内存管理呢,个人认为有以下几点原则:尽量用系统提供给我们的封装对象,不要用原生的,比如用string而不要用char*,用

Linux中的内存管理(转)

原文:http://blog.chinaunix.net/uid-26611383-id-3761754.html 前一段时间看了<深入理解Linux内核>对其中的内存管理部分花了不少时间,但是还是有很多问题不是很清楚,最近又花了一些时间复习了一下,在这里记录下自己的理解和对Linux中内存管理的一些看法和认识. 我比较喜欢搞清楚一个技术本身的发展历程,简而言之就是这个技术是怎么发展而来的,在这个技术之前存在哪些技术,这些技术有哪些特点,为什么会被目前的技术所取代,而目前的技术又解决了之前的技

OC--内存管理

------------内存管理--------------- 内存管理分类:ARC(自动内存管理) 和MRC (手动内存管理) ARC:内存管理的事情有系统来做 MRC: 内存管理的事情由程序员来做 要想手动管理内存,先要将程序变成手动内存管理状态 retain 使引用计数+1 release 使引用计数 -1 alloc/copy/new 都会使引用计数 +1  ,但是只用于创建对象 retainCount :记录引用计数器的值 注意:内存的法则:引用计数有+1就要有-1,保持平衡. 野指针

linux内核 内存管理

以下内容汇总自网络. 在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址. 如果这个系统只运行一个程序,那么只要这个程序所需的内存不要超过该机器的物理内存就不会出现问题,我们也就不需要考虑内存管理这个麻烦事了,反正就你一个程序,就这么点内存,吃不吃得饱那是你的事情了. 然而现在的系统都是支持多任务,多进程的,这样CPU以及其他硬件的利用率会更高,这个时候我们就要考虑到将系统内有限的物理内存如何及时有效的分配给多个程序了,这个事情本身我们就称之为内存