Intel x86 Assembly& Microarchitecture 介绍

示例

历史

第一台电脑

早期的计算机有一块内存,供程序员将代码和数据放入其中,并在此环境中执行CPU。鉴于当时的计算机非常昂贵,很不幸的是它将完成一项工作,停止并等待将下一项工作加载到其中,然后再处理该工作。

多用户,多处理

因此,计算机迅速变得更加复杂,并同时支持多个用户和/或程序-但这是简单的“一块内存”的想法开始出现问题的时候。如果一台计算机同时运行两个程序,或者为多个用户运行同一程序-当然,每个用户都需要单独的数据-那么对该内存的管理就变得至关重要。

示例

例如:如果一个程序被编写为在内存地址1000上工作,但是另一个程序已经在那里加载,则无法加载新程序。解决此问题的一种方法是使程序使用“相对寻址”-程序在何处加载都没有关系,它所做的一切都与加载的内存地址有关。但这需要硬件支持。

复杂性

随着计算机硬件变得越来越复杂,它能够支持更大的内存块,允许同时执行更多程序,并且编写不干扰已加载内容的程序变得更加棘手。一个杂散的内存引用不仅可以关闭当前程序,还可以关闭内存中的任何其他程序-包括操作系统本身!

解决方案

所需要的是一种允许内存块具有动态地址的机制。这样一来,程序可以在其识别的地址处与其内存块一起工作,并且无法访问其他程序的其他块(除非允许进行某些合作)。

分割

实现此目的的一种机制是细分。这样就可以定义所有不同大小的内存块,并且程序需要定义它希望一直访问的段。

问题

该技术功能强大-但它的灵活性非常成问题。由于段实际上将可用内存细分为不同大小的块,因此这些段的内存管理是一个问题:分配,释放,增长,缩小,碎片化-所有这些都需要复杂的例程,有时还需要大量复制才能实现。

分页

另一种技术是将所有内存分成相等大小的块,称为“页面”,这使分配和释放例程非常简单,并且消除了增长,收缩和碎片化(内部碎片化只是一个问题,浪费)。

虚拟寻址

通过将存储器划分为这些块,可以根据需要将它们分配给不同的程序,并提供程序所需的地址。内存物理地址和程序所需地址之间的这种“映射”非常强大,并且是当今每个主要处理器(Intel,ARM,MIPS,Power等)的内存管理的基础。

硬件和操作系统支持

硬件自动连续地执行重新映射,但是需要内存来定义要做什么的表。当然,与此重新映射相关的内务处理必须通过某种方式来控制。操作系统将必须根据需要分配内存,并管理硬件所需的数据表以支持所需的程序。

分页功能

硬件可以重新映射后,它允许什么?主要驱动程序是多处理-运行多个程序的能力,每个程序具有各自的“自有”内存,并且相互保护。但是其他两个选项包括“稀疏数据”和“虚拟内存”。

多处理

每个程序都有自己的虚拟“地址空间”-可以在所需的任何地址上将物理内存映射到的地址范围。只要有足够的物理内存(尽管请参阅下面的“虚拟内存”),就可以同时支持许多程序。

而且,这些程序无法访问未映射到其虚拟地址空间的内存-程序之间的保护是自动的。如果程序需要通信,他们可以要求OS安排共享的内存块-物理内存块,该块内存同时映射到两个不同程序的地址空间中。

稀疏数据

如果未映射较大的虚拟地址空间(通常为4 GB,以与这些处理器通常具有的32位寄存器相对应),则不会浪费内存,而浪费内存本身。这样就可以创建巨大的数据结构,在任何时候都仅映射某些部分。想象一个在每个方向上包含1,000个字节的3维数组:通常需要十亿个字节!但是,程序可以保留其虚拟地址空间的一部分来“保存”该数据,但是只能在填充小部分时进行映射。这样可以进行高效的编程,同时又不会浪费内存来存储不需要的数据。

虚拟内存

