假设我有内存映射文件并从不同的线程写入它(写入永远不会重叠并且彼此独立)。我想将已写入的数据与磁盘同步并执行
msync
或 FlushViewOfFile
,然后取消映射文件。
我是否需要将写入线程与刷新线程同步,例如在写入器上使用释放内存栅栏并在刷新器上使用获取栅栏?我担心此时某些写入仍会在 CPU 缓存中,而不是在主内存中。
CPU 和操作系统是否保证仍在 CPU 缓存上的写入最终会进入磁盘,或者我应该首先确保写入到达主 RAM,然后才刷新并取消映射文件?
我的线程不会从映射页面读取数据,如果可能的话,我只想使用宽松的原子操作来跟踪我的数据。
我尝试做的伪代码:
static NUM_WRITERS: AtomicUint = 0;
static CURRENT_OFFSET: AtomicUint = 0;
fn some_thread_fn(void* buffer, payload: &[byte]){
NUM_WRITERS.fetch_add(1, memory_order_relaxed);
offset = CURRENT_OFFSET.fetch_add(payload_size, memory_order_relaxed);
void* dst = buffer + offset;
memcpy(dst, payload, payload_size);
// Do I need memory fence or fetch_sub(Release) here?
compiler_fence(memory_order_release); // Prevent compiler from reordering instructions
NUM_WRITERS.fetch_sub(1, memory_order_relaxed);
if offset + payload_size < buffer_size {
// Some other thread is responsible for unmapping.
return;
}
while (NUM_WRITERS.load(relaxed) > 0) {
mm_pause();
}
// Do I need acquire memory fence here?
compiler_fence(memory_order_acquire); // Prevent compiler from reordering instructions
flush_async(buffer);
unmap(buffer);
}
所有商店都需要在
munmap
之前发生,否则他们可能会出错。它们将被写入磁盘,除非系统在此之前崩溃。对于受 msync
影响的数据,请确保写入(分配/memcpy)发生在该地址范围上的 msync
之前。
在 fdatasync
之后的 fd 上的
munmap
将是确保所有脏数据尽快写入磁盘的更简单方法,除非您有 不想 想要同步的文件的其他区域。如果没有任何手动同步,页面缓存中的脏页会在超时(例如 15 秒)后排队等待写回磁盘。
Sequenced-before 是happens-before 的一种足够强的形式, 因为调用
munmap
或mysnc
会从此线程进行“观察”。例如,在单个线程内,页表条目中的“脏”标志位将被该线程的存储指令修改的任何后续内核代码(例如在msync
系统调用期间)看到。 (或者通过您同步的任何其他线程。)我认为在你的情况下,是的,你确实需要
NUM_WRITERS.fetch_sub(1, memory_order_release);
为每个线程,并且
while (NUM_WRITERS.load(acquire) > 0) { pause }
为到达旋转等待循环进行清理的一个线程。
mm_pause()
是 x86 特定的;在 x86 上,
acquire
加载是免费的,与
relaxed
相同。所有 RMW 都需要
lock
,例如
lock add
,同样的组件对于
seq_cst
来说足够强大。如果您计划移植到其他 ISA,那么请放心,AArch64 具有相对高效的
acquire
和
release
。
relaxed
可能在大多数情况下都有效,但理论上会允许该线程在其他线程到达存储指令之前
munmap
并使页表条目无效,从而导致错误。或者更合理的是,一些商店会在
mysnc
之后发生。使用缓存一致性 DMA(例如在 x86 上),只有设备对内存的实际 DMA 读取才是存储提交到缓存的最后期限。 (但是为了让
msync
首先注意到页面是脏的并将其排队写入磁盘,在操作系统检查硬件页表之前至少必须写入其中的一个字节。)当存储指令在 TLB 条目显示
D
(脏)位 = 0 的页面上运行时,在 x86 上,该内核采用微代码辅助以原子方式 RMW 页表条目以具有
D=1
。 (https://wiki.osdev.org/Paging#Page_Directory) 还有一个
A
位甚至可以通过读取来设置。 (操作系统可以清除它并很快查看哪些页面再次设置了它;这些页面对于驱逐来说是不好的选择。)
atomic_signal_fence
(又名
compiler_barrier
),因为编译器已经无法将存储移动到可能读取存储数据的函数调用的函数之外。 (就像
arr[i] = 1; foo(arr)
,其中
foo
是
msync
或
munmap
,出于完全相同的原因,CPU 不知道的用户定义函数是安全的。)执行乱序执行的 CPU 将保留按程序顺序运行的单个线程的错觉。
msync
。如果写入小于页面,这就不太好了,因为您会为同一页面触发多个磁盘 I/O。