请考虑以下代码段:
volatile uint32_t *registers;
\\ ...
\\ registers gets mmap'd and so on
\\ ...
registers[0] = 0x1;
registers[1] = 0x1;
此registers
初始化为mmap
到某些外围地址空间(因此是易失性的)。
我还没有在任何地方看到这个问题,但在一般情况下,我认为这些寄存器写入(或实际上任何寄存器访问)应该通过内存屏障相互保护。问题是如果外设期望访问正确排序,编译器可能会忽略它。
所以看起来应该是这样的:
volatile uint32_t *registers;
pthread_mutex_t reg_mutex;
\\ ...
\\ registers gets mmap'd and so on
\\ ...
pthread_mutex_lock(®_mutex);
registers[0] = 0x1;
pthread_mutex_unlock(®_mutex);
pthread_mutex_lock(®_mutex)
registers[1] = 0x1;
pthread_mutex_unlock(®_mutex);
我的推理是否正确?我错过了什么吗?有更好的方法吗?
在我看来,这应该是任何理解使用内存映射设备的核心。
编辑:在回答问题时,我注意到无序执行指令存在潜在问题。在这种情况下,我将重新解释这个问题以明确解决这个问题:内存屏障是否是正确的方法来约束处理器正确排序? (并且互斥量是一种有效的策略吗?)
答案 0 :(得分:1)
根据C和C ++标准,对易失性存储器位置的访问永远不会重新排序,因此您不必执行任何特殊操作。它们也永远不会合并,所以如果你这样做:
registers[0] = 0x1;
registers[0] = 0x1;
registers[0] = 0x1;
registers[0] = 0x1;
编译器将生成4次内存写入。
查看C11标准的最新草案 - http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
6.7.3类型限定符
...
7具有volatile限定类型的对象可能会以未知的方式进行修改 实施或有其他未知的副作用。因此任何表达都是指 对这样的对象应严格按照抽象机的规则进行评估, 如5.1.2.3中所述。 此外,在每个序列点上最后存储的值 对象应与抽象机器规定的内容一致,除非经过修改 前面提到的未知因素.13)什么构成了对象的访问 volatile的限定类型是实现定义的。
在你的例子中,每一行实际上是一个"完整的表达式" (因为每个都以;
结束)所以每行包含一个序列点。上述要求强制所有访问按给定顺序执行,没有优化,重新排序或任何巧妙的技巧。
然而 - 请注意,这仅表明编译器将生成以与源代码中的确切顺序和编号访问这些内存位置的指令。例如,如果您需要在多个内核之间同步这些访问,那么这完全取决于硬件,任何C标准都不会对您有所帮助。无论如何 - 互斥体可能有点太多而不能强制进行这种同步。通过使用编译器的一些内部函数,你会感觉更好。