我有一个案例,需要确保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_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;
}
1条答案
按热度按时间bzzcjhmw1#
(More讨论的要点而非规范的答案)
带有后续控制流依赖项的获取-释放操作难道不能解决所有的歧义吗?
就像这样:
由于它是一个获取-释放,在交换之前不能重新排序减载。作为一个额外的好处,它可以防止两个线程试图减载,但在减载仍在进行时第二个线程无法加载。