用于读取的原子块与 ARM SysTicks

Atomic Block for reading vs ARM SysTicks

我目前正在将我的 DCF77 library (you may find the source code at GitHub) 从 Arduino(基于 AVR)移植到 Arduino Due(ARM Cortex M3)。我是 ARM 平台的绝对初学者。

使用基于 AVR 的 Arduino,我可以使用 avr-libc 来获取原子块。基本上这会在块期间阻止所有中断,并且稍后会再次允许中断。对于 AVR,这很好。现在对于 ARM Cortex,事情开始变得复杂起来。

首先:对于库的当前使用,这种方法也适用。所以我的第一个问题是:是否有类似于 ARM 的 avr-libc 的 "ATOMIC" 宏的东西?显然其他人已经想到了 something in this directions. Since I am using gcc I could enhance these macors to work almost exactly like the avr-libv ATOMIC macors. I already found some CMSIS documentation 但是这似乎只提供了一个 "enable_irq" 宏而不是 "restore_irq" 宏。

问题 1:是否有任何库(针对 gcc)已经这样做了?

因为 ARM 有不同的优先级中断,我也可以用不同的方式建立原子性。在我的例子中,"atomic" 块必须只确保它们不会被 systick 中断打断。所以实际上我不需要阻止所有内容来制作我的块 "atomic enough"。进一步搜索我发现了一个 ARM synchronization primitives article in the developer infocenter. Especially there is a hint at lockless programming. According to the article this is an advanced concept and that there are many publications on it. Searching the net I found only general explanations of the concept, e.g. here。我认为无锁实现会非常酷,但此时我对 ARM 没有足够的信心从头开始实现它。

问题 2:关于 ARM Cortex M3 上内存块的无锁读取,有没有人给我一些提示?

正如我已经说过的,我只需要保护优先级较低的线程不受 sysTicks 的影响。所以另一种选择是暂时禁用 sysTicks。由于我正在实施一个对时间敏感的时钟算法,所以这不能降低长 运行 中的整体 sysTick 频率。不过,引入一些小的抖动是可以的。这个时候我会觉得这个最吸引人。

问题 3:有什么好的方法可以阻止 sysTick 中断而不丢失任何滴答?

我也找到了CMSIS documentation for semaphores。但是我有点不知所措。特别是我想知道我是否应该使用 CMSIS 以及如何在 Arduino Due 上执行此操作。

问题 4:我最好的选择是什么?或者我应该在哪里继续阅读?

部分答案: 根据 Notlikethat 的提示,我实施了

#if defined(ARDUINO_ARCH_AVR)
    #include <util/atomic.h>
    #define CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

#elif defined(ARDUINO_ARCH_SAM)
    // Workaround as suggested by Whosebug user "Notlikethat"
    // 

    static inline int __int_disable_irq(void) {
        int primask;
        asm volatile("mrs %0, PRIMASK\n" : "=r"(primask));
        asm volatile("cpsid i\n");
        return primask & 1;
    }

    static inline void __int_restore_irq(int *primask) {
        if (!(*primask)) {
            asm volatile ("" ::: "memory");
            asm volatile("cpsie i\n");
        }
    }
    // This critical section macro borrows heavily from
    // avr-libc util/atomic.h
    // --> http://www.nongnu.org/avr-libc/user-manual/atomic_8h_source.html
    #define CRITICAL_SECTION for (int primask_save __attribute__((__cleanup__(__int_restore_irq))) = __int_disable_irq(), __ToDo = 1; __ToDo; __ToDo = 0)

#else
    #error Unsupported controller architecture
#endif

这个宏或多或少做了我需要的。但是我发现还有改进的余地,因为这会阻止所有中断,尽管只阻止 systicks 就足够了。所以问题3仍然悬而未决

您所引用的大部分内容是关于在多个 CPU 之间同步内存访问,或者在同一个 CPU 上抢先安排线程,鉴于所述情况,这似乎完全不合适。 "Atomicity" 在这个意义上指的是保证当一个观察者 更新内存 时,任何观察者 读取内存 看到初始状态,或者更新后的状态,但绝不会介于两者之间。

"Atomicity" 关于中断遵循相同的原则——即确保如果中断发生,代码序列要么不运行全部,或 运行 完全 - 但在概念上是不同的东西 1。只有两件事保证是原子的w.r.t。中断:单个指令2,或在禁用中断的情况下执行的指令序列。

"right" 方法确实是通过 CPSID/CPSIE 指令实现的,这些指令包含在 __disable_irq()/__enable_irq() 内在函数中。请注意,系统中有两个 "stages" 中断处理:M3 内核本身只有一个 IRQ 信号 - 外部 NVIC 的工作是将所有 routing/multiplexing/prioritisation 系统 IRQ 处理到这一行.当 CPU 想要进入临界区时,它需要做的就是用 CPSID 屏蔽它自己的 IRQ 输入,做它需要的,然后用 CPSIE 取消屏蔽,此时任何来自 NVIC 的未决 IRQ 将立即被执行。

对于 nested/re-entrant 临界区的情况,内在函数提供了一种方便的 int __disable_irq(void) 形式,其中 return 是先前的状态,因此您可以有条件地取消屏蔽。

对于其他不提供此类内在函数的编译器,您可以直接使用自己的内在函数,例如:

static inline int disable_irq(void) {
    int primask;
    asm volatile("mrs %0, PRIMASK\n"
                 "cpsid i\n" : "=r"(primask));
    return primask & 1;
}

static inline void enable_irq(int primask) {
    if (primask)
        asm volatile("cpsie i\n");
}

[1] 一个令人困惑的重叠是后一种意义通常用于在单CPU 多任务处理中实现前者 - 如果中断关闭,则在您之前无法安排其他线程已完成,因此将永远不会看到部分更新的内存。

[2] 除了 load/store-multiple 指令可能例外 - 在低延迟中断配置中,这些 可以 被中断,并且在 return.

后重新启动或继续