【问题标题】:Am I right volatile keyword in C needs special hardware support to work?我对 C 中的 volatile 关键字是否需要特殊的硬件支持才能工作?
【发布时间】:2015-06-03 13:36:13
【问题描述】:

我了解 volatile 做什么和不做什么,以 this question 为例

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

我的问题是:在存在 cpu 缓存的情况下,volatile 不能保证上述工作,因为它只强制 cpu 从内存中读取 smePer,但 cpu 不知道内存是在 RAM 中还是在缓存之一中。因此,如果另一个设备更改了 WELL_KNOWN_SEM_ADDR 的内容,waitForSemaphore 不一定知道。所以必须有其他东西使它起作用。

我已阅读 thisthis,似乎 volatile 本身不足以保证此类程序正常工作,必须有一些依赖于平台的魔法,要么通过 L1/2/3 缓存或强制刷新它们,我我对吗?如果是这样,是否所有流行平台(例如 x86)都提供此类支持?

【问题讨论】:

  • 怎么会这样,volatile只是编译器不缓存cpu寄存器中的变量的指令,它不会造成内存屏障,如何确保读取的值与RAM一致?
  • 通常内存映射的硬件是不可缓存的。
  • @user3528438: volatile 普通 RAM 中的变量也用于线程/中断同步。使用 C11,stdatomic.h 可能是更好的选择,一旦涉及多个 CPU,volatile 几乎无用。
  • @swang 如果硬件的内存模型不能保证一致性(例如 x86 上的 mfence 指令),您需要发出硬件/cpu 特定指令来执行此操作。但不要那样做。要么你在一个低级处理器上工作,你将某些硬件寄存器映射到内存(这是不可缓存的)并在你的 C 程序中使用 volatile,或者使用新 C/C++ 版本的互斥体/信号量或原子特性,或者为您的平台提供经过良好测试的现有原子库。

标签: c++ c caching volatile


【解决方案1】:

volatile 禁止编译器优化对此类变量的访问或对所有易失性变量的此类访问重新排序(仅限!)。实际语义可能是实现定义的(但必须由编译器指定)。

对于硬件:是的,缓存和总线缓冲区(例如“写缓冲区”)可能仍会重新排序,更不用说 CPU。 volatile 并不意味着栅栏/障碍。因此,底层硬件必须至少将这些内存区域标记为“(强)有序”。如果涉及外部硬件,该区域也必须标记为“未缓存”,因此每次访问都将直接进入硬件。这也可能适用于其他 CPU,除非系统中有某种“窥探”硬件(每个 CPU 都会收到另一个 CPU 中缓存更改的通知)。

C11 提供stdatomic.h(可选)。那将更适合线程/中断同步。如果有多个 CPU,volatile 无论如何都几乎没用。但是,它仍然有硬件应用程序(可能需要额外的互斥锁或对外围设备的隐式独占访问)。

【讨论】:

  • 如果超越 x86,所有类型的重新排序都是可能的,而不仅仅是存储-存储重写(我认为您在“写入缓冲区”下的意思是);有关可能重新订购的详细信息,请参阅kernel.org/doc/Documentation/memory-barriers.txt
  • @No-BugsHare:作为嵌入式开发人员,我总是超越 x86。我在哪里限制了重新排序的含义?
  • 不仅“写缓冲区”可能会重新排序,因为它可能(错误)从您的回复中理解;有大量不同的重新排序可能。我不坚持你的意思,但这样很容易被误解。
  • @No-BugsHare:我忘记了右括号。为了进一步澄清,我现在还添加了两个字母和两个点。嗯,我也可以添加 CPU;实际上我并没有排除它们,您想说明非显而易见的。但你是对的,x86 用户可能会忘记 CPU 也可能会重新排序。
  • @Jongware:我删除了那句话并在该段末尾添加了一个。 C11 6.7.3#7 为 ID 留出了一些空间,这可能是我感到困惑的地方。感谢您的提示。
【解决方案2】:

volatile 本身仅告诉编译器“假设当您存储到 volatile 对象时,有人可能会注意到,因此您无法优化存储”和“假设当您读取 volatile 对象时,结果可能与您存储的内容不同,并且有人可能会注意到您读取了 volatile 对象,因此您无法优化读取,并且您必须使用读取的值而不是您认为应该存储的值” .

例如,如果你写

int x = 0 * (*p);

编译器不能说“无论 *p 是什么,我都会将 x 设置为 0,所以我不需要费心阅读 *p”。如果 *p 是不稳定的,那就错了。

在 C11 中,有一些新功能可以帮助您,或者您可能想要使用适当的操作系统功能。

【讨论】:

    【解决方案3】:
    1. 不,volatile 本身不需要硬件支持即可工作。有关详细信息,请参阅 gnaster729 的回答。

    2. 缓存同步问题与所谓的“内存模型”(https://en.wikipedia.org/wiki/Memory_ordering)有关;如果将其描述得很短-没有“如果另一个设备更改了变量”之类的东西(从阅读端看不到),只有“如果另一个设备更改变量a早于变量b”之类的东西-这是可观察的。为了处理它,使用了所谓的“内存栅栏”又名“内存屏障”(https://en.wikipedia.org/wiki/Memory_barrierhttps://www.kernel.org/doc/Documentation/memory-barriers.txt),如果我没记错的话,它在 C++11 中有特殊的支持。在 x86 上,内存栅栏隐含在 CAS 指令中; x86 被广泛认为符合所谓的 TSO(总存储排序)内存模型,该模型非常严格并且是最容易处理的模型之一(尽管如上面 kernel.org ref 中所述,处理它是完全可行的任何内存模型下的正确性)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-01-30
      • 1970-01-01
      • 2012-02-03
      • 1970-01-01
      • 2012-05-15
      • 1970-01-01
      • 2013-05-18
      相关资源
      最近更新 更多