在中断例程中使用C ++对象(和volatile)的正确方法是什么? [英] What is the correct way of using C++ objects (and volatile) inside interrupt routines?

查看:115
本文介绍了在中断例程中使用C ++对象(和volatile)的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用Atmel AVR微控制器(gcc),但希望此答案适用于整个微控制器领域,即通常是单线程但有中断.

I am currently working with Atmel AVR microcontrollers (gcc), but would like the answer to apply to the microcontroller world in general, i.e. usually single-threaded but with interrupts.

我知道在访问可以在ISR中修改的变量时如何在C代码中使用volatile.例如:

I know how to use volatile in C code when accessing a variable that can be modified in an ISR. For example:

uint8_t g_pushIndex = 0;
volatile uint8_t g_popIndex = 0;
uint8_t g_values[QUEUE_SIZE];

void waitForEmptyQueue()
{
    bool isQueueEmpty = false;
    while (!isQueueEmpty)
    {
        // Disable interrupts to ensure atomic access.
        cli();
        isQueueEmpty = (g_pushIndex == g_popIndex);
        sei();
    }
}

ISR(USART_UDRE_vect) // some interrupt routine
{
    // Interrupts are disabled here.
    if (g_pushIndex == g_popIndex)
    {
        usart::stopTransfer();
    }
    else
    {
        uint8_t value = g_values[g_popIndex++];
        g_popIndex &= MASK;
        usart::transmit(value);
    }
}

由于g_popIndex在ISR内被修改并在ISR之外进行访问,因此必须将其声明为volatile,以指示编译器不要优化对该变量的内存访问.请注意,除非我弄错了,否则不必将g_pushIndexg_values声明为volatile,因为它们不会被ISR修改.

Because g_popIndex is modified inside the ISR and accessed outside of the ISR, it must be declared volatile to instruct the compiler not to optimize memory accesses to that variable. Note that, unless I'm mistaken, g_pushIndex and g_values need not be declared volatile, since they are not modified by the ISR.

我想将与队列相关的代码封装在一个类中,以便可以重用:

I want to encapsulate the code related to the queue inside a class, so that it can be reused:

class Queue
{
public:
    Queue()
    : m_pushIndex(0)
    , m_popIndex(0)
    {

    }

    inline bool isEmpty() const
    {
        return (m_pushIndex == m_popIndex);
    }

    inline uint8_t pop()
    {
        uint8_t value = m_values[m_popIndex++];
        m_popIndex &= MASK;
        return value;
    }

    // other useful functions here...

private:
    uint8_t m_pushIndex;
    uint8_t m_popIndex;
    uint8_t m_values[QUEUE_SIZE];
};

Queue g_queue;

void waitForEmptyQueue()
{
    bool isQueueEmpty = false;
    while (!isQueueEmpty)
    {
        // Disable interrupts to ensure atomic access.
        cli();
        isQueueEmpty = g_queue.isEmpty();
        sei();
    }
}

ISR(USART_UDRE_vect) // some interrupt routine
{
    // Interrupts are disabled here.
    if (g_queue.isEmpty())
    {
        usart::stopTransfer();
    }
    else
    {
        usart::transmit(g_queue.pop());
    }
}

上面的代码可以说更具可读性.但是,在这种情况下,应该如何处理volatile?

The code above is arguably more readable. However, what should be done about volatile in this case?

1)是否仍然需要?即使将函数声明为inline,调用方法Queue::isEmpty()是否仍能确保对g_queue.m_popIndex的非优化访问?我不信.我知道编译器使用启发式方法来确定是否不应优化访问,但是我不喜欢依赖这种启发式方法作为一般解决方案.

1) Is it still needed? Does calling the method Queue::isEmpty() somehow ensures non-optimized access to g_queue.m_popIndex, even if the function is declared inline? I doubt that. I know that compilers use heuristics to determine if an access should not be optimized, but I dislike relying on such heuristics as a general solution.

2)我认为一个可行的(有效的)解决方案是在类定义中声明成员Queue::m_popIndex volatile.但是,我不喜欢这种解决方案,因为类Queue的设计者需要确切地知道如何使用它来知道哪个成员变量必须为volatile.随着将来代码的更改,它将无法很好地扩展.而且,所有Queue实例现在都将具有volatile成员,即使未在ISR中使用某些实例.

