从两个 32 位定时器计数器读取 64 位定时器值时,正确的 ARM64(AArch64) 数据内存屏障用法是什么?

What is the correct ARM64(AArch64) data memory barrier usage when reading 64bit timer value from two 32bit timer counters?

对于从中提到的两个 32 位定时器计数器读取 64 位定时器值的序列 https://developer.arm.com/documentation/100400/0001/multiprocessing/global-timer/global-timer-registers

在读取之间插入 ARM64 内存屏障的正确方法是什么?

像下面这样的东西对吗?有人可以解释一下在这种情况下如何使用以及使用什么数据内存屏障吗?

do {
  high1 = read(base+4);
  asm volatile("dmb sy");
  low = read(base);
  asm volatile("dmb sy");
  high2 = read(base+4);
  asm volatile("dmb sy");
} while (high2 != high1);

我知道关于如何读取 64 位定时器的问题已经存在,但那里没有内存屏障使用的详细信息,我需要它用于 ARM 机器 - How to read two 32bit counters as a 64bit integer without race condition

有不同类型的内存映射。每种类型都定义了如何进行内存访问以及 reading/writing.

的可能重新排序

在这种情况下重新排序,例如当指令序列 high1 = read(base+4); low = read(base); 由 CPU 像 low = read(base); high1 = read(base+4); 执行时。从性能的角度来看,这是完全合理的。在 CPU 尝试执行 while (high2 != high1); 的阶段,通常先分配哪个寄存器并不重要 'low' 或 'high1'。基本上 CPU 根本不知道 2 个词之间的相互依存关系。

对于这种 64 位值的情况,我们应该采取额外的措施来防止 CPU 删除这种寄存器依赖性。

第一种 'the most right' 方法是将计时器映射为 'Device' 内存。通常所有的硬件映射内存都是'device'内存。 'Device' 内存映射保证严格的内存排序。所以 CPU 不会对内存读取(或写入或两者)进行任何重新排序,它始终是 high1lowhigh2。设备内存也是不可缓存的。在这种情况下无关紧要,但对于使用 DMA 的东西来说,这可以避免保持 cache-memory 一致性。作为结论,在这种情况下,'device' 内存的任何 同步障碍都是多余的

如果想找麻烦,硬件可能被映射为'generic'/'common'内存。 对于 'generic' 内存重新排序是允许的。所以你可能会遇到以下情况。假设我们有像 0000-9999 这样的计数器值(十进制,高位 4 位,低位 4 位)。

  • high1 = read(base+4); low = read(base); 被重新排序并执行为 low = read(base); high1 = read(base+4);
  • low 被读取为 9999,读取完成后计时器递增。
  • 现在计时器是 0001-0000
  • high 读作 0001
  • 我们有 0001-9999 阅读 high2 将再次获得 0001,生活从此变得非常有趣。

所以我认为有必要防止重新排序阅读 high1low,以及 lowhigh2 因为我们可以得到 0001-9999 两种情况下的情况(第二种情况是 high1=0000、high2=0000 和 low=0000,缺少 0001 放在 high 中)。

所以我会说

do {
  high1 = read(base+4);
  asm volatile("dmb sy");
  low = read(base);
  asm volatile("dmb sy");
  high2 = read(base+4);
  // asm volatile("dmb sy"); This looks like excessive
} while (high2 != high1);

PS:看起来你不需要像 sy 这样严格的排序,非常小的保证特定 CPU 的排序应该足够了。