Meltdown缓解与`calloc()的CoW"延迟分配"相结合,是否意味着calloc()的性能损失 - 分配内存?

Tul*_*x86 7 performance memory-management cpu-architecture calloc page-fault

所以calloc()通过向操作系统询问一些虚拟内存来工作.操作系统正在使用MMU进行操作,并巧妙地响应虚拟内存地址,该地址实际上映射到一个写满了零的只读页面.当程序尝试写入该页面中的任何位置时,会发生页面错误(因为您无法写入只读页面),会创建该页面的副本,并且您的程序的虚拟内存将映射到这些全新的副本零.

既然Meltdown是一个东西,操作系统已被修补,因此不再可能跨内核用户边界推测性地执行.这意味着每当用户代码调用内核代码时,它就会有效地导致管道停顿.通常,当管道在循环中停顿时,它对性能造成破坏性,因为CPU最终会浪费时间等待数据,无论是来自缓存还是主存储器.

鉴于此,我想知道的是:

  • 当程序写入分配的从未访问过的页面calloc(),并且重新映射到新的CoW页面时,是否正在执行内核代码?
  • 是否在OS级别或MMU级别实现了页面错误写时复制功能?
  • 如果我调用calloc()分配4GiB的内存,然后在紧密的循环中用一些任意值(比如说,0xFF而不是0x00)初始化它,我的(英特尔)CPU每次写入新页面时都会触及推测边界吗?
  • 最后,如果它是真实的,那么这种影响对现实世界的表现有何影响?

Pet*_*des 4

你的前提是错误的。页面错误从来都不是管道化的/超级便宜的。不过,Meltdown(和 Spectre)缓解措施以及系统调用和所有其他用户->内核转换确实使它们变得更加昂贵。


跨内核/用户边界的推测执行是不可能的;Intel CPU 不会重命名特权级别,即内核/用户转换始终需要完整的管道刷新。我认为您误解了 Meltdown:这纯粹是由于用户空间中的推测执行和对 TLB 命中的特权检​​查的延迟处理造成的。

AFAIK,这在 CPU 设计中是通用的。我不知道有任何微架构会重命名权限级别或以其他方式推测内核代码、x86 或其他代码。

Meltdown 缓解措施增加的成本是进入内核刷新 TLB。(或者在支持 TLB 进程上下文 ID 的 CPU 上,内核可以使用 PCID 来使内核与用户空间使用单独的页表更加便宜)。

内核入口点(在 Linux 上)变成了一个交换页表并跳转到真正的内核入口点的蹦床,以避免将内核 ASLR 偏移量暴露给用户空间。但除了这一点以及mov cr3, reg进入和退出内核时的额外操作(设置新的页表)之外,没有其他任何改变。

(Spectre 缓解也很棘手,需要更多更改,例如 retpolines...并且还可能显着增加用户->内核->用户的成本。不知道页面错误成本。)

@BeeOnRope 报告(有关完整详细信息,请参阅评论和他的回答),如果没有 Spectre 补丁,仅应用 Meltdown 补丁,但nopti启动选项“禁用”它,增加了 Skylake CPU 上内核往返的成本(带有syscall伪造的 RAX) ,-ENOSYS立即返回)从约 100 个周期增加到约 300 个周期。那么这可能就是蹦床的费用吗? 在启用实际页表隔离的情况下,它达到了约 700 个周期。这根本就没有Spectre 缓解补丁。(另外,这是 x86-64syscall入口点,而不是页面错误。不过它们可能很相似。)


页面错误异常

CPU 无法预测页面错误,因此它们无论如何都无法推测性地执行处理程序。页面错误入口点的预取或解码可能在管道刷新时发生,但该过程直到页面错误指令尝试退出时才会启动。错误的加载/存储被标记为在退役时生效,并且不会重新引导前端;崩溃的关键在于,在故障负载报废之前缺乏对它采取的行动。

相关:当中断发生时,流水线中的指令会发生什么?

另外:乱序执行与推测执行有一些关于哪种推测真正导致 Meltdown 以及 CPU 如何处理故障的细节。


当程序写入分配有 的从未访问过的页面calloc(),并重新映射到新的 CoW 页面时,这是在执行内核代码吗?

是的,页面错误由内核的页面错误处理程序处理。写入时复制没有纯硬件处理。

如果我调用 calloc() 来分配 4GiB 内存,然后在紧密循环中使用某个任意值(例如 0xFF 而不是 0x00)对其进行初始化,那么我的(Intel)CPU 每次写入时都会遇到推测边界吗?一个新页面?

是的。内核不会对归零页面进行故障处理(与页面缓存中的数据很热时的文件支持映射不同)。因此,每个触及的新页面都会导致页面错误,即使对于小型 4k 正常页面也是如此。(感谢@BeeOnRope 提供了这方面的准确信息。)使用匿名大页面,每 2MiB (x86-64) 只会出现一次页面错误,这要好得多。

如果要避免每页成本,请mmap(MAP_POPULATE)在 Linux 系统上进行分配以将所有页面预先放入硬件页表中。我不确定是否madvise可以为您预先设置页面故障,例如madvise(MADV_WILLNEED)在已映射的区域上。但madvise(MADV_HUGEPAGE)会鼓励内核使用匿名大页(如果您没有将其配置为在没有 的情况下执行此操作,则可能会整理物理内存碎片以释放连续的 2M 块madvise)。

相关:每个 mmap/access/munmap 两次 TLB 未命中perf在带有 KPTI 补丁的 Linux 内核上有一些结果。