热门关键字:  ubuntu  分区  函数  Fedora  linux系统进程

嵌入式编程中一个常见且又不易排除的错误

来源: 作者: 时间:2007-12-13 Tag: 点击:
本文为原创,转载请注明作者和出处。
 
#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)
都进行了初始化.
 
    从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)
 
    假设原来接收了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

最新评论共有 4 位网友发表了评论
发表评论
评论内容:不能超过250字,需审核,请自觉遵守互联网相关政策法规。
用户名: 密码:
匿名?
注册