为什么在此禁用中断?

use*_*409 7 c embedded rtos

static void RadioReleaseSPI(void) {
    __disable_interrupt();
    spiTxRxByteCount &= ~0x0100;
    __enable_interrupt();
}
Run Code Online (Sandbox Code Playgroud)

我知道多个任务可能会尝试使用SPI资源.spiTxRxByteCount是一个全局变量,用于跟踪SPI当前是否正被另一个任务使用.当任务需要SPI时,它可以检查状态spiTxRxByteCount以查看是否正在使用SPI.当使用SPI完成任务时,它会调用此函数并清除该位,以指示SPI现在是空闲的.但为什么要先禁用中断然后再重新启用呢?只是偏执狂?

Jas*_*onD 15

&=将执行读 - 修改 - 写操作 - 它不是原子的.您不希望中断在中间改变,导致写入覆盖的值不正确.


emb*_*yle 9

您需要禁用中断以确保原子访问.您不希望任何其他进程在您阅读时访问并可能修改该变量.

简介到嵌入式计算:

对原子访问的需求

想象一下这个场景:在8位uC上运行的前台程序需要检查一个16位变量,称之为X.所以它加载高字节然后加载低字节(或者相反,顺序没有没关系,然后检查16位值.现在假设一个带有相关ISR的中断修改了这个16位变量.进一步想象在程序执行的给定时间,变量的值恰好是0x1234.以下是可能发生的非常糟糕的事情:

  • 前景加载高字节(0x12)
  • 发生ISR,将X修改为0xABCD
  • 前景加载低字节(0xCD)
  • 前台程序看到16位值0x12CD.

问题在于,在访问变量的过程中,实际上修改了一个不可分割的数据,我们的变量X,因为访问变量的CPU指令是可分的.因此我们对变量X的加载已被破坏.您可以看到变量读取的顺序无关紧要.如果在我们的示例中顺序相反,则该变量将被错误地读取为0xAB34而不是0x12CD.无论哪种方式,读取的值既不是旧的有效值(0x1234)也不是新的有效值(0xABCD).

编写ISR引用的数据并不是更好.这次假设为了ISR的利益,前台程序已经写入了先前的值0x1234,然后需要写入一个新的值0xABCD.在这种情况下,VBT如下:

  • foreground存储新的高字节(0xAB)
  • 发生ISR,将X读为0xAB34
  • 前台存储新的低字节(0xCD)

代码(这次是ISR)再次看不到先前的有效值0x1234,也没有看到新的有效值0xABCD,而是看到无效的0xAB34值.

虽然spiTxRxByteCount &= ~0x0100;可能看起来像C中的单个指令,但它实际上是CPU的几个指令.在GCC中编译,汇编列表如下所示:

  57:atomic.c      ****     spiTxRxByteCount &= ~0x0100;
  68                    .loc 1 57 0
  69 004d A1000000      movl    _spiTxRxByteCount, %eax
  69      00
  70 0052 80E4FE        andb    $254, %ah
  71 0055 A3000000      movl    %eax, _spiTxRxByteCount
  71      00
Run Code Online (Sandbox Code Playgroud)

如果在这些指令之间插入中断并修改数据,则第一个ISR可能会读取错误的值.因此,您需要在操作之前禁用中断并声明变量volatile.