我目前正在使用 Atmel AVR 微控制器 (gcc),但希望答案适用于一般微控制器世界,即通常是单线程但带有中断。
我知道如何使用volatile
在 C 代码中访问可在 ISR 中修改的变量时。例如:
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_pushIndex
and g_values
无需声明volatile
,因为它们没有被 ISR 修改。
我想将与队列相关的代码封装在一个类中,以便可以重用:
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
在这种情况下?
1)还需要吗?是否调用该方法Queue::isEmpty()
以某种方式确保非优化访问g_queue.m_popIndex
,即使函数已声明inline
?我不信。我知道编译器使用启发式方法来确定是否不应优化访问,但我不喜欢依赖这种启发式方法作为通用解决方案。
2)我认为一个可行的(并且有效的)解决方案是声明成员Queue::m_popIndex
volatile
在类定义里面。但是,我不喜欢这个解决方案,因为类的设计者Queue
需要确切地知道它将如何使用才能知道哪个成员变量必须是volatile
。它不会随着未来的代码更改而很好地扩展。另外,所有Queue
实例现在将有一个volatile
成员,即使其中一些未在 ISR 中使用。
3)如果有人看Queue
类就好像它是内置的一样,我认为自然的解决方案是声明全局实例g_queue
本身作为volatile
,因为它是在 ISR 中修改并在 ISR 外部访问的。然而,这并不能很好地工作,因为只有volatile
可以调用函数volatile
对象。突然间,所有成员函数Queue
必须声明volatile
(不仅仅是const
或 ISR 内部使用的)。再次,设计师如何Queue
提前知道吗?另外,这对所有Queue
用户。仍然有可能复制所有成员函数并同时拥有volatile
和非volatile
类中的重载,因此非volatile
用户不会受到惩罚。不漂亮。
4) The Queue
类可以在策略类上进行模板化,可以选择添加volatile
仅在需要时才访问其所有成员变量。同样,类设计者需要提前知道这一点,并且解决方案更难以理解,但是哦,好吧。
我很想知道我是否缺少一些更简单的解决方案。附带说明一下,我正在编译(尚不支持 C++11/14)。