围绕标志和ISR使用正确使用C++原子

kx5bkwkv  于 2023-02-01  发布在  其他
关注(0)|答案(1)|浏览(147)

我有一个案例,需要确保ISR在嵌入式系统(基于ARM Cortex-M4)中卸载库时无法与库交互。
库可以在任何时候加载或卸载,中断/ISR也可以在任何时候触发,这些库加载/卸载/处理ISR调用被封装在项目特定的例程中,因此我可以完全控制在边界执行的内容。
对于这一点,防止这种情况的自然方法似乎是添加一个"is loaded"标志(并使用获取/释放顺序进行操作),并且只有在我可以确定库当前已加载时才调用进程ISR库例程。
但是,卸载 Package 器例程存在一个问题-释放内存的顺序(我最初的想法)不会导致在库实际卸载之前看到"is loaded"标志,因为根据C发布语义,这只提供了在原子操作被重新排序之前没有加载/存储的保证,之后不加载/存储。我确实需要"按获取顺序存储"-在库卸载之前,必须清除标志并使ISR可见(没有加载/存储可以在操作之前重新排序)-但C标准似乎暗示这是不可能的,至少对于正常的获取/释放是这样。
由于附加的总排序属性,我已经探索了使用SeqCst操作/防护的想法,但我不确定我是否正确使用了它。检查防护版本的装配,它似乎正确,DMB出现在正确的位置,但阅读有关atomic_thread_fence和防护-防护同步的标准,看起来这在技术上是不正确的用法,因为"FA在线程A中不是排序在X之前"。
是否有人能够通过这个过程来判断这种用法是否正确(使用C++标准术语,即:https://en.cppreference.com/w/cpp/atomic/memory_orderhttps://en.cppreference.com/w/cpp/atomic/atomic_thread_fence)?
等效代码链接:https://godbolt.org/z/v9nzqvjxa-这使用SeqCst隔离。
库代码的内部是无关紧要的,可以被视为一个不透明的盒子。
编辑:
以上等效代码链接的副本:

#include <atomic>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <chrono>
#include <random>

/// Library code

bool non_atomic_state;

void load_library()
{
    if (!non_atomic_state)
        std::cout << "loaded" << std::endl;
    non_atomic_state = true;
}

void deload_library()
{
    if (non_atomic_state)
        std::cout << "deloaded library" << std::endl;
    non_atomic_state = false;
}

void library_isr_routine()
{
    if (!non_atomic_state)
        throw std::runtime_error("crash");
    std::cout << "library routine" << std::endl;
}

/// MCU project code

std::atomic<bool> loaded;

void mcu_library_isr()
{
    if (loaded.load(std::memory_order_relaxed))
    {
        std::atomic_thread_fence(std::memory_order_seq_cst);

        library_isr_routine();
    }
}

void mcu_load_library()
{
    load_library();

    std::atomic_thread_fence(std::memory_order_seq_cst);

    loaded.store(true, std::memory_order_relaxed);
}

void mcu_deload_library()
{
    loaded.store(false, std::memory_order_relaxed);

    std::atomic_thread_fence(std::memory_order_seq_cst);

    deload_library();
}

/// Test harness code

void t1()
{
    std::random_device rd;

    for (int i = 0; i < 10000; i++)
    {
        auto sleep_duration = std::chrono::milliseconds(rd() % 10);
        std::this_thread::sleep_for(sleep_duration);
        mcu_library_isr();
    }
}

void t2()
{
    std::random_device rd;

    for (int i = 0; i < 10000; i++)
    {
        auto random_value = rd();
        auto sleep_duration = std::chrono::milliseconds(random_value % 10);
        std::this_thread::sleep_for(sleep_duration);
        if (random_value % 2 == 0)
            mcu_load_library();
        else
            mcu_deload_library();
    }
}

int main()
{
    std::thread t1_handle(t1);
    std::thread t2_handle(t2);

    t1_handle.join();
    t2_handle.join();

    return 0;
}
bzzcjhmw

bzzcjhmw1#

(More讨论的要点而非规范的答案)
带有后续控制流依赖项的获取-释放操作难道不能解决所有的歧义吗?
就像这样:

if(loaded.exchange(false, std::memory_order_acq_rel))
    deload_library();

由于它是一个获取-释放,在交换之前不能重新排序减载。作为一个额外的好处,它可以防止两个线程试图减载,但在减载仍在进行时第二个线程无法加载。

相关问题