簡體   English   中英

圍繞標志和 ISR 使用正確使用 C++ 原子

[英]Correct usage of C++ atomics around flag & ISR usage

我有一個案例,我需要確保 ISR 在嵌入式系統(基於 ARM Cortex-M4)中卸載時無法與庫交互。

該庫可以隨時加載或卸載,也可以隨時觸發中斷/ISR。 這些庫加載/卸載/處理 ISR 調用包含在項目特定例程中,因此我可以完全控制在邊界執行的內容。

為此,防止這種情況的自然方法似乎是添加“已加載”標志(並使用獲取/釋放順序進行操作),並且僅在我可以確定庫當前已加載。

然而,deload wrapper routine 出現了一個問題——發布 memory 排序(我最初的想法)不會導致在庫實際卸載之前看到“已加載”標志,因為根據 C++ 發布語義,這僅提供保證對於要重新排序的原子操作之前沒有加載/存儲,而不是之后的加載/存儲。 我真的想要一個“具有獲取排序的存儲”——在卸載庫之前,必須清除標志並對 ISR 可見(在操作之前不能重新排序加載/存儲) ——但是 C++ 標准似乎暗示這不是至少在正常的獲取/釋放中是不可能的。

由於額外的總排序屬性,我探索了使用 SeqCst 操作/柵欄的想法,但我不確定我是否正確使用它。 檢查 fence 版本的程序集, DMB出現在正確的位置似乎是正確的,但是閱讀關於atomic_thread_fence和 Fence-fence 同步的標准,這似乎在技術上是不正確的用法,因為“在線程 A 中,FA沒有在 X 之前排序”。

有人能夠弄清楚這種用法是否正確(使用 C++ 標准術語,即: https://en.cppreference.com/w/cpp/atomic/memory_order,https ://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;
}

(更多的是討論點而不是規范的答案)

具有后續控制流依賴性的獲取-釋放操作不會解決所有歧義嗎?

像這樣:

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

由於是acquire-release,deloading不能在exchange之前重新排序。 作為獎勵,它可以防止兩個線程嘗試卸載,但不能在卸載仍在進行時防止第二個線程加載。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM