所以calloc()
通过向操作系统询问一些虚拟内存来工作。操作系统与 MMU 协同工作,并巧妙地以虚拟内存地址进行响应,该地址实际上映射到 copy-on-write, read-only page full of zeroes .当程序尝试写入该页面的任何位置时,就会发生页面错误(因为您无法写入只读页面),会创建该页面的副本,并且您的程序的虚拟内存被映射到这些全新的副本零。
既然 Meltdown 是一回事,操作系统已被修补,因此不再可能跨内核用户边界进行推测性执行。这意味着每当用户代码调用内核代码时,它都会有效地导致管道停顿。通常,当管道在循环中停滞时,对性能来说是毁灭性的,因为 CPU 最终会浪费时间等待数据,无论是来自缓存还是主内存。
鉴于此,我想知道的是:
calloc()
,并且重新映射到新的 CoW 页面,这是在执行内核代码吗? calloc()
分配 4GiB 的内存,然后在一个紧密的循环中用一些任意值(比如 0xFF
而不是 0x00
)初始化它,我的(Intel)CPU 每次写入新的内存时都会达到推测边界吗?页? 最佳答案
你的前提是错误的。页面错误从来没有流水线化/ super 便宜。但是,随着系统调用和所有其他用户-> 内核转换,Meltdown(和 Spectre)缓解确实使它们变得更加昂贵。
跨内核/用户边界的推测执行是不可能的 ; Intel CPU 不会重命名权限级别,即内核/用户转换始终需要完整的管道刷新。我认为您误解了 Meltdown:这纯粹是由用户空间和 delayed handling of the privilege checks on TLB hits 中的推测执行引起的。 .
这在 CPU 设计中是通用的,AFAIK。我不知道任何重命名特权级别或以其他方式推测内核代码、x86 或其他方式的微体系结构。
Meltdown 缓解增加的成本是进入内核会刷新 TLB。 (或者在具有 TLB 进程上下文 ID 支持的 CPU 上,内核可以使用 PCID 来使内核与用户空间使用单独的页表便宜得多)。
内核入口点(在 Linux 上)变成了一个蹦床,它交换页表并跳转到真正的内核入口点,以避免将内核 ASLR 偏移量暴露给用户空间。但除此之外还有一个额外的 mov cr3, reg
在进入和退出内核时(设置一个新的页表),没有其他任何改变。
(Spectre 缓解也很棘手,需要更多的更改,例如 retpolines ......并且还可能显着增加 user->kernel->user 的成本。关于页面错误成本的 IDK。)
@BeeOnRope 报告(见评论和他的完整细节回答)没有 Spectre 补丁,只应用了 Meltdown 补丁,但 nopti
“禁用”它的启动选项,增加了 Skylake CPU 上内核的往返成本(使用 syscall
和伪造的 RAX,立即返回 -ENOSYS
)从 ~100 到 ~300 个周期。所以这可能是蹦床的成本? 启用实际的页表隔离后,它上升到约 700 个周期 .那根本就没有 Spectre 缓解补丁。 (此外,这是 x86-64 syscall
入口点,而不是页面错误。不过它们可能很相似。)
缺页异常 :
CPU 不会预测页面错误,因此无论如何它们都无法推测性地执行处理程序 .页面错误入口点的预取或解码可能会在管道刷新时发生,但该过程在页面错误指令试图退出之前不会启动。错误的加载/存储被标记为在退出时生效,并且不会重新引导前端; Meltdown 的整个关键是在故障负载达到退役之前缺乏对它的操作。
相关:When an interrupt occurs, what happens to instructions in the pipeline?
另外:Out-of-order execution vs. speculative execution有一些关于哪种推测真正导致 Meltdown 以及 CPU 如何处理故障的详细信息。
When a program writes to a never-before-accessed page which was allocated with
calloc()
, and the remapping to the new CoW page occurs, is this executing kernel code?
是的,页面错误由内核的页面错误处理程序处理。写时复制没有纯硬件处理。
If I call calloc() to allocate 4GiB of memory, then initialize it with some arbitrary value (say, 0xFF instead of 0x00) in a tight loop, is my (Intel) CPU going to be hitting a speculation boundary every time it writes to a new page?
是的。内核不会对归零的页面进行故障排除(与当页面缓存中的数据很热时文件支持的映射不同)。因此,每个新页面都会导致页面错误,即使对于 4k 的普通页面也是如此。 (感谢@BeeOnRope 提供有关此的准确信息。)使用匿名大页面,您每 2MiB (x86-64) 只会出现一次页面错误,这要好得多。
如果您想避免每页成本,请使用
mmap(MAP_POPULATE)
进行分配在 Linux 系统上将所有页面预置到硬件页表中。我不确定 madvise
可以为您预置页面,例如madvise(MADV_WILLNEED)
在已经映射的区域上。但是madvise(MADV_HUGEPAGE)
将鼓励内核使用匿名大页面(并且可能对物理内存进行碎片整理以释放连续的 2M 块以启用该功能,如果您没有将其配置为在没有 madvise
的情况下执行此操作)。相关:Two TLB-miss per mmap/access/munmap有一些
perf
在带有 KPTI 补丁的 Linux 内核上的结果。
关于performance - Meltdown 缓解与 `calloc()` s CoW "lazy allocation"相结合,是否意味着 calloc() 分配的内存会受到性能影响?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50191769/