可能访问挥发物重新排序? [英] May accesses to volatiles be reordered?

查看:137
本文介绍了可能访问挥发物重新排序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下对 volatile 内存的写入顺序,我已从 David Chisnall在InformIT 的文章了解C11和C ++ 11原子:

  volatile int a = 1; 
volatile int b = 2;
a = 3;

我从C ++ 98的理解是这些操作无法重新排序,每个C ++ 98 1.9:


符合
实现需要模拟抽象机器的可观察行为,因为

...
抽象机器的可观察行为是对易失性数据的读写操作序列和
对库I / O函数的调用


Chisnall说,订单保存的约束仅适用于单个变量,写出一个符合的实现可以生成执行此操作的代码:

  a = 1; 
a = 3;
b = 2;

或:

  b = 2; 
a = 1;
a = 3;

C ++ 11重复C ++ 98的措辞


符合
的实现需要模拟(仅)抽象机器的可观察行为,如下面的
所述。


但是关于 volatile s(1.9 / 8):


对volatile对象的访问严格按照抽象机器的规则进行计算。


1.9 / 12表示访问 volatile glvalue(其中包括变量 a / code>和上面的 c )是一个副作用,1.9 / 14表示一个完整表达式(例如语句)中的副作用必须在以后的完整表达式在同一个线程中的副作用。这使我得出结论,Chisnall显示的两个重新排序是无效的,因为它们不对应于抽象机器指定的排序。



我可以俯瞰某物,或是Chisnall错误吗?



(注意这不是一个线程问题。问题是编译器是否允许在单个线程中重新访问不同的 volatile 变量。)

解决方案

IMO Chisnalls解释(由您提供)显然是错误的。更简单的情况是C ++ 98。需要保留对易失性数据的读取和写入的序列,并且适用于任何易失性数据的读取和写入的有序序列,而不是单个变量。 p>

这很明显,如果你考虑volatile:memory-mapped I / O的原始动机。在mmio中,通常在不同的存储器位置具有几个相关寄存器,并且I / O设备的协议需要对其寄存器集合进行特定的读取和写入序列 - 寄存器之间的顺序很重要。



C ++ 11的措辞避免谈论一个绝对的读写序列,因为在多线程环境中没有一个明确定义这样的事件跨线程的序列 - 并且这不是一个问题,如果这些访问去独立的存储器位置。但我相信的意图是,对于任何序列的易失性数据访问与一个明确定义的顺序规则保持与C ++ 98相同 - 顺序必须保留,无论在该序列访问多少个不同的位置。



这是一个完全独立的问题,这是一个实现。如何(甚至如果)从程序外部可观察到易失性数据访问,以及程序的访问顺序如何映射到外部可见事件未指定。一个实现应该给你一个合理的解释和合理的保证,但什么是合理的取决于上下文。



C ++ 11标准为不同步之间的数据竞争留下了空间易失性访问,所以没有什么需要围绕这些由全内存栅栏或类似的结构。如果存在真正用作存储器映射I / O或DMA的外部接口的存储器部分,那么实现可能是合理的,以保证对这些部分的易失性存取如何暴露给消费设备。 / p>

一个保证可能是从标准推断的(参见[into.execution]):类型的值std :: sigatomic_t 必须具有与它们的写入顺序兼容的值,即使在信号处理程序中 - 至少在单线程程序中。


Consider the following sequence of writes to volatile memory, which I've taken from David Chisnall's article at InformIT, "Understanding C11 and C++11 Atomics":

volatile int a = 1;
volatile int b = 2;
             a = 3;

My understanding from C++98 was that these operations could not be reordered, per C++98 1.9:

conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below ... The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions

Chisnall says that the constraint on order preservation applies only to individual variables, writing that a conforming implementation could generate code that does this:

a = 1;
a = 3;
b = 2;

Or this:

b = 2;
a = 1;
a = 3;

C++11 repeats the C++98 wording that

conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.

but says this about volatiles (1.9/8):

Access to volatile objects are evaluated strictly according to the rules of the abstract machine.

1.9/12 says that accessing a volatile glvalue (which includes the variables a, b, and c above) is a side effect, and 1.9/14 says that the side effects in one full expression (e.g., a statement) must precede the side effects of a later full expression in the same thread. This leads me to conclude that the two reorderings Chisnall shows are invalid, because they do not correspond to the ordering dictated by the abstract machine.

Am I overlooking something, or is Chisnall mistaken?

(Note that this is not a threading question. The question is whether a compiler is permitted to reorder accesses to different volatile variables in a single thread.)

解决方案

IMO Chisnalls interpretation (as presented by you) is clearly wrong. The simpler case is C++98. The sequence of reads and writes to volatile data needs to be preserved and that applies to the ordered sequence of reads and writes of any volatile data, not to a single variable.

This becomes obvious, if you consider the original motivation for volatile: memory-mapped I/O. In mmio you typically have several related registers at different memory location and the protocol of an I/O device requires a specific sequence of reads and writes to its set of registers - order between registers is important.

The C++11 wording avoids talking about an absolute sequence of reads and writes, because in multi-threaded environments there is not one single well-defined sequence of such events across threads - and that is not a problem, if these accesses go to independent memory locations. But I believe the intent is that for any sequence of volatile data accesses with a well-defined order the rules remain the same as for C++98 - the order must be preserved, no matter how many different locations are accessed in that sequence.

It is an entirely separate issue what that entails for an implementation. How (and even if) a volatile data access is observable from outside the program and how the access order of the program maps to externally observable events is unspecified. An implementation should probably give you a reasonable interpretation and reasonable guarantees, but what is reasonable depends on the context.

The C++11 standard leaves room for data races between unsynchronized volatile accesses, so there is nothing that requires surrounding these by full memory fences or similar constructs. If there are parts of memory that are truly used as external interface - for memory-mapped I/O or DMA - then it may be reasonable for the implementation to give you guarantees for how volatile accesses to these parts are exposed to consuming devices.

One guarantee can probably be inferred from the standard (see [into.execution]): values of type volatile std::sigatomic_t must have values compatible with the order of writes to them even in a signal handler - at least in a single-threaded program.

这篇关于可能访问挥发物重新排序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