上面我用术语“虚拟寻址”来描述硬件执行的虚拟到物理寻址。这通常被称为“虚拟内存”-但该术语更正确地对应于使用虚拟寻址来支持提供比实际可用更多内存的幻觉的技术。

它是这样的:

  • 加载程序并请求更多内存时,操作系统将使用其可用内存来提供内存。除了跟踪已映射的内存外,操作系统还跟踪实际使用内存的时间-硬件支持标记已使用的页面。

  • 当操作系统的物理内存用完时,它将查看为最少使用或最长使用页面而分配给它的所有内存。它会将特定页面的内容保存到硬盘上,记住该位置,将其标记为原始所有者的硬件“不存在”,然后将页面清零并将其交给新所有者。

  • 如果原始所有者尝试再次访问该Page,则硬件会通知OS。然后,操作系统分配一个新的页面(可能必须再次执行上一步!),加载旧页面的内容,然后将新页面移交给原始程序。

    需要注意的重要一点是,由于任何页面都可以映射到任何地址,并且每个页面具有相同的大小,因此只要内容保持不变,一个页面就和其他页面一样好!

  • 如果程序访问未映射的内存位置,则硬件会像以前一样通知操作系统。这次,操作系统指出不是保存的页面,因此将其识别为程序中的错误并终止它!

    当您的应用程序神秘地消失在您身上时,实际上就是这种情况-可能是操作系统中的MessageBox。这也是(经常)导致臭名昭著的蓝屏或悲伤的Mac的原因-这个有错误的程序实际上是一个OS驱动程序,访问了它不应该访问的内存!

分页决定

硬件架构师需要对分页做出一些重大决策,因为设计会直接影响CPU的设计!一个非常灵活的系统将具有很高的开销,需要大量内存才能管理Paging基础结构本身。

Page应该有多大?

在硬件中,分页的最简单实现是获取一个地址并将其分为两部分。上部将指示要访问的页面,而下部将是所需字节在页面中的索引:

+-----------------+------------+
| Page index      | Byte index |
+-----------------+------------+

尽管小页面对每个程序都需要庞大的索引,但很快变得很明显:即使未映射的内存也需要在表中指明这一点。

因此,改为使用多层索引。地址分为多个部分(在下面的示例中显示为三部分),顶部(通常称为“目录”)索引到下一部分,依此类推,直到对最后一页的最终字节索引进行解码为止:

+-----------+------------+------------+
| Dir index | Page index | Byte index |
+-----------+------------+------------+

这意味着目录索引可以在大部分地址空间中指示“未映射”,而无需大量的页索引。

如何优化页表的用法?

CPU将进行的每个地址访问都必须进行映射-因此,虚拟到物理过程必须尽可能高效。如果要实现上述三层系统,则意味着每个内存访问实际上将是三个访问:一个进入目录;另一个进入目录。一进入页表;最后是所需的数据本身。而且,如果CPU也需要执行内务处理(例如指示此页面已被访问或写入),则将需要更多访问权限来更新字段。

内存可能很快,但这会在分页期间对所有内存访问施加三倍的减速!幸运的是,大多数程序都具有“范围的局部性”-也就是说,如果它们访问内存中的一个位置,那么将来的访问可能就在附近。而且由于Pages不太小,因此仅在访问新Page时才需要执行映射转换:并非每次访问都需要这样做。

但是,更好的办法是实现对最近访问的页面的缓存,而不仅仅是最新的页面。问题将跟上已访问哪些页面和未访问哪些页面-硬件将必须在每次访问时扫描缓存以查找缓存的值。因此,该缓存被实现为内容可寻址缓存:而不是通过地址访问,而是通过内容访问-如果请求的数据存在,则将其提供,否则将标记为空白位置以进行填充。缓存管理所有这些。

这种可寻址内容的缓存通常称为转换后备缓冲区(TLB),并且要求OS作为虚拟寻址子系统的一部分进行管理。当操作系统修改目录或页表时,它需要通知TLB更新其条目-或只是使它们无效。