[英]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.