zoukankan      html  css  js  c++  java
  • 内存管理--分发您的程序存储器

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

    1 32位虚拟内存布局

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

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

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

    wps_clip_image-30045

    图 1

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

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

    wps_clip_image-17631

    图 2

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

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

    1.1 32位经典内存布局

    wps_clip_image-10604

    图 3

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

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

    1.2 32位默认内存布局

    wps_clip_image-19772

    图 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()系统调用(实现)来分配请求所需的内存块。堆管理是非常复杂的。须要精细的算法。应付我们程序中杂乱的分配模式,优化速度和内存使用效率。

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

    wps_clip_image-19798

    图 5

    BSS 数据段 代码段

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

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

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

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

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

    wps_clip_image-10904

    图 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 ).

    wps_clip_image-22928
    图 7

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

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

  • 相关阅读:
    poj 1088 滑雪
    位运算与bitset
    hdu 4607 Park Visit
    树的直径
    codeforces 495D Sonya and Matrix
    German Collegiate Programming Contest 2015(第三场)
    BAPC 2014 Preliminary(第一场)
    Benelux Algorithm Programming Contest 2014 Final(第二场)
    E. Reachability from the Capital(tarjan+dfs)
    poj2104 K-th Number(划分树)
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4655112.html
Copyright © 2011-2022 走看看