本文为原创,转载请注明作者和出处。
#ifndef _RINGBUFFER_HPP_
#define _RINGBUFFER_HPP_ template<class Token>
class RingBuffer
{
public:
// constructors and destructors
RingBuffer(unsigned int iSize):size(iSize), filled(0), in(0), out(0)
{
pBuffer = (Token*) new Token[size];
}
~RingBuffer()
{delete pBuffer;} void add(Token token)
{
pBuffer[in] = token;
in = (in + 1) % size;
++filled;
} // add
Token get()
{
Token token = pBuffer[out];
out = (out + 1) % size;
--filled;
return token;
} // get
private:
unsigned int size; // sizeof buffer
unsigned int filled; // # of objects in the buffer
unsigned int in; // next token is stored here
unsigned int out; // next token is taken from here
Token *pBuffer;
}; // class RingBuffer
上面是一个简化的RingBuffer的C++源码,在构造函数中将构造一个buffer数组,并对size(iSize), filled(0), in(0), out(0)
都进行了初始化.
#define _RINGBUFFER_HPP_ template<class Token>
class RingBuffer
{
public:
// constructors and destructors
RingBuffer(unsigned int iSize):size(iSize), filled(0), in(0), out(0)
{
pBuffer = (Token*) new Token[size];
}
~RingBuffer()
{delete pBuffer;} void add(Token token)
{
pBuffer[in] = token;
in = (in + 1) % size;
++filled;
} // add
Token get()
{
Token token = pBuffer[out];
out = (out + 1) % size;
--filled;
return token;
} // get
private:
unsigned int size; // sizeof buffer
unsigned int filled; // # of objects in the buffer
unsigned int in; // next token is stored here
unsigned int out; // next token is taken from here
Token *pBuffer;
}; // class RingBuffer
都进行了初始化.
从add()和get()两个函数我们可以看出只要buffer不溢出的话,则 filled = in - out;
我的系统利用UART进行通信的,当数据进来的时候则在UART的ISR中调用pRingBuffer->add(bReceivedData);
然后在主程序中建立一个循环,该循环中不断检查RingBuffer中的数据,只要filled!=0,就读取数据并显示:
if(pCOMMUNICATION->IsUartBufferEmpty() == false)
{
alt_u8 bData = pCOMMUNICATION->GetData();
}
但是在调试的过程中发现老是出现错误,也就是出现filled != in - out;的情况.
经过一段时间的调试终于发现了问题的根源:filled是个全局变量,在UART的ISR中通过add()对它进行访问,而在主程序中也通过get()对它进行访问,这样就发生了竞争,从而导致程序出错.下面我们就来剖析一下竞争的过程.
首先我们来剖析get()函数中filled--的反汇编代码(基于altera的NiosII-32位处理器)
ldw r2,0(fp) //将filled变量赋予r2 ----------- (1)
addi r2,r2,-1 //r2 - 1 -> filled-- ----------- (2)
stw r2,0(fp) //将r2的值回写入filled ----------- (3)
stw r2,0(fp) //将r2的值回写入filled ----------- (3)
假设原来接收了10个数据并且还没来得及读出来显示,即filled = 10, in = 10, out = 0;
此时main函数正好调用了get()函数进行读数操作,并且执行完filled--的第(1)步,即r2 = filled = 10;
接下来要执行第(2)步,可是就在这时候发生了UART中断,则中断函数调用了add()函数,执行结果是filled = 11, in = 11
中断返回原来的被中断点,开始执行filled--的第(2)步,由于原来r2 = 10, 则执行完第(2)步后, r2 = 9
接下来执行filled--的第(3)步,则filled = r2 = 9 [color=Red]就是这次的赋值把中断产生的新的filled的值给覆盖掉了[/color]
而执行完get()函数的结果是 out = 1
这时候我们再来看看filled,in,out三个变量的关系,
(in - out = 11 - 1 = 10) != (filled = 9)
瞧见没有,bug乖乖现身了^_^
此错误是由于在ISR和ISR外函数中同时对全局变量进行写操作所导致的,只要在ISR外的函数对该全局变量的写操作进行保护(关中断)就可以避免这样的bug.
引申1:对于嵌入式系统,每一个IO口都可以引申为一个全局变量,所以对IO口的操作也同样要注意上述的问题.
引申2:在8位处理器上,写入32位浮点值可能需要四条指令,在16位处理器上可能需要两条指令。此时假设上面的filled为32位的整数,则执行filled--的时候,其反汇编代码的第(3)步在8位的处理器上将需要4步才能实现:假设filled的内存空间为0x00,0x01,0x02,0x03,则每步只更新一个地址的数据,如果更新到0x00/0x01/0x02的时候发生中断,则即使ISR中只对filled进行读操作也会发生不可预知的错误.
总结:(详细请参考转载的文献:嵌入式软件设计中查找缺陷的几个技巧)
当两个或更多独立线程(ISR与main可以理解为两个独立线程)同时访问同一资源(例如IO口或者全局变量)时,就出现了竞争。
解决方案:
* 如果一个ISR对共享数据进行写入,则该ISR之外的每次可中断的读操作都必须予以保护。
* 如果一个ISR对共享数据进行写入,则该ISR之外的任何读-修-写操作都必须予以保护。
* 如果一个ISR读取共享数据,则对该数据的可中断写操作必须予以保护。
* 如果一个ISR和其它代码都要检查一个硬件状态标志,以便在使用某资源之前确定其可用性,如:
if (!resource_busy)
{
// Use resource
}
则从检查标志之时开始,到硬件设置标志表示资源不可用为止,必须采取保护措施。
from:http://blog.chinaunix.net/u1/34012/showart_440922.html
