现代cpus如何处理跨页不对齐访问?

时间:2014-05-11 14:46:47

标签: linux arm x86-64 memory-alignment mmu

我试图理解未对齐的内存访问(UMA)如何在现代处理器(即x86-64和ARM体系结构)上运行。我知道我可能遇到UMA问题,从性能下降到CPU故障。我读到了posix_memalign和缓存行。

当我的请求超出页面边界时,我找不到现代系统/硬件如何处理这种情况?

以下是一个例子:

  1. malloc()一块8KB的内存。
  2. 假设malloc()没有足够的内存,sbrk()为8KB。
  3. 内核获取两个内存页面(每个4KB)并将它们映射到我的进程的虚拟地址空间(假设这两个页面在内存中不是一个接一个
  4. movq (offset + $0xffc), %rax我从第4092个字节开始请求8个字节,这意味着我需要从第一页末尾开始的4个字节和从第二个页面开头开始的4个字节。
  5. 物理记忆:

    ---|---------------|---------------|-->
       |... 4b|        |        |4b ...|-->
    

    我需要在页面边界分割的8个字节。

    x86-64和ARM上的MMU如何处理这个问题?内核MM中是否有任何机制以某种方式为这种请求做准备?在malloc中是否有某种保护?处理器做什么?他们取两页吗?

    我的意思是要完成这样的请求MMU必须将一个虚拟地址转换为两个物理地址。它如何处理这样的请求?

    如果我是软件程序员,我应该关心这些事情吗?为什么?

    我正在阅读谷歌,SO,drepper的cpumemory.pdf和gorman的Linux VMM书中的很多链接。但它是一个信息的海洋。如果你至少向我提供一些我可以使用的指针或关键词,那就太好了。

3 个答案:

答案 0 :(得分:4)

我并不过分熟悉英特尔架构的内涵,但ARM架构在“未对齐数据访问限制”下的单个项目中总结了这一具体细节:

  
      
  • 执行未对齐访问的操作可以中止它所进行的任何内存访问,并且可以在多个访问中止。这意味着在页面边界上发生的未对齐访问可以在边界的任一侧生成中止。
  •   

除了可能从单个操作生成两个页面错误之外,它只是另一个未对齐的访问。当然,这仍然假设“只是另一个未对齐的访问”的所有警告 - 即它仅在普通(非设备)内存上有效,仅对于某些加载/存储指令,不保证原子性并且可能很慢 - 微架构将可能从多个对齐的访问 1 中合成一个未对齐的访问,这意味着多个MMU转换,如果它跨越行边界,可能有多个缓存未命中等等。

另一方面,如果未对齐的访问跨越页面边界,那么所有这意味着如果第一个“子访问”的对齐地址转换为OK,则任何后续部分的对齐地址肯定会在TLB中出现。 MMU本身并不关心 - 它只是翻译处理器提供的一些地址。除非MMU引发页面错误,否则内核甚至不会进入图片,即便如此,它也与其他任何页面错误无异。

我已经快速浏览了英特尔手册并且他们的答案没有突然出现在我身上 - 但是在“数据类型”章节中他们说明了这一点:

  

[...]处理器需要两次内存访问才能进行未对齐访问;对齐访问只需要一次内存访问。

所以如果不是大致相同(即每个对齐访问一次翻译),我会感到惊讶。

现在,这是大多数应用程序级程序员不应该担心的事情,只要他们自己行事 - 在汇编语言之外,实际上很难进行未对齐的访问。可能的罪魁祸首是打字指针和搞乱结构包装,这两种情况都有99%的时间没有没有理由靠近,而另外1%几乎肯定是错误的要做。


[1] ARM体系结构伪代码实际上将未对齐访问指定为一系列单独的字节访问,但我希望实现在适当的情况下实际将其优化为更大的对齐访问。

答案 1 :(得分:2)

所以这个体系结构并不重要,除了x86传统上并没有直接告诉你不要在mips和arm传统上产生数据中止的地方,而不是试图让它工作。

无关紧要的是,所有处理器都有固定数量的引脚,固定大小(最大)数据总线和固定大小(最大)地址总线,现代处理器"往往有超过8位宽的数据总线,但地址上的单位仍然是8位字节,因此存在未对齐的机会。如果架构允许,在特定传输中大于一个字节的任何东西都有机会不对齐。

传输通常以字节和/或总线宽度为单位。例如,在ARM amba / axi总线上,长度字段以总线宽度为单位,32或64位,4或8字节。不,它不会以4K字节为单位....

(是的,这是基本的我假设你理解所有这些)。

无论是16位还是128位,未对齐的代价来自额外的总线周期,这些天是每个额外的总线时钟。因此,对于ARM 16位未对齐传输(该臂将在其较新的内核上支持而没有故障),这意味着您需要读取128位而不是64位,64位获得16不是惩罚,因为64是总线的最小大小传递。无论是数据总线的单个宽度还是多个数据总线的每个传输都有多个与之相关的时钟周期,我们可以说有6个时钟周期来进行对齐的16位读取,然后理想情况下,这是7个周期来做一个未对齐的16位。看起来很小,但它确实加起来。

缓存帮助很多,因为缓存的dram端将设置为使用总线宽度的倍数,并且将始终对缓存提取和驱逐进行对齐访问。非缓存访问将遵循相同的痛苦,除了dram方面不是一些时钟,而是几十到几百个时钟的开销。

对于随机访问,单个16位读取不仅跨越总线宽度边界而且碰巧跨越高速缓存行边界不仅会在处理器端产生一个额外的时钟,而且最坏的情况是它会产生额外的高速缓存行获取数十到数百个额外的时钟周期。如果你正在浏览一些碰巧没有对齐的事物(结构/联合可能是一个例子,取决于编译器和代码),那么无论如何都会发生额外的缓存行提取,如果事情的数组有点过了一端或两端,您可能仍然会产生一两个缓存线提取,如果阵列已对齐,您将避免这些提取。

这就是读取的关键所在,在对齐区域之前或之后,您可能需要为每一侧进行转移。

写作既好又坏。随机读取速度较慢,因为事务必须停止,直到答案回来。对于随机写入,内存控制器具有所需的所有信息,它具有地址,数据,字节掩码,传输类型等。因此,它是火,忘记了处理器完成了它的工作,可以从它的角度调用事务完成继续。自然地将这些内容捆绑起来或者对刚写入的内容进行读取,然后处理器由于先前写入的完成以及当前事务而停止。

例如,未对齐的16位写入不仅会产生额外的读取周期,而且假设每个位置一个字节的32或64位宽总线,因此您必须对最近的内存执行读取 - 修改 - 写入是(缓存或dram)。因此,根据处理器和内存控制器如何实现它,它可以是两个单独的读 - 修改 - 写事务(不太可能产生两倍的开销),或双倍宽度读取,修改两个部分和双倍宽度读取。在开销之上和之上产生两个额外的时钟,开销也增加了一倍。如果它是一个对齐的总线宽度写入,那么不需要读取 - 修改 - 写入,您保存读取。现在,如果这个读取 - 修改 - 写入在缓存中,那么这个速度相当快,但是在几个时钟之后仍然可以看到,具体取决于排队的内容,你必须等待。

我也最熟悉ARM。手臂传统上会通过中止来惩罚未对齐的访问,你可以将其关闭,而你将转而使用公共汽车,而不是溢出,这将使一些不错的免费赠品终端交换。更现代的臂芯将容忍并实现不对齐的转移。例如,理解针对非64位对齐地址的4个或更多个寄存器的多个存储器不被视为未对齐访问,即使它是对非64位也不是128位对齐的地址的128位写入。在这种情况下,处理器的作用是将其制动为3次写入,对齐的32位写入,对齐的64位写入和对齐的32位写入。内存控制器不必处理未对齐的内容。这适用于像存储多个这样的法律事务。我熟悉的核心不会做超过2的写长度,8个寄存器存储多个,不是4个写入的单个长度它是2个独立长度的两个写入。但加载8个寄存器的倍数,只要它在64位地址上对齐就是4个事务的单个长度。我很确定,因为总线端没有用于读取的屏蔽,所以一切都以总线宽度为单位,没有理由在一个不是64位对齐到3个事务的地址上说4个寄存器加载倍数,只需做3读长度。当处理器读取单个字节时,您无法从总线上看到所有您看到的是64位读取AFAIK。处理器将字节通道剥离。如果处理器/总线确实关心它是arm,x86,mips等,那么你肯定会看到单独的传输。

大家都这样做吗?没有旧处理器(不考虑arm或x86)会给内存控制器带来更多负担。我不知道现代x86和mips等等。

你的malloc示例。首先,您不会看到4K字节的单总线传输,无论如何4k将被分解为可消化的位。首先,它必须针对内存管理单元执行一到多个总线周期才能找到物理地址和其他属性(这些答案可以缓存以使它们更快,但有时它们必须完全停止以减慢速度)所以对于那个例子来说,唯一重要的转移就是一个对齐的传输,它将4k边界分开,比如一个16位的传输,为mmu系统工作的唯一方式就是支持它必须变成两个独立的在那些物理地址空间中发生的8位传输,是的,mmu查找循环缓存/ dram总线周期等所有内容实际上是两倍。除了那个边界之外,你的8k被分割没有什么特别之处。你的大部分周期将在两个4k页面中的一个之内,所以它看起来像任何其他随机访问,当然重复/顺序访问获得了缓存的好处。

简短的回答是,无论您在哪个平台上1)平台将中止未对齐的转移,或2)路径中的某个地方还有一个或多个(几十个/几百个)由于与对齐访问相比,未对齐访问。

