【问题标题】:Using Methods of Volatile Standard Template Objects使用易失标准模板对象的方法
【发布时间】:2019-08-14 17:29:50
【问题描述】:

我现在希望在 Teensy 3.6 微控制器上的基于定时器的中断中运行一些代码。该代码访问一个类的 [global] 对象数组。我已将该数组和所有成员变量标记为 volatile,我相信这是正确处理中断的第一步。

我标记为 volatile 的成员变量之一是 std::bitset,我想将其称为非易失性方法,我不能这样做

"passing 'volatile std::bitset<16u>' as 'this' argument discards qualifiers [-fpermissive]"

我认为我可以复制 bitset 库并将所有内容切换到 volatile,但我认为这不是必需的,所以我认为要么有更好的解决方案,要么我的想法不正确。

请告诉我应该怎么做。

这些答案似乎建议在 ISR 中访问全局变量时使用 volatile: C 'Volatile' keyword in ISR and multithreaded program?,

Why is volatile needed in C?,

What is the correct way of using C++ objects (and volatile) inside interrupt routines?,

Is volatile needed when variable is only read during interrupt

volatile keyword usage in ISR function in micro-controller programing

这是许多外部资源推荐使用的补充。也许我的原始信息不清楚,或者我的情况与这些不同。

【问题讨论】:

  • 你希望volatile 应该做什么?
  • 您只需要 volatile 在写入和读取诸如 pin 之类的东西和其他被认为有副作用的东西时使用。
  • 扩展这个“正确处理中断”的概念。我不认为volatile 做你认为它做的事,或者如果它做了,你将成为 SOL
  • 实际上,C++ 在嵌入式系统中变得越来越流行。见embedded.com/design/programming-languages-and-tools/4438660/…
  • 非常小心volatile。大多数人不明白它实际上做了什么,它做了什么很少是他们想要的。几乎在所有情况下,使用volatile 都是一个错误,而您实际上还想使用其他东西。最重要的是; volatile Not 是否意味着“线程安全”。它还将有效地禁用您的编译器优化器。另见:git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/…en.cppreference.com/w/cpp/language/cv

标签: c++ arduino interrupt volatile c++-standard-library


【解决方案1】:

您不应该将所有内容都设置为易失性。 Volatile 有一个特定的目的,那就是防止编译器优化对内存的读取和写入。让我们看一个非常简单的例子。

int regular_sum(int* ptr) {
    int a = *ptr;
    int b = *ptr;
    return a + b;
}
int volatile_sum(int volatile* ptr) {
    int a = *ptr;
    int b = *ptr;
    return a + b; 
}

当我们查看程序集时,我们看到在regular_sum 中,编译器意识到您要取消引用同一个指针两次,并将其优化为仅取消引用一次。但是在volatile_sum 中,编译器会插入两个取消引用:

regular_sum(int*):
        mov     eax, DWORD PTR [rdi]
        add     eax, eax
        ret
volatile_sum(int volatile*):
        mov     eax, DWORD PTR [rdi]
        mov     edx, DWORD PTR [rdi]
        add     eax, edx
        ret

优化很好,而且大多数时候,您不需要使用 volatile。如果您正在执行内存映射 IO,或者您正在将值写入引脚,就好像它们是指针一样,这就是您使用 volatile 的地方。重申 Nathan Oliver 所说的话,

您只需要在硬件可以更改变量值的变量上使用 volatile,因为编译器无法知道这一点。这就是 volatile 的用途,让编译器知道这是一个特殊变量,可以以它不知道的方式进行更改。如果硬件不能改变你的价值,那么你就不需要 volatile。

但是,如果您要对一个对象进行计算,不要使用 volatile。对普通对象进行计算,然后将结果复制到 volatile 指针。

易失性和中断服务例程。

在可能被中断服务例程修改的全局变量上使用volatile 是合适的。话虽如此,volatile 不能与std::bitset 之类的对象一起使用,因为std::bitset 不支持易失性操作,并且std::bitset 不是可轻易复制的。

对此,您有两种选择:

  • 使用包含易失原语的容器(例如,std::vector&lt;volatile bool&gt;
  • 编写您自己的类,支持 volatile。

如果你有一个类可以简单复制的,那么你可以做如下的事情。首先,我们必须定义函数以允许我们在 volatile 类型之间进行复制:

template<class T>
T volatile_copy(T const volatile& source) {
    static_assert(std::is_trivially_copyable_v<T>, "Input must be trivially copyable");
    T dest;
    auto* dest_ptr = dynamic_cast<char*>(&dest);
    auto* source_ptr = dynamic_cast<char const volatile*>(&source);

    for(int i = 0; i < sizeof(T); i++) {
        dest_ptr[i] = source_ptr[i];
    }

    return dest;
}

template<class T>
void volatile_assign(T volatile& dest, T const& source) {
    static_assert(std::is_trivially_copyable_v<T>, "Input must be trivially copyable");
    auto* source_ptr = dynamic_cast<char*>(&source);
    auto* dest_ptr   = dynamic_cast<char volatile*>(&dest);

    for(int i = 0; i < sizeof(T); i++) {
        dest_ptr[i] = source_ptr[i];
    }
}

然后,我们可以正常编写一个类,只要它是可简单复制的,我们就可以从 volatile 版本创建一个副本:

struct MyBitset {
    uint64_t bits;
    // Logic

    void flip() {
        bits = ~bits;
    }
    void addOne() {
        bits++;
    }
};

volatile MyBitset flags;

void interrupt_handler() {
    auto local = volatile_copy(flags);

    // Do stuff to local

    volatile_assign(flags, local); 
};

我们也可以将这种行为封装在一个类中,这样我们就可以“检出”易失性变量:

template<class T>
struct Checkout {
    T local;
    T volatile& source;
    Checkout(T volatile& source)
      : local(volatile_copy(source))
      , source(source) {}
    void save() {
        volatile_assign(source, local);
    }
    ~Checkout() {
        save();
    }
};

使用它可以创建volatile变量的本地副本,对其进行修改,结果将自动保存:

volatile MyBitset flags;

void interrupt_handler() {
    auto f = Checkout(::flags);

    f.local.flip(); //We can call whatever member functions we want on the local

    // When the function exits, changes made to the local are automatically assigned to the volatile global
}

【讨论】:

  • 几乎所有其他地方都说在 ISR 中访问/写入的全局变量需要标记为 volatile。我不明白我的情况有何不同。
  • 你能指定:无处不在吗?我怀疑你有一些文章或书籍?
  • be aware that the C++11 ISO Standard volatile keyword is different and is supported in MSVC when the /volatile:iso compiler option is specified -> Microsoft 默认不符合标准
  • Volatile 仅在您将地址映射到要写入的硬件时才有意义,所有其他情况都是错误
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-09
相关资源
最近更新 更多