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

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

此外,我希望使用像memcpy()这样的函数来访问共享内存是不正确的,因为签名表明易失性限定被丢弃,并且内存访问不被视为I / O.

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

但是,我使用指向volatile对象的指针和std::copy()访问内存映射文件的经验是,它比使用memcpy()要慢memcpy()数量级。 我很想得出结论,或许clang和海湾合作委员会在处理volatile上过于保守。 是这样的吗?

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


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

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

我对C和C ++中volatile的语义的理解是它将内存访问转换为I / O.

不,它没有那样做。 所有volatile都是从程序员到编译器进行通信,可以随时通过“其他内容”更改某个内存区域。

“别的东西”可能是很多不同的东西。 例子:

  • 内存映射硬件寄存器
  • 变量与ISR共享
  • 变量从回调函数更新
  • 变量与另一个线程或进程共享
  • 通过DMA更新内存区域

由于标准(5.1.2.3)保证对易失性对象的访问(读/写)可能无法优化,因此volatile也可用于阻止某些编译器优化,这在硬件相关编程中最有用。

每当读取或写入内存映射文件(或共享内存)时,我都希望指针符合volatile标准

不一定,没有。 数据的性质无关紧要,只关系它的更新方式。

我希望使用像memcpy()这样的函数来访问共享内存是不正确的

总的来说,这取决于你对“共享内存”的定义。 这是你整个问题的一个问题,因为你一直在谈论“共享记忆”,这不是一个正式的,定义明确的术语。 内存与另一个ISR /线程/进程共享?

是的,与另一个ISR /线程/进程共享的内存可能必须声明为volatile ,具体取决于编译器。 但这只是因为volatile可以阻止编译器做出错误的假设并优化代码以错误的方式访问这些“共享”变量。 在旧的嵌入式系统编译器上特别容易发生的事情。 它在现代托管系统编译器上不应该是必需的。

volatile不会导致内存屏障行为。 它(不一定)强制表达式以某种顺序执行。

volatile肯定不能保证任何forms的primefaces性。 这就是将_Atomic类型限定符添加到C语言的原因。

所以回到复制问题 – 如果内存区域在几个ISR /线程/进程之间“共享”,那么volatile根本就没有帮助。 相反,您需要一些同步方法,例如互斥锁,信号量或临界区。

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

不,这是错误的,因为已经提到的原因。

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

使用系统特定的API:s通过互斥锁/信号量/临界区保护内存访问。

我认为你是在思考这个问题。 我没有看到mmap或等效的任何原因(我将在这里使用POSIX术语)内存是易失性的。

从编译器的角度来看, mmap返回一个被修改的对象,然后在_Exit期间将其赋予msyncmunmap或隐含的unmap。 这些function需要被视为I / O,没有别的。

你几乎可以用malloc + readmunmap替换mmapwrite + free ,你可以获得I / O何时以及如何完成的大部分保证。

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

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

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

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

我对C和C ++中volatile的语义的理解是它将内存访问转换为I / O.

你的理解是错误的。 易失性对象是副作用volatile – 它的值可能会被编译期间编译器不可见的内容所改变

所以volatile对象必须具有永久性(在其范围内)内存存储位置,必须在任何使用之前从中读取,并在每次更改后保存

请参阅示例: https : //godbolt.org/g/1vm1pq

BTW IMO文章是垃圾 – 它假设程序员认为易变性意味着primefaces性和一致性,这不是事实。 那篇文章应该有一个标题 – “为什么我对易变性的理解是错误的,为什么我仍然生活在神话世界中”