答案 2 :(得分:0)

物理页面是否相邻并不重要。现代CPU使用缓存。数据一次传输到DRAM或从DRAM传输完整的缓存线。因此,DRAM永远不会看到跨越64B边界的多字节读或写,更不用说页边界了。

跨越页面边界的商店仍然很慢(在现代x86上)。我假设硬件通过在稍后的管道阶段检测它来处理页面拆分情况,并触发执行两次TLB检查的重做。 IDK,如果英特尔设计在管道中插入额外的uop来处理它,或者是什么。 (即对延迟,页面分割的吞吐量,所有存储器访问的吞吐量,其他(例如非存储器)uops的吞吐量的影响)。

通常,对于缓存行内的未对齐访问(因为关于Nehalem)没有任何惩罚,并且对于不是页面拆分的缓存行拆分有一点点惩罚。均匀分裂显然比其他人便宜。 (例如,16B负载从一个高速缓存行获取8B,从另一个高速缓存行获取8B)。

无论如何,DRAM永远不会直接看到未对齐的访问。 AFAIK,没有理智的现代设计只有直写高速缓存,所以DRAM只在刷新高速缓存行时看到写入,此时一个未对齐访问弄脏了两个高速缓存行的事实是不可用的。缓存甚至不记录哪些字节是脏的;他们只是在需要时将整个64B写入下一级(或最后一级到DRAM)。

可能有些CPU设计不能以这种方式工作,但英特尔和AMD的设计也是这样。

警告:加载/存储到无法访问的内存区域可能会产生较小的存储,但可能仍然只在一个缓存行中。 (在x86上,此概率适用于使用写入组合存储缓冲区但以其他方式绕过缓存的MOVNT非临时存储。)

跨越页面边界的不可缓存的未对齐商店可能仍会拆分为单独的商店(因为每个部分都需要单独的TLB转换)。

警告2:我没有事实检查这一点。我确定整个缓存行对齐DRAM的访问权限"正常"加载/存储到"正常"但是,记忆区域。