【问题标题】:Is it safe to share a volatile variable between the main program and an ISR in C?在 C 中的主程序和 ISR 之间共享 volatile 变量是否安全?
【发布时间】:2023-03-22 01:31:01
【问题描述】:

在主程序和 C 中的 ISR 之间共享一个不大于处理器自然字的对齐整数变量是否安全?是否保证不会发生撕裂的读取或写入?

【问题讨论】:

  • @M.M 虽然 Z80 可以处理两个字节的值,但它的“自然字”仍然是一个字节。
  • @M.M 你真的用c为Z80开发过固件吗?你失去了写大量XOR AINC HL的美好机会;)
  • 不可能有这样的保证——谁或什么会提供它?通用 C 保证的唯一来源是 C 标准,而 C 标准没有说明 ISR。
  • @Olaf 不,这不是保证的工作方式。你不能通过列出你认为事情可能出错的方式来综合保证,如果这些方式都不存在,你就说你有保证。当有人说(通过标准或通过文档)“X 保证做 Y”并且该方有责任确保它有效或他们未能满足他们给你的保证时,你就有了保证。如果有保证,就有提供它的标准或文件。如果您不能指向它,则无法保证。

标签: c embedded volatile


【解决方案1】:

volatile 关键字并不意味着原子性——它只是确保一个变量被显式读取,而不是假定没有改变。对于没有任何其他保护机制的安全共享访问,变量必须是原子的并且声明为volatile

编译器可以记录任何特定目标的原子类型,并且可以为此目的定义sig_atomic_t

一般来说,假设您的编译器不会做任何反常的事情并在指令集允许原子读取的地方拆分对齐的字读取,这可能是合理的。然而,在平台之间移植代码时应谨慎 - 此类低级代码应被视为特定于目标且不可移植。

【讨论】:

  • 无法保证您所需要的只是明确的读/写和原子性。未来的硬件平台可能有我们今天不知道的其他要求。根本没有保证,期限。
  • @DavidSchwartz :在编写系统级代码时,假设您知道独立于语言的平台依赖性可能是公平的。此外,编写系统级代码您正在编写一个特定的系统现在而不是一些可能的未知未来系统。
  • 没错。这就是为什么您的答案(至少第一段的最后一句话)不正确的原因。根据您平台的具体情况,可能存在许多其他问题。在不知道特定系统要求的情况下,对于volatile 是否满足您的任何或所有要求,绝对没有有用的通用答案。 volatile 是否能帮助您满足您的要求取决于这些要求是什么,并且这些要求与预期目标有很大差异——没有独立于平台知识的通用保证。
  • @DavidSchwartz :我显然没有声称volatile 满足*所有“要求-您完全歪曲了我所写的内容。原子性是与volatile 无关的独立属性。@ 的正确使用987654329@remains important in embedded systems.
  • @DavidSchwartz :我声明了共享变量所需的两个属性-您声称这不是真的-您必须在那里帮助我。在某些情况下可能会有额外的要求,但至少必须读取变量,以免它在另一个上下文中被更改,并且它在被更改时不能更改,这仍然是正确的。读;在什么情况下会是不正确的
【解决方案2】:

关于volatile 关键字,它只是为了防止编译器可能的不正确优化。它对线程安全没有帮助。

使用给定大小的共享变量是否是线程安全的取决于编译器。不能保证访问是原子的。例如,编译器可能会在进一步处理之前将变量加载到寄存器中,然后再将其写回内存。主要取决于CPU指令集。如果你想确定,你将不得不检查反汇编代码或用汇编程序编写代码。

否则,您可以使用布尔值创建“穷人的互斥锁”。 这仅适用于不能被其他中断中断的微控制器 ISR 的特定情况。 既然您知道 ISR 不能被中断,那么您可以这样做:

static volatile bool busy;
static volatile uint16_t shared;

void isr (void)
{
  if(!busy)
  {
    shared = something;
  }
}


void main (void)
{
  ...

  busy = true;
  do_something(shared);
  busy = false;

  ...
}

使用这种方法,busyshared 是否是原子的并不重要。无论在哪里触发中断,shared 都不会在访问中被破坏。

【讨论】:

    【解决方案3】:

    不能保证任何通用整数变量都可以原子地写入和读取。如果你需要这样的保证,你应该使用sig_atomic_t 类型。它是唯一有这种保证的类型。

    来自 C99 标准 7.14:

    2 定义的类型是

    sig_atomic_t

    它是对象的(可能是 volatile 限定的)整数类型,即使存在异步中断,也可以作为原子实体访问。

    【讨论】:

    • sig_atomic_t 是布尔类型。而且 C99 不是标准 C。标准 C 提供了可选的原子。
    • @Olaf 好吧,如果您认为sig_atomic_t 是一个布尔类型,那么标准编写者肯定会浪费时间将SIG_ATOMIC_MINSIG_ATOMIC_MAX 包含在内。但他们也节省了时间,因为他们没有在 C99 和 C11 之间更改 sig_atomic_t 的定义。就是这样。
    • (对不起,我错了 sig_atomic_t 是布尔类型)。无论如何,sig_atomic_tsignal.h 的一部分,独立的实施和环境不需要它,而且大多数不提供它。 OP似乎使用了这样的环境。 stdatomic.h OTOH is 为某些实现提供,例如通过 ARM 平台上的 gcc。所以这肯定是一个更好的主意。如果标准类型被原子访问(ATOMIC_INT_LOCK_FREE 等),该标头还提供信息,例如使用静态断言进行测试(这会使您的第一个语句有问题)。
    • @Olaf 关于 stdatomic.h 中定义的原子 - 它们在标准中根据线程和可见性进行定义。在 C 的独立实现中,没有任何线程(并且 ISR 不是单独的线程),您无法提供符合标准的原子。虽然这是事实,但很容易想象这种原子在没有线程的独立环境中应该是什么。
    • @mrn 告诉 gcc 开发人员,因为他们确实支持 stdatomic.h 和关键字,例如手臂。而 gcc 一个独立的实现。一旦你看到他们是如何实现它们的,你可能想重新阅读标准。 C(和 C++)中的原子确实依赖于线程的特定实现!它们正是为了提供通常映射到特定 CPU 指令(屏障、独占加载/存储、CAS 等)的原语。提到线程只是因为它们也必须使用原子。与 C11 中的许多其他地方一样。
    【解决方案4】:

    检查您的编译器。 ISR 是非标准的。此外,除了 int 之外,C 没有“处理器自然词”的真正概念。

    【讨论】:

      【解决方案5】:

      答案是“有时”。如果 ISR 或主进程更改了变量,那么您就可以了。但是,如果两者都操纵变量,则类似于

      main
      Var = Var + 10
      
      ISR
      Var = Var + 10
      

      那么谁知道呢?你会认为最终结果是 var + 20,但如果在汇编中序列是

      Main - Get Var
      ISR -            Get Var
                       Add 10
                       Store Var
                       Return
             Add 10
             Store Var
      

      那么最终结果将是 10,而不是 20

      正如之前的海报所说,你需要有一些保护代码来防止这种情况发生。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-10-02
        • 2015-03-28
        • 2023-03-22
        • 2012-10-27
        • 2013-07-26
        • 1970-01-01
        • 2023-03-06
        • 1970-01-01
        相关资源
        最近更新 更多