出于并行/并行GC的目的, 我对mprotect系统调用提供的内存顺序保证感兴趣(即具有多个线程的mprotect的行为或mprotect的内存模型)。我的问题是(假设没有更复杂的重新排序或有足够的编译器障碍)
如果线程1由于mprotect而触发地址上的段错误 线程2,我可以确定一切都发生在线程2之前 系统调用可以在信号处理程序中的线程1中观察到 段错误?如果信号中放置了完整的内存屏障,该怎么办? 在thread1上执行加载之前的处理程序?
如果线程1对设置为的地址执行易失性加载
线程2的PROT_NONE并没有触发段错误,这就足够了
a发生在两者之间的关系之前。或者换句话说,如果是
两个线程(*ga
以0
开头,p
是以只读方式启动的页面对齐地址)
// 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;
是否可以保证线程2上的a
为1
? (假设没有
在线程1上观察到段错误,没有其他代码修改*ga
)
我最感兴趣的是Linux行为,尤其是x86(_64),arm / aarch64和ppc,尽管欢迎使用有关其他arch / OS的信息(对于windows,用VirtualProtect替换mprotect或其他任何名称... )。到目前为止,我对x64和aarch64 Linux的测试表明没有违反这些,但我不确定我的测试是否具有决定性,或者是否可以长期依赖这种行为。
有些搜索表明mprotect
可能会在所有线程上发出TLB击落,并在删除权限时映射地址,这可能会提供此处所述的保证(换句话说,提供此保证似乎是这样的操作)虽然我不清楚未来的内核代码优化是否会打破这个保证。
参考LKML post我在一周前询问了这个问题,但没有回复......
编辑:关于问题的澄清。我知道tlb击落应该提供我正在寻找的保证,但我想知道是否可以依赖这种行为。换句话说,内核发出此类请求的原因是什么,因为如果不提供某种排序保证则不需要它。
答案 0 :(得分:1)
所以我在帖子发布后的第二天就机械同情小组问了这个问题,得到了吉尔特恩的回答。在他的允许下,这是我对他的答案的总结。如果我没有包含任何不清楚的内容,那么完整的主题可用here。
对于操作系统可以预期的整体行为。
(如“操作系统不能满足会令人惊讶”):
对于在调用之前和之后发生的加载和存储,对mprotect()的调用是完全有序的。这往往在CPU和操作系统级别上很容易实现,因为mprotect是一个系统调用,它涉及一个陷阱,而后者又涉及完整的排序。 [在奇怪的无环转换实现中(例如,在内核中执行等),保护调用可能会负责模拟这种排序假设]。
- 醇>
在保护请求在语义上占据进程内的任何位置之前,不会返回对mprotect的调用。如果mprotect()调用设置了可能导致错误的保护,则在此mprotect()调用之后发生的任何线程上的任何操作都需要出错。类似地,如果mprotect()调用设置了一个可以防止出现故障的保护,那么在这个mprotect()调用之后发生的任何线程上的任何操作都不需要出错。
这实质上意味着其他线程上受影响页面上的内存操作与调用mprotect
的线程同步。更具体地说,可以预期原始问题中提到的两种情况都得到保证。即。
如果观察到受影响页面中一个线程的负载由于mprotect调用而发生故障,则此错误发生在mprotect()调用之后,因此可以观察到在mprotect之前发生的所有内存操作
如果观察到受影响页面中一个线程上的负载没有故障,则会在mprotect调用和mprotect调用之前发生加载,并且在加载之后发生任何代码,并且能够观察在加载之前发生的任何内存操作。
还指出传递性可能不起作用,即一个线程上的故障负载可能不是在另一个线程上的非故障负载之后。这可以(有效地)由tlb flush的非原子性引起,导致不同的线程/ cpus在不同的时间观察访问权限的变化。