ARM Cortex M4处理器上的DSB

3

我已经阅读了ARM文档,发现在某些地方他们说Cortex M4可以重新排序内存写操作,而在其他地方则表明M4不会这样做。

具体来说,我想知道是否需要使用DBM指令,例如:

volatile int flag=0; 
char buffer[10]; 
void foo(char c)
{     
      __ASM volatile ("dbm" : : : "memory");
      __disable_irq(); //disable IRQ as we use flag in ISR
    buffer[0]=c;
    flag=1;
      __ASM volatile ("dbm" : : : "memory");
      __enable_irq(); 
}

请注意,我提出这个问题是因为我看到的所有示例代码在__disable_irq()和__enable_irq()之前都没有使用DBM。所以我想知道它是否应该存在?此外,我想知道何时应该使用DBM。 - Trampas
2个回答

2
嗯,这取决于你的标志是什么,并且因芯片而异。
如果标志存储在内存中:
这里不需要 DSB。访问标志的中断处理程序必须首先从内存中加载它。即使您之前的写操作仍在进行中,CPU 也会确保在存储后发生的加载以正确的顺序进行。
如果标志存储在外设内存中:
现在变得有趣了。假设标志位于某个硬件外设中。对其进行写操作可能会使中断挂起或确认中断(也称为清除挂起的中断)。与上面的内存示例相反,这种效果发生时,CPU 无需先读取标志。因此,存储和加载的自动排序将无法帮助您。由于 CPU 和外设之间存在不同的时钟域,对标志的写入可能会产生出乎意料的长延迟。
因此,以下情况可能发生:
- 您将 flag=1 写入以清除已处理的中断。 - 调用 __enable_irq() 启用中断 - 中断被启用,写入 flag=1 仍在等待。 - 呼呼,中断挂起,CPU 跳转到中断处理程序。 - flag=1 生效了。您现在处于没有任何事情要做的中断处理程序中。
在 __enable_irq() 前执行 DSB 将防止此问题,因为在 __enable_irq() 执行之前,由 flag=1 触发的任何内容都将生效。
如果您认为这种情况纯粹是学术性的:不,它是真实存在的。
想想实时时钟。这些通常以 32khz 运行。如果您从以 64Mhz 运行的 CPU 向其外设空间写入数据,则可能需要长达 2000 个周期才能使写入生效。现在对于实时时钟,数据表通常显示特定的序列,以确保您不会遇到此问题。
但是,与慢速外设一样,同样的问题也可能发生。
当我们在项目后期实施省电时,我的个人轶事就发生了。一切正常运作。然后,我们将 I²C 和 SPI 外设的外设时钟速度降低到可以逃脱的最低速度。这可以节省大量电力并延长电池寿命。我们发现突然间中断开始做出意外的事情。它们似乎每次都会触发两次,造成破坏。在每个受影响的中断处理程序的末尾放置 DSB 可以解决此问题,因为 - 您可以猜测 - 由于外设时钟较慢,我们在清除中断源生效之前离开了中断处理程序。

这种使用 DSB 的方式听起来很不错。我想知道核心是如何知道跨时钟域的写操作发生了呢? - Trampas
Nils,你的部分启用了数据缓存吗? - Trampas
@Trampas 那部分只有为了弥补小型闪存而设置的代码缓存。RAM 上有片上 SRAM,因此不需要缓存。 - Nils Pipenbrinck
所以我猜测使用ARM指令流水线时,总线会停顿到外设,但仍然可以退出中断服务处理程序。因此需要DMB。我知道Atmel在其外设中做了一些疯狂的事情,并且由于时钟差异而具有本地同步位。我认为它们可能具有在核心速度下运行的寄存器的本地缓存,然后稍后发生外设更新。 - Trampas
@Trampas 我认为重点是:如果你写入多个寄存器,大多数情况下你只关心顺序,而不是写入生效的确切时刻。因此,如果你将逻辑放入芯片中,并在每次单独写入时进行时钟域同步,你将为每个外设访问付出代价。在结尾处使用 DMB 可以解决问题,只需要一个单一的停顿。 - Nils Pipenbrinck
我很好奇“在每个中断处理程序的末尾放置DSB的方法”是否真正解决了任何问题。Cortex-M应用笔记321“内存屏障指令编程指南”指出,“除了ISB指令外,架构还定义了异常进入和异常返回也具有ISB语义,导致重新获取指令并重新评估中断。” 由于ISB比DSB甚至更严格,额外的DSB怎么会有帮助呢? - Peter Hansen

0

这部分Cortex M4通用设备用户指南列举了可能会影响重排序的因素。

  • 处理器可以重新排序某些内存访问以提高效率,前提是不会影响指令序列的行为。

  • 处理器具有多个总线接口。

  • 内存映射中的某些内存或设备具有不同的等待状态。

  • 某些内存访问被缓冲或投机执行。

您还应该记住,通常需要使用DSB和ISB(按此顺序),而C语言不保证排序(除了线程内的易失性访问)。

您经常会观察到短流水线和指令序列结合在一起,使竞争条件似乎无法通过特定的编译镜像达成,但这并不是您可以依赖的。时间条件可能很少见(但可能存在),或者随后的代码更改可能会改变结果指令序列。


我已经阅读了 ARM 文档中的一些内容。其中包括,由于我的处理器是单核心的,即使有缓存,它也不会重新排序内存访问或指令。然而,同时建议使用 DSB 和 ISB 使代码更具可移植性到未来的处理器。我还读过像上面链接中提到的,当内存存在等待状态时,可能会发生乱序访问内存的情况。 - Trampas
在像这样只有一个 CPU 的情况下,DSB/ISB 绝对不仅仅是为了可移植性。 - Sean Houlihane
你有单核M4上应该使用DMB、DSB、ISB的示例吗? - Trampas
启用FPU,编程MPU,更改SP。如果您尝试,不一定总能找到这些情况。 - Sean Houlihane
@Trampas AN321《内存屏障指令编程指南》明确指出:“由于Cortex-M3和Cortex-M4上所有加载和存储的固有排序,所有使用DMB都是多余的。”,因此我们至少可以忽略它(如果选择不支持对其他架构的可移植性)。 - Peter Hansen
1
@PeterHansen 是的,我也读到了同样的东西... 我已经开始添加DMB,以便清晰明了并具有代码可移植性。我还发现,在供应商外设中清除中断标志非常混乱,特别是在时钟域和外设缓存之间。不要相信供应商外设,因为它们经常不记录缓存,并且通常当他们这样做时是错误的。因此,我学会了编写防御性代码,因此添加了DMB... - Trampas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接