Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/codeigniter/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C99“;原子的;装入裸金属便携式图书馆_C_Atomic_C99_Isr - Fatal编程技术网

C99“;原子的;装入裸金属便携式图书馆

C99“;原子的;装入裸金属便携式图书馆,c,atomic,c99,isr,C,Atomic,C99,Isr,我正在为裸金属嵌入式应用程序开发一个便携式库 假设我有一个定时器ISR,它增加一个计数器,在主循环中,这个计数器的读取肯定不是原子负载 我试图确保负载一致性(即,我没有读取垃圾,因为负载被中断,值被更改),而不必禁用中断。只要读取的值正确,读取计数器后值是否发生变化并不重要。这能奏效吗 uint32_t read(volatile uint32_t *var){ uint32_t value; do { value = *var; } while(value != *var);

我正在为裸金属嵌入式应用程序开发一个便携式库

假设我有一个定时器ISR,它增加一个计数器,在主循环中,这个计数器的读取肯定不是原子负载

我试图确保负载一致性(即,我没有读取垃圾,因为负载被中断,值被更改),而不必禁用中断。只要读取的值正确,读取计数器后值是否发生变化并不重要。这能奏效吗

uint32_t read(volatile uint32_t *var){
    uint32_t value;
    do { value = *var; } while(value != *var);
    return value;
}

您是否在任何
uint32\t
大于单个汇编指令字读/写大小的系统上运行?如果不是,IO到内存应该是单个指令,因此是原子的(假设总线也是字大小的…),当编译器将其分解为多个较小的读/写时,您会遇到麻烦。否则,我总是不得不求助于DI/EI。您可以让用户配置您的库,以便在原子指令或最小32位字长可用以防止中断旋转时,它具有信息。如果您有这些保证,就不需要验证代码

不过,要回答这个问题,在必须拆分读/写操作的系统上,您的代码是不安全的。假设您在“do”部分正确读取了值,但在“while”部分检查期间该值被拆分。此外,在极端情况下,这是一个无限循环。为了完全安全,您需要重试计数和错误条件来防止这种情况。循环的情况肯定是极端的,但我希望它只是以防万一。这当然会延长运行时间

让我们举一个失败的例子——在一台一次读取8位值的机器上使用16位数字,以便更容易理解:

  • 要从内存读取的值*var为0x1234
  • 读取8位0x12
  • *变量变为0x5678
  • 读取8位0x78-值现在为0x1278(无效)
  • *变量变为0x1234
  • 验证步骤读取8位0x12
  • *变量变为0x5678
  • 验证读取8位0x78
  • 值确认正确0x1278,但这是一个错误,因为*var仅为0x1234和0x5678


    另一种失败的情况是,*var恰好以与代码运行相同的频率更改,这可能导致在每次验证失败时出现无限循环。或者,即使它最终爆发了,这也将是一个很难跟踪的性能缺陷。

    很可能没有任何一种可移植的解决方案来解决这个问题,尤其是因为很多纯C平台都是纯C的,并且使用一次性编译器,也就是说,没有像gcc或clang这样符合主流和现代标准的。因此,如果你真正的目标是根深蒂固的C语言,那么它完全是特定于平台的,不可移植的——以至于“C99”支持已经失败了。对于可移植C代码,您所能期望的最好的是ANSIC支持——指的是ANSI发布的第一个非草案C标准。不幸的是,这仍然是主要供应商可以侥幸逃脱的共同点。我的意思是:Zilog以某种方式逃脱了惩罚,即使他们现在只是Littelfuse的一个部门,以前是Littelfuse收购的IXYS半导体的一个部门

    例如,以下是一些编译器,其中只有一种特定于平台的方法:

    • Zilog eZ8使用“最近”的Zilog C编译器(任何20年或更短的版本都可以):8位值读修改写是原子的。编译器生成与单词对齐的单词指令(如
      LDWX
      INCW
      DECW
      )的16位操作也是原子操作。如果读-修改-写操作符合3条或更少的指令,则应使用
      asm(“\tATM”)。否则,您需要禁用中断:
      asm(“\tPUSHF\n\tDI”),然后重新启用它们:
      asm(“\tPOPF”)

    • Zilog ZNEO是一个具有32位寄存器的16位平台,对寄存器的读-修改-写访问是原子的,但通常通过寄存器进行内存读-修改-写往返,并接受3条指令-因此使用
      asm(“\tATM”)
      预先结束R-M-W操作

    • Zilog Z80和eZ80需要将代码包装在
      asm(“\tDI”)
      asm(“\tEI”)
      中,尽管这仅在知道代码运行时总是启用中断时有效。如果它们可能未启用,则会出现问题,因为Z80不允许读取
      IFF1
      -中断启用触发器的状态。因此,您需要将其状态的“阴影”保存在某个位置,并使用该值有条件地启用中断。不幸的是,eZ80没有提供允许访问
      IEF1
      (eZ80使用
      IEFn
      术语而不是
      IFFn
      )的中断控制器寄存器,因此这种架构监督从古老的Z80延续到了“现代”Z80

    这些不一定是最流行的平台,而且很多人不喜欢Zilog编译器,因为它们的质量相当差(低到你的编译器必须编写一个eZ8目标编译器*)。然而,这些奇怪的角落是纯C代码库的主体,库代码别无选择,只能适应这一点,如果不是直接的话,至少可以通过提供可以用特定于平台的魔力重新定义的宏来实现

    例如,您可以提供默认为空的宏
    MYLIB\u BEGIN\u ATOMIC(vector)
    MYLIB\u END\u ATOMIC(vector)
    ,这些宏将用于包装需要访问给定中断向量的原子代码(或者如果涉及所有中断向量,则为
    -1
    )。当然,将
    MYLIB
    替换为特定于库的“名称空间”前缀

    在“现代”Zilog平台上启用特定于平台的优化,例如
    ATM
    vs
    DI