2) I think a working (and efficient) solution is to declare the member Queue::m_popIndex volatile inside the class definition. However, I dislike this solution, because the designer of class Queue needs to know exactly how it will be used to know which member variable must be volatile. It will not scale well with future code changes. Also, all Queue instances will now have a volatile member, even if some are not used inside an ISR.

3)如果人们将Queue类看作是内置类,我认为自然的解决方案是将全局实例g_queue本身声明为volatile,因为在实例中对其进行了修改. ISR,并在ISR之外访问.但是,这不能很好地工作,因为只能在volatile对象上调用volatile函数.突然,必须将Queue的所有成员函数声明为volatile(而不仅仅是const或ISR内部使用的函数).同样,Queue的设计者如何事先知道呢?同样,这也会惩罚所有Queue用户.仍然可能复制所有成员函数,并且在类中同时具有volatile和non- volatile重载,因此不会损害非volatile用户的利益.不漂亮.

3) If one looks at the Queue class as if it were a built-in, I think the natural solution would be to declare the global instance g_queue itself as volatile, since it is modified in the ISR and accessed outside of the ISR. However, this doesn't work well, because only volatile functions can be called on volatile objects. Suddenly, all member functions of Queue must be declared volatile (not just the const ones or the ones used inside the ISR). Again, how can the designer of Queue know that in advance? Also, this penalize all Queue users. There is still the possibility of duplicating all member functions and having both volatile and non-volatile overloads in the class, so that non-volatile users are not penalized. Not pretty.

4)Queue类可以在策略类上进行模板化,该策略类可以选择仅在需要时将volatile添加到其所有成员变量.同样,班级设计人员需要事先知道,而且解决方案要理解起来更复杂,但是很好.

4) The Queue class could be templated on a policy class that can optionally add volatile to all its member variables only when needed. Again, the class designer need to know that in advance and the solution is more complicated to understand, but oh well.

我很想知道我是否缺少一些更简单的解决方案.附带说明,我在编译时尚不支持C ++ 11/14.

I am curious to know if I am missing some easier solution to this. As a side note, I am compiling with no C++11/14 support (yet).

推荐答案

是的,非常需要内联.
1)编译器通常将内联函数的新副本放置在被调用的每个位置.这种优化似乎不会影响易失性变量.这样就可以了.
2)我第二次将此作为正确的解决方案(带有扩展名).因为您唯一需要可变的变量实际上是队列索引.
3)不,不需要将整个类实例标记为volatile,因为这可能会阻止其他潜在的优化.
4)您可以使用继承.声明一个队列必须具有哪些功能的接口,以及两个继承的类,一个用于ISR(具有可变的队列索引),另一个用于不使用ISR的继承的类.此外,您始终可以将您的课程也定义为模板:

Yes, inline is definetely needed.
1) Compilers generally places a new copy of the inlined function in each place it is called. This optimization seems to not affect volatile variables. So this is OK.
2) I second this as the correct solution(with an extension). Because your only variable that needs to be volatile is really queue index.
3) Nope, no need to mark whole class instance volatile since it may prevent other potential optimizations.
4) You can use inheritance. An interface that declares which functions must a queue have, and two inherited classes for one using with ISR(has queue index volatile) and the other for not using ISR. Additionally, you can always define your class also templated:

template<typename T>
class IQueue
{
public:
        virtual bool isEmpty() const = 0;
        virtual T pop() = 0;
protected:
    uint8_t pushIndex;
    T values[QUEUE_SIZE];
};


template<typename T>
class ISRQueue : public IQueue<T>
{
    volatile uint8_t popIndex;
public:
    inline bool isEmpty()const
    {
        return (pushIndex == popIndex);
    }

    inline T pop()
    {
        T value = values[popIndex++];
        popIndex &= MASK;
        return value;
    }
};

template<typename T>
class Queue : public IQueue<T>
{
    uint8_t popIndex;
public:
    inline bool isEmpty()const
    {
        return (pushIndex == popIndex);
    }

    inline T pop()
    {
        T value = values[popIndex++];
        popIndex &= MASK;
        return value;
    }
};

typedef ISRQueue<uint8_t> ISRQueueUInt;
typedef ISRQueue<uint8_t> QueueUInt;

这篇关于在中断例程中使用C ++对象(和volatile)的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