内存映射文件和指向易失性对象的指针

Arv*_*vid 9 c c++ mmap volatile language-lawyer

我对volatileC和C++ 语义的理解是它将内存访问转化为(可观察的)副作用.每当读取或写入内存映射文件(或共享内存)时,我都希望指针是volatile限定的,以表明这实际上是I/O. (John Regehr写了一篇关于语义的非常好的文章volatile).

此外,我希望使用像memcpy()访问共享内存的函数是不正确的,因为签名表明挥发性资格被丢弃,并且内存访问不被视为I/O.

在我看来,这是一个赞成的论据std::copy(),其中volatile限定符不会被丢弃,并且内存访问被正确地视为I/O.

但是,我使用指向易失性对象的指针和std::copy()访问内存映射文件的经验是,它比仅使用它要慢几个数量级memcpy().我很想得出结论,或许clang和GCC在对待他们时过于保守volatile.是这样的吗?

对于访问共享内存有什么指导volatile,如果我想遵循标准的字母并将它归还给我依赖的语义?


标准[intro.execution]§14的相关引用:

读取由volatile glvalue指定的对象,修改对象,调用库I/O函数或调用执行任何这些操作的函数都是副作用,这些都是执行环境状态的变化.表达式(或子表达式)的评估通常包括值计算(包括确定用于glvalue评估的对象的身份以及获取先前分配给用于prvalue评估的对象的值)和启动副作用.当对库I/O函数的调用返回或通过volatile glvalue进行访问时,即使调用所隐含的某些外部操作(例如I/O本身)或易失性访问,也会认为副作用已完成可能尚未完成.

Art*_*Art 3

我认为你想太多了。我没有看到任何原因mmap或等效的(我将在这里使用 POSIX 术语)内存是易失性的。

从编译器的角度来看,mmap返回一个被修改的对象,然后在 期间赋予msyncormunmap或隐含的 unmap _Exit。这些函数需要被视为 I/O,而不是其他。

您几乎可以mmapmalloc+readmunmap+替换writefree并且您将获得 I/O 何时以及如何完成的大部分保证。

请注意,这甚至不需要将数据反馈到munmap,只是更容易以这种方式进行演示。您可以mmap返回一块内存并将其内部保存在列表中,然后是一个msyncall没有任何参数的函数(我们称之为 ),它会写出所有调用mmap之前返回的所有内存。然后我们可以据此构建,任何执行 I/O 的函数都有一个隐式msyncall. 但我们不需要走那么远。从编译器的角度来看,libc 是一个黑匣子,其中某些函数返回一些内存,该内存必须在任何其他调用 libc 之前同步,因为编译器无法知道之前从 libc 返回的内存的哪些位仍然被引用并在内部积极使用。

上面这段话是在实践中是如何运作的,但是我们如何从标准的角度来看待它呢?我们先来看一个类似的问题。对于线程来说,共享内存仅在某些非常特定的函数调用时同步。这非常重要,因为现代 CPU 重新排序读取和写入以及内存屏障非常昂贵,并且旧 CPU 可能需要显式缓存刷新才能让其他人(无论是其他线程、进程还是 I/O)看到写入的数据。规范mmap说:

当将 mmap() 与任何其他文件访问方法结合使用时,应用程序必须确保正确的同步

但它没有指定如何完成同步。我知道在实践中同步几乎是必须的,msync因为仍然存在读/写不使用与 mmap 相同的页面缓存的系统。