6

出于并发/并行 GC 的目的,我对 mprotect 系统调用提供的内存顺序保证(即 mprotect 多线程的行为或 mprotect 的内存模型)感兴趣。我的问题是(假设没有编译器重新排序或有足够的编译器障碍)

  1. 如果线程 1 由于线程 2 上的 mprotect 触发了地址上的段错误,我是否可以确定在段错误的信号处理程序中的线程 1 中观察到系统调用之前,所有事情都发生在线程 2 上?如果在对线程 1 执行加载之前在信号处理程序中放置了一个完整的内存屏障怎么办?

  2. 如果线程 1 对由线程 2 设置为 PROT_NONE 的地址进行易失性加载并且没有触发段错误,那么这是否足够发生在两者之间的关系之前。或者换句话说,如果两个线程都这样做(*ga开始为0p是一个页面对齐的地址以只读方式开始)

    // thread 1
    *ga = 1;
    *(volatile int*)p; // no segfault happens
    
    // thread 2
    mprotect(p, 4096, PROT_NONE); // Or replace 4096 by the real userspace-visible page size
    a = *ga;
    

    是否可以保证a在线程 2 上会出现1?(假设在线程 1 上没有观察到段错误并且没有其他代码修改*ga

我最感兴趣的是 Linux 行为,尤其是 x86(_64)、arm/aarch64 和 ppc,尽管欢迎访问有关其他 archs/OS 的信息(对于 Windows,将 mprotect 替换为 VirtualProtect 或其他任何名称......)。到目前为止,我在 x64 和 aarch64 Linux 上的测试表明没有违反这些规定,尽管我不确定我的测试是否是结论性的,或者是否可以长期依赖这种行为。

一些搜索表明,mprotect可能会在删除权限时使用映射的地址对所有线程发出 TLB 击落,这可能会提供此处所述的保证(或者换句话说,提供此保证似乎是此类操作的目标),尽管我不清楚如果内核代码的未来优化可能会破坏这个保证。

参考LKML 帖子,我一周前问过这个问题,但还没有回复......

编辑:澄清问题。我知道 tlb 击落应该提供我正在寻找的保证,但我想知道是否可以依赖这种行为。换句话说,内核发出此类请求的原因是什么,因为如果不是为了提供某种排序保证,就不需要它。

4

1 回答 1

4

所以我在这里发帖一天后在机械同情小组上问了这个问题,并得到了 Gil Tene 的回答。征得他的同意,这是我对他的回答的总结。完整的线程可以在这里找到,以防我没有包括任何不清楚的东西。

对于可以从操作系统获得的整体行为。

(如“操作系统不满足会令人惊讶):

  1. 对 mprotect() 的调用根据调用前后发生的加载和存储完全排序。这往往在 CPU 和操作系统级别上很容易实现,因为 mprotect 是一个系统调用,它涉及一个陷阱,而该陷阱又涉及全排序。[在奇怪的无环转换实现(例如内核执行等)中,保护调用可能负责模拟这种排序假设]。

  2. 对 mprotect 的调用将不会在保护请求在语义上占据进程中的任何地方之前返回。如果 mprotect() 调用设置了会导致错误的保护,则在此 mprotect() 调用之后发生的任何线程上的任何操作都需要出现错误。类似地,如果 mprotect() 调用设置了可以防止故障的保护,则在此 mprotect() 调用之后发生的任何线程上的任何操作都必须不出错。

这实质上意味着其他线程上受影响页面上的内存操作与线程调用同步mprotect。更具体地说,可以预期原始问题中提到的两种情况都得到保证。IE

  1. 如果观察到由于 mprotect 调用导致受影响页面中的一个线程上的负载发生故障,则此故障发生在 mprotect() 调用之后,因此在并且能够观察到在 mprotect 之前发生的所有内存操作。

  2. 如果观察到受影响页面中一个线程上的加载不会导致 mprotect 调用出错,则加载发生在 mprotect 调用和 mprotect 调用之前,并且在加载之后的任何代码都将能够观察到任何内存加载之前发生的操作。

还指出传递性可能不起作用,即一个线程上的故障加载可能不在另一个线程上的非故障加载之后。这可能(有效地)由 tlb 刷新的非原子性引起,导致不同的线程/cpu 在不同时间观察访问权限的变化。

于 2017-05-04T01:34:19.057 回答