Mmap(2)
在所有线程之间的映射是原子性的;至少部分原因是 unmap(2)
也是如此。简单来说,所描述的情况大致如下:
MapRegion(from, to, obj) {
Lock(&CurProc->map)
while MapIntersect(&CurProc->map, from, to, &range) {
MapUnMap(&CurProc->map, range.from, range.to)
MapObjectRemove(&CurProc->map, range.from, range.to)
}
MapInsert(&CurProcc->map, from, to, obj)
UnLock(&CurProc->map)
}
接下来,
map_unmap
必须确保在删除映射时,没有线程可以访问它们。请注意
Lock(&thisproc->map)
。
MapUnMap(map, from, to) {
foreach page in map.mmu[from .. to] {
update page structure to invalidate mapping
}
foreach cpu in map.HasUsed {
cause cpu to invoke tlb cache invalidation for (map, from, to)
}
}
第一阶段是重写处理器特定的页面表以使区域失效。
第二阶段是强制每个曾经加载过此映射到其转换缓存中的cpu来使该缓存失效。这一位高度依赖于架构。在旧的x86上,重新编写cr3通常足够了,因此HasUsed实际上是CurrentlyUsing;而较新的amd64可能能够缓存多个地址空间标识符,因此将是HasUsed。在ARM上,本地tlb失效被广播到本地集群;因此HasUsed将指向集群id而不是cpu id。要了解更多详细信息,请搜索“tlb shootdown”,因为它通常被称为。
完成这两个阶段后,没有线程可以访问此地址范围。任何尝试这样做的尝试都会导致故障,从而导致故障线程锁定其映射结构,该结构已由映射线程锁定,因此它将等待映射完成。当映射完成时,所有旧映射都已被删除并替换为新映射,因此在此点之后无法检索先前的映射。
如果另一个线程在更新期间引用了地址范围,会怎样呢?它将继续使用旧数据或者出现故障。在这方面,旧数据并不是一种不一致性,就好像在“映射线程”进入“mmap(2)”之前刚刚引用了它一样。出现故障的情况与上述“故障线程”相同。
总之,通过一系列事务来实现对映射的更新,以确保地址空间的一致视图。这些事务的成本因架构而异。实现此功能的代码可能非常复杂,因为它需要防范隐式操作(例如,推测获取)以及显式操作。
mmap
调用来自同一个线程,只有一个线程在此处调用mmap
。显然,我希望mmap
调用对于进行调用的线程(即从返回后的代码的角度来看,mmap
已完全生效)是原子的,但问题是第二个线程从受mmap
调用影响的区域读取(或写入)。我将尝试澄清问题。 - BeeOnRope