我在32位Cortex-M3 ARM控制器(STM32L1)上有一个64位整数变量,可以通过中断处理程序异步修改。
volatile uint64_t v;
void some_interrupt_handler() {
v = v + something;
}
显然,我需要一种方法来访问它,以防止获得不一致的,中途更新的值。
这是第一次尝试
static inline uint64_t read_volatile_uint64(volatile uint64_t *x) {
uint64_t y;
__disable_irq();
y = *x;
__enable_irq();
return y;
}
CMSIS内联函数__disable_irq()
和__enable_irq()
有一个不幸的副作用,迫使编译器存在内存障碍,所以我试图想出更细粒度的东西
static inline uint64_t read_volatile_uint64(volatile uint64_t *x) {
uint64_t y;
asm ( "cpsid i\n"
"ldrd %[value], %[addr]\n"
"cpsie i\n"
: [value]"=r"(y) : [addr]"m"(*x));
return y;
}
它仍然会禁用中断,这是不可取的,所以我想知道是否有办法不使用cpsid
。 权威指南
ARM Cortex-M3和Cortex-M4处理器,Joseph Yiu的第三版
如果处理器执行a时中断请求到达 多周期指令,例如整数除法,指令 可以在中断处理程序之后放弃并重新启动 完成。此行为也适用于加载双字(LDRD)和 存储双字(STRD)指令。
这是否意味着只要写下来就可以了?
static inline uint64_t read_volatile_uint64(volatile uint64_t *x) {
uint64_t y;
asm ( "ldrd %[value], %[addr]\n"
: [value]"=&r"(y) : [addr]"m"(*x));
return y;
}
(使用"=&r"
解决ARM勘误表602117)
是否有一些库或内置函数可以移植?我在atomic_load()
中尝试了stdatomic.h
,但在undefined reference to '__atomic_load_8'
失败了。
答案 0 :(得分:1)
根据ARMv7m参考手册,LDRD不保证原子性。 (A3.5.1)
The only ARMv7-M explicit accesses made by the ARM processor which exhibit single-copy atomicity are:
• All byte transactions
• All halfword transactions to 16-bit aligned locations
• All word transactions to 32-bit aligned locations
LDM, LDC, LDRD, STM, STC, STRD, PUSH and POP operations are seen to be a sequence of 32-bit
transactions aligned to 32 bits. Each of these 32-bit transactions are guaranteed to exhibit single-copy
atomicity. Sub-sequences of two or more 32-bit transactions from the sequence also do not exhibit
single-copy atomicity
你可以做的是用一个字节来表示你正在阅读的ISR。
non_isr(){
do{
flag = 1
foo = doubleword
while(flag > 1)
flag = 0
}
isr(){
if(flag == 1)
flag++;
doubleword = foo
}
来源(需要登录): http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0403e.b/index.html
答案 1 :(得分:0)
我还尝试使用64位(2 x 32位)system_tick,但在STM32L4xx(ARM cortex M3)上使用。我发现,当我尝试仅使用“ volatile uint64_t system_tick”时,编译器注入了汇编指令LDRD,这可能已经足够,因为在读取第一个单词后被中断会导致两个单词都被再次读取。
我在IAR软件支持部门询问了技术人员,他回答说我应该使用C11原子;
#include "stdatomic.h"
#ifdef __STDC_NO_ATOMICS__
static_assert(__STDC_NO_ATOMICS__ != 1);
#endif
volatile atomic_uint_fast64_t system_tick;
/**
* \brief Increment system_timer
* \retval none
*/
void HAL_IncTick(void)
{
system_tick++;
}
/**
* \brief Read 64-bit system_tick
* \retval system_tick
*/
uint64_t HAL_GetSystemTick(void)
{
return system_tick;
}
/**
* \brief Read 32 least significant bits of system_tick
* \retval (uint64_t) system_tick
*/
uint32_t HAL_GetTick(void)
{
return (uint32_t)system_tick;
}
但是我发现添加了大量的代码,使读取的内容变得“原子”。
回到8位微控制器的时代,诀窍是读取高字节,读取低字节,然后读取高字节,直到高字节两次相同-证明没有翻转由ISR创建。因此,如果您反对禁用IRQ,请阅读system_tick,然后启用IRQ,请尝试以下技巧:
/**
* \brief Read 64-bit system_tick
* \retval system_tick
*/
uint64_t HAL_GetSystemTick(void)
{
uint64_t tick;
do {
tick = system_tick;
} while ((uint32_t)(system_tick >> 32) != (uint32_t)(tick >> 32));
return tick;
}
这个想法是,如果最高有效字没有翻转,那么整个64位system_timer必须有效。如果HAL_IncTick()除了进行简单的增量操作以外,其他任何操作都无法实现。
答案 2 :(得分:0)
是的,在此应用程序中使用简单的ldrd
是安全的,因为如果被中断,它将重新启动(不恢复),因此从中断处理程序的角度来看,它将是原子的。
对于所有加载指令而言,这更笼统,除了 exception-continuable (例外可延续),这是一个非常受限制的子集:
ldm
,pop
,vldm
和vpop
可以连续it
块中的指令永远无法持续ldm
/ pop
的第一个加载寄存器也是基址寄存器(例如ldm r0, { r0, r1 }
)永远无法连续这为原子读取由同一内核上的中断处理程序修改的多字变量提供了许多选项。如果您希望读取的数据不是连续的单词数组,则可以执行以下操作:
1: ldrex %[val0], [%[ptr]] // can also be byte/halfword
... more loads here ...
strex %[retry], %[val0], [%[ptr]]
cbz %[retry], 2f
b 1b
2:
您为ldrex / strex使用哪个字(或字节/半字)并不重要,因为异常会执行隐式的clrex
。
另一个方向是,编写一个由中断处理程序读取的变量要困难得多。我不确定100%,但是我认为保证对中断处理程序显示为原子的唯一存储是“单拷贝原子”的存储,即单字节,对齐的半字和对齐的字。任何较大的操作都需要禁用中断或使用一些巧妙的无锁结构。