c - 裸机可移植库中的 C99“原子”加载
问题描述
我正在为裸机嵌入式应用程序开发一个便携式库。
假设我有一个定时器 ISR,它增加一个计数器,并且在主循环中,这个计数器读取来自肯定不是原子负载。
我试图确保负载的一致性(即我没有读取垃圾,因为负载被中断并且值发生了变化)而不诉诸于禁用中断。只要读取的值正确,读取计数器后值是否改变都没有关系。这行得通吗?
uint32_t read(volatile uint32_t *var){
uint32_t value;
do { value = *var; } while(value != *var);
return value;
}
解决方案
几乎不可能有任何形式的可移植解决方案,尤其是因为许多纯 C 平台实际上是纯 C 并使用一次性编译器,即没有像 gcc 或 clang 这样的主流和符合现代标准的编译器。因此,如果您真正针对的是根深蒂固的 C,那么它完全是特定于平台的而不是可移植的 - 以至于“C99”支持是一个失败的原因。对于可移植的 C 代码,您可以期待的最好的就是 ANSI C 支持 - 指的是 ANSI 发布的第一个非草案 C 标准。不幸的是,这仍然是主要供应商逃脱的共同点。我的意思是:Zilog 不知何故侥幸逃脱,即使他们现在只是 Littelfuse 的一个部门,以前是 Littelfuse 收购的 IXYS Semiconductor 的一个部门。
例如,这里有一些编译器,其中只有一种特定于平台的方法:
Zilog eZ8 使用“最新”的 Zilog C 编译器(任何 20 岁或以下的都可以):8 位值读取-修改-写入是原子的。
LDWX
编译器生成字对齐字指令(如,INCW
, )的16 位操作DECW
也是原子的。如果 read-modify-write 以其他方式适合 3 条或更少的指令,您将在操作前添加asm("\tATM");
. 否则,您需要禁用中断:asm("\tPUSHF\n\tDI");
,然后重新启用它们:asm("\tPOPF");
。Zilog ZNEO 是具有 32 位寄存器的 16 位平台,对寄存器的读-修改-写访问是原子的,但内存读-修改-写通常通过寄存器进行往返,并且需要 3 条指令 - 因此预先添加 RMW操作
asm("\tATM")
。Zilog Z80 和 eZ80 需要将代码包装在
asm("\tDI")
and中asm("\tEI")
,尽管这仅在知道代码运行时始终启用中断时才有效。如果它们可能没有被启用,那么就会出现问题,因为 Z80 不允许读取IFF1
中断启用触发器的状态。因此,您需要在某处保存其状态的“影子”,并使用该值有条件地启用中断。不幸的是,eZ80 没有提供允许访问的中断控制器寄存器IEF1
(eZ80 使用IEFn
命名法而不是IFFn
)——因此这种架构疏忽从古老的 Z80 延续到了“现代”。
这些不一定是最流行的平台,而且由于 Zilog 编译器的质量相当差(低到你真的不得不编写一个针对 eZ8 的编译器*),因此许多人不理会 Zilog 编译器。然而,这些奇怪的角落是纯 C 代码库的支柱,库代码别无选择,只能适应这一点,如果不是直接的,那么至少通过提供可以用特定于平台的魔法重新定义的宏。
例如,您可以提供默认为空的宏MYLIB_BEGIN_ATOMIC(vector)
,MYLIB_END_ATOMIC(vector)
用于包装需要针对给定中断向量(或例如-1
,如果针对所有中断向量)进行原子访问的代码。自然地,替换MYLIB_
为特定于您的库的“命名空间”前缀。
为了在“现代”Zilog 平台上启用特定于平台的优化,例如ATM
vs DI
,可以向宏提供一个额外的参数,以分离编译器易于生成的三指令序列与更长的序列的假定“短”序列。这种微优化通常需要汇编输出审计(很容易自动化)来验证指令序列长度的假设,但至少驱动决策的数据是可用的,用户可以选择使用它还是忽略它.
*如果某个迷失的灵魂想知道任何接近奥术的东西。eZ8 - 问一下。我对那个平台了解太多,细节如此血腥,即使是现代好莱坞 CG 和 SFX 也很难在屏幕上再现真实的深度体验。我也可能是唯一一个偶尔以 48MHz 时钟运行 20MHz eZ8 部件的人——这肯定是多元宇宙允许的恶魔附身的迹象。如果您认为这种堕落将其用于生产硬件是令人发指的 - 我支持您。唉,商业案例就是商业案例,该死的物理定律。
推荐阅读
- java - 如何从 csv 文件中获取记录的长度?
- angular - Angular Material Datatable - 对自定义数据源应用过滤
- r - 如何将类列表的输出分配给R中的变量?
- r - 在R中一个接一个地显示多个ggplot2图(就像plot.lm一样)
- git - 如果 else 语句在 Jenkins 管道脚本中无法正常工作(groovy)
- firebase - Firebase 数据库 UI 保持打开状态导致配额问题
- javascript - vue.runtime.common.js:当不使用 this.errors 时,“无法读取未定义的属性 '_transitionClasses'”
- uber-api - products v1.2 api 返回池和机场池。如何决定哪一个适用于用户
- c - c语言scanf()中的[ampersn]有什么用
- c++ - 如何从 char 数组 C++ 中提取 2 个整数并将它们存储在 2 个变量中(初学者)