环形缓冲区_串口环形缓冲区 - CSDN
精华内容
参与话题
  • 环形缓冲区C语言实现

    万次阅读 多人点赞 2020-01-10 15:42:49
    环形缓冲区 1. 环形缓冲区的特性 1、先进新出 2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据 现实中的存储介质都是线性的,因此我们需要做一下处理,才能在功能上实现...

    环形缓冲区

    理想环形缓冲区

    1. 环形缓冲区的特性
      1、先进新出
      2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据
      现实中的存储介质都是线性的,因此我们需要做一下处理,才能在功能上实现环形缓冲区
      这里写图片描述
      算法说明:
      1、pHead和pTail分别是连续存储介质的首地址和尾地址
      2、pTail - pHead 的值是环形缓冲区的总长度
      3、pValid 是使用区域的起始指针,取数据时的起点,当取数据时pValid要发生偏移
      4、pValidTail 是使用区域的的结尾指针,存数据时的起点,当存数据时,pValidTail要发生偏移
      5、现有长度为addLen字节要存入,当pValidTail + addLen > pTail 时(超出了缓冲区,这时就要绕到开头pHead)
      int len1 = pTail - pValidTail;
      int len2 = addLen - len1;
      pValidTail = pHead + len2;//新的使用区的尾指针
      6、判断总长度是否变更,即是否有数据覆盖pValid所指向的区域,如果有,要偏移pValid
    • 下面是已验证的代码
      ringBuffer.c:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include "ringBuffer.h"
    #define BUFFER_SIZE  16   //缓冲区的长度,可以修改
    
    static u32 validLen;//已使用的数据长度
    static u8* pHead = NULL;//环形存储区的首地址
    static u8* pTail = NULL;//环形存储区的结尾地址
    static u8* pValid = NULL;//已使用的缓冲区的首地址
    static u8* pValidTail = NULL;//已使用的缓冲区的尾地址
    
    /*
     * 初始化环形缓冲区
     * 环形缓冲区这里可以是malloc申请的内存,也可以是Flash存储介质
     * */
    void initRingbuffer(void)
    {
    	if(pHead == NULL)
    	{
    		pHead = (u8*) malloc(BUFFER_SIZE);
    	}
    	pValid = pValidTail = pHead;
    	pTail = pHead + BUFFER_SIZE;
    	validLen = 0;
    }
    
    /*
     * function:向缓冲区中写入数据
     * param:@buffer 写入的数据指针
     * 		 @addLen 写入的数据长度
     * return:-1:写入长度过大
     * 		  -2:缓冲区没有初始化
     * */
    int wirteRingbuffer(u8* buffer,u32 addLen)
    {
    	if(addLen > BUFFER_SIZE) return -2;
    	if(pHead==NULL) return -1;
    	assert(buffer);
    
    	//将要存入的数据copy到pValidTail处
    	if(pValidTail + addLen > pTail)//需要分成两段copy
    	{
    		int len1 = pTail - pValidTail;
    		int len2 = addLen - len1;
    		memcpy( pValidTail, buffer, len1);
    		memcpy( pHead, buffer + len1, len2);
    		pValidTail = pHead + len2;//新的有效数据区结尾指针
    	}else
    	{
    		memcpy( pValidTail, buffer, addLen);
    		pValidTail += addLen;//新的有效数据区结尾指针
    	}
    
    	//需重新计算已使用区的起始位置
    	if(validLen + addLen > BUFFER_SIZE)
    	{
    		int moveLen = validLen + addLen - BUFFER_SIZE;//有效指针将要移动的长度
    		if(pValid + moveLen > pTail)//需要分成两段计算
    		{
    			int len1 = pTail - pValid;
    			int len2 = moveLen - len1;
    			pValid = pHead + len2;
    		}else
    		{
    			pValid = pValid + moveLen;
    		}
    		validLen = BUFFER_SIZE;
    	}else
    	{
    		validLen += addLen;
    	}
    
    	return 0;
    }
    
    /*
     * function:从缓冲区内取出数据
     * param   :@buffer:接受读取数据的buffer
     *		    @len:将要读取的数据的长度
     * return  :-1:没有初始化
     *	 	    >0:实际读取的长度
     * */
    int readRingbuffer(u8* buffer,u32 len)
    {
    	if(pHead==NULL) return -1;
    
    	assert(buffer);
    
    	if(validLen ==0) return 0;
    
    	if( len > validLen) len = validLen;
    
    	if(pValid + len > pTail)//需要分成两段copy
    	{
    		int len1 = pTail - pValid;
    		int len2 = len - len1;
    		memcpy( buffer, pValid, len1);//第一段
    		memcpy( buffer+len1, pHead, len2);//第二段,绕到整个存储区的开头
    		pValid = pHead + len2;//更新已使用缓冲区的起始
    	}else
    	{
    		memcpy( buffer, pValid, len);
    		pValid = pValid +len;//更新已使用缓冲区的起始
    	}
    	validLen -= len;//更新已使用缓冲区的长度
    
    	return len;
    }
    
    /*
     * function:获取已使用缓冲区的长度
     * return  :已使用的buffer长度
     * */
    u32 getRingbufferValidLen(void)
    {
    	return validLen;
    }
    
    /*
     * function:释放环形缓冲区
     * */
    void releaseRingbuffer(void)
    {
    	if(pHead!=NULL) free(pHead);
    	pHead = NULL;
    }
    

    ringBuffer.h

    #ifndef RINGBUFFER_H_
    #define RINGBUFFER_H_
    typedef unsigned char u8;
    typedef unsigned int u32;
    
    void initRingbuffer(void);
    int wirteRingbuffer(u8* buffer,u32 len);
    int readRingbuffer(u8* buffer,u32 len);
    u32 getRingbufferValidLen(void);
    void releaseRingbuffer(void);
    
    #endif /* RINGBUFFER_H_ */
    

    测试 main 函数

    #include <stdio.h>
    #include <stdlib.h>
    #include "ringBuffer.h"
    // 主函数
    int main()
    {
    	char c;
    	int readLen;
    	u8 readBuffer[10];
    	//setvbuf(stdout,NULL,_IONBF,0); //pinrtf、putchar不能立马输出,打开此注释
    	initRingbuffer();
    
    	printf("Please enter a line [blank line to terminate]> ");
    	do{
    		c=getchar();
    		putchar(c);
    		switch(c)
    		{
    			case 'Q':
    				goto exit;
    			break;
    			case 'R':
    				readLen = readRingbuffer(readBuffer,10);
    				printf("readRingbuffer len:%d\n",readLen);
    				if(readLen > 0){
    					printf("readRingbuffer:");
    					for(int i=0;i<readLen;i++){
    						printf("%c ",(char)readBuffer[i]);
    					}
    					printf("\n");
    				}
    			break;
    			default :
    				if(c!='\n') wirteRingbuffer((u8*)&c,1);
    			break;
    		}
    	}while (1);
    
    
    
    exit:
    	releaseRingbuffer();
    	printf("exit.\n");
        return 0;
    }
    

    单片机开发

    **注意:请尊重原创者的辛劳,转载请注明


    展开全文
  • 环形缓冲区的实现

    千次阅读 2018-10-01 10:14:49
    队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),在另一端进行删除(出队)。 队列的特点   ...类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的...

    转载:https://blog.csdn.net/p23onzq/article/details/80750745

    队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),在另一端进行删除(出队)。


    队列的特点

     

    640?wx_fmt=png

     

    640?wx_fmt=png类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的人只能后买到票

    640?wx_fmt=png队列的常见两种形式

     

    640?wx_fmt=png

     

    640?wx_fmt=png普通队列

     

     

    640?wx_fmt=png

     

    在计算机中,每个信息都是存储在存储单元中的,比喻一下吧,上图的一些小正方形格子就是一个个存储单元,你可以理解为常见的数组,存放我们一个个的信息。

     

    当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如过要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。

    640?wx_fmt=png环形队列

     

    640?wx_fmt=png

     

    它的队列就是一个环,它避免了普通队列的缺点,就是有点难理解而已,其实它就是一个队列,一样有队列头,队列尾,一样是先进先出(FIFO)。我们采用顺时针的方式来对队列进行排序。

     

    队列头 (Head) :允许进行删除的一端称为队首。队列尾 (Tail) :允许进行插入的一端称为队尾。

     

    环形队列的实现:在计算机中,也是没有环形的内存的,只不过是我们将顺序的内存处理过,让某一段内存形成环形,使他们首尾相连,简单来说,这其实就是一个数组,只不过有两个指针,一个指向列队头,一个指向列队尾。指向列队头的指针(Head)是缓冲区可读的数据,指向列队尾的指针(Tail)是缓冲区可写的数据,通过移动这两个指针(Head) &(Tail)即可对缓冲区的数据进行读写操作了,直到缓冲区已满(头尾相接),将数据处理完,可以释放掉数据,又可以进行存储新的数据了。

     

    实现的原理:初始化的时候,列队头与列队尾都指向0,当有数据存储的时候,数据存储在‘0’的地址空间,列队尾指向下一个可以存储数据的地方‘1’,再有数据来的时候,存储数据到地址‘1’,然后队列尾指向下一个地址‘2’。当数据要进行处理的时候,肯定是先处理‘0’空间的数据,也就是列队头的数据,处理完了数据,‘0’地址空间的数据进行释放掉,列队头指向下一个可以处理数据的地址‘1’。从而实现整个环形缓冲区的数据读写。

     

    640?wx_fmt=png

     

    看图,队列头就是指向已经存储的数据,并且这个数据是待处理的。下一个CPU处理的数据就是1;而队列尾则指向可以进行写数据的地址。当1处理了,就会把1释放掉。并且把队列头指向2。当写入了一个数据6,那么队列尾的指针就会指向下一个可以写的地址。

     

    640?wx_fmt=png

     

    640?wx_fmt=png从队列到串口缓冲区的实现

     

    串口环形缓冲区收发:在很多入门级教程中,我们知道的串口收发都是:接收一个数据,触发中断,然后把数据发回来。这种处理方式是没有缓冲的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据,那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的程序是一个致命的创伤。

     

    那么如何避免这种情况的发生呢,很显然,上面说的一些队列的特性很容易帮我们实现我们需要的情况。将接受的数据缓存一下,让处理的速度有些许缓冲,使得处理的速度赶得上接收的速度,上面又已经分析了普通队列与环形队列的优劣了,那么我们肯定是用环形队列来进行实现了。下面就是代码的实现:

     

    ①定义一个结构体:

     

    typedef struct
    {
        u16 Head;           
        u16 Tail;
        u16 Lenght;
        u8 Ring_Buff[RINGBUFF_LEN];
    }RingBuff_t;
    RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
    

     

    ②初始化结构体相关信息:使得我们的环形缓冲区是头尾相连的,并且里面没有数据,也就是空的队列。

     

     /**
     * @brief  RingBuff_Init
     * @param  void
     * @return void
     * @author 杰杰
     * @date   2018
     * @version v1.0
     * @note   初始化环形缓冲区
     */
    void RingBuff_Init(void)
    {
       //初始化相关信息
       ringBuff.Head = 0;
       ringBuff.Tail = 0;
       ringBuff.Lenght = 0;
    }
    

     

    初始化效果如下:

    640?wx_fmt=png

     

    写入环形缓冲区的代码实现:

     

     /**
     * @brief  Write_RingBuff
     * @param  u8 data
     * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
     * @author 杰杰
     * @date   2018
     * @version v1.0
     * @note   往环形缓冲区写入u8类型的数据
     */
    u8 Write_RingBuff(u8 data)
    {
       if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满
        {
          return FLASE;
        }
        ringBuff.Ring_Buff[ringBuff.Tail]=data;
    //    ringBuff.Tail++;
        ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
        ringBuff.Lenght++;
        return TRUE;
    }

     

    读取缓冲区的数据的代码实现:

     

     /**
     * @brief  Read_RingBuff
     * @param  u8 *rData,用于保存读取的数据
     * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功
     * @author 杰杰
     * @date   2018
     * @version v1.0
     * @note   从环形缓冲区读取一个u8类型的数据
     */
    u8 Read_RingBuff(u8 *rData)
    {
       if(ringBuff.Lenght == 0)//判断非空
        {
           return FLASE;
        }
      *rData = ringBuff.Ring_Buff[ringBuff.Head];//先进先出FIFO,从缓冲区头出
    //   ringBuff.Head++;
       ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
       ringBuff.Lenght--;
       return TRUE;
    }

    640?wx_fmt=png

    对于读写操作需要注意的地方有两个:

    1:判断队列是否为空或者满,如果空的话,是不允许读取数据的,返回FLASE。如果是满的话,也是不允许写入数据的,避免将已有数据覆盖掉。那么如果处理的速度赶不上接收的速度,可以适当增大缓冲区的大小,用空间换取时间。

    2:防止指针越界非法访问,程序有说明,需要使用者对整个缓冲区的大小进行把握。

    640?wx_fmt=png

    那么在串口接收函数中:

     

    void USART1_IRQHandler(void)   
    {
       if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
                       {
               USART_ClearITPendingBit(USART1,USART_IT_RXNE);       //清楚标志位
              Write_RingBuff(USART_ReceiveData(USART1));      //读取接收到的数据
           }
    }
    

     

    640?wx_fmt=png测试效果

    640?wx_fmt=png

    测试数据没有发生丢包现象。

    640?wx_fmt=png补充

      对于现在的阶段,杰杰我本人写代码也慢慢学会规范了。所有的代码片段均使用了可读性很强的,还有可移植性也很强的。我使用了宏定义来决定是否开启环形缓冲区的方式来收发数据,移植到大家的代码并不会有其他副作用,只需要开启宏定义即可使用了。

     

     #define USER_RINGBUFF  1  //使用环形缓冲区形式接收数据
     #if  USER_RINGBUFF
     /**如果使用环形缓冲形式接收串口数据***/
     #define  RINGBUFF_LEN          200     //定义最大接收字节数 200
     #define  FLASE   1 
     #define  TRUE    0 
     void RingBuff_Init(void);
     u8 Write_RingBuff(u8 data);
     u8 Read_RingBuff(u8 *rData);
    #endif
    

     

    当然,我们完全可以用空闲中断与DMA传输,效率更高,但是某些单片机没有空闲中断与DMA,那么这种环形缓冲区的作用就很大了,并且移植简便。

     

    展开全文
  • 关于环形缓冲区的用法和理解

    千次阅读 2020-08-11 11:49:05
    接下来谈谈常用的环形缓冲区的特点和使用方法: 说到环形缓冲区,相信大家都不会陌生,这里就不贴图了,百度都有一大把图片,那就直接就贴代码说明吧。 /*------------------------------------------------------...

    通信中,经常会遇到数据交互的问题。为了保证数据的高效处理和解析,通常会有------缓冲区的说法。接下来谈谈常用的环形缓冲区的特点和使用方法:

    说到环形缓冲区,相信大家都不会陌生,这里就不贴图了,百度都有一大把图片,那就直接就贴代码说明吧。

    /*------------------------------------------------------------------------------------------------------------------
    Macros
    */
    #define RING_FIFO_DEPTH   2048      /*Ring fifo depth*/ //缓冲区大小必须做成 2^n ,看看读写位操作的巧妙地方
    
    /*------------------------------------------------------------------------------------------------------------------
    Variables
    */
    static unsigned char ringBuf[RING_FIFO_DEPTH];    //初始化缓冲区 
    static unsigned int ring_wr_pt = 0;               //写指针计数
    static unsigned int ring_rd_pt = 0;               //读指针计数
    
    
    /*------------------------------------------------------------------------------------------------------------------
    Functions
    */
    
    /*
    ********************************************************************************************************************
    @ Name   : void ring_buf_clear(void)
    
    @ Brief     : 清空缓冲区
    
    @ Author  : L17
    ********************************************************************************************************************
    */
    void ring_buf_clear(void)
    {
        ring_wr_pt = 0;
        ring_rd_pt = 0;
        memset(ringBuf, 0, RING_FIFO_DEPTH);
    }
    
    /*
    ********************************************************************************************************************
    @ Name   : unsigned char read_ring_buf(void)
    
    @ Brief     : 读缓冲区数据,每读一个,默认已经处理当前数据
    
    @ Author  : L17
    ********************************************************************************************************************
    */
    unsigned char read_ringBuf(void)
    {    
        unsigned char data = ringBuf[(ring_rd_pt++)&(RING_FIFO_DEPTH-1)];
        return data;
    }
    
    /*
    ********************************************************************************************************************
    @ Name   : void write_ringBuf(int data)
    
    @ Brief     : 写缓冲区
    
    @ Author  : L17
    ********************************************************************************************************************
    */
    
    unsigned char write_ringBuf(unsigned char data)
    {
        if(get_ringBuf_count() > RING_FIFO_DEPTH)
        {
        return 1;  /*写满*/
        }
        else
      {
            ringBuf[ring_wr_pt & (RING_FIFO_DEPTH - 1)] = data;
            ring_wr_pt++;        
        }
    
        return 0;
    }
    
    /*
    ********************************************************************************************************************
    @ Name   : unsigned int get_ringBuf_count(void)
    
    @ Brief  : 获取环形缓冲区数据当前有效数据长度
    
    @ Author : L17
    ********************************************************************************************************************
    */
    unsigned int get_ringBuf_count(void)
    {
        unsigned int data_size;
        data_size = ring_wr_pt - ring_rd_pt;
        return data_size;
    }
    
    /*
    ********************************************************************************************************************
    @ Name   : unsigned int get_ringBuf_space(void)
    
    @ Brief     : 获取环形缓冲区剩余有效空间大小
    
    @ Author  : L17
    ********************************************************************************************************************
    */
    unsigned int get_ringBuf_space(void)
    {
        unsigned int data_size = ring_wr_pt - ring_rd_pt;
        return RING_FIFO_DEPTH - data_size - 1;
    }
    
    /*
    ********************************************************************************************************************
    @ Name   : void read_ringBuf_num(unsigned char *buf,unsigned int len)
    
    @ Brief     : 读缓冲区一定数量的数据
    
    @ Author  : L17
    ********************************************************************************************************************
    */
    void read_ringBuf_num(unsigned char *buf,unsigned int len)
    {
        if (len)
        {
            unsigned int count = get_ringBuf_count();
            if (len > count)
            {
                return;
            }
    
            while (len--)
            {
                *buf++ = ringBuf[ring_rd_pt & (RING_FIFO_DEPTH - 1)];
                ring_rd_pt++;
            }
        }
    }

     

    --------------------------------------------------------------------------------------------------------------------------------------

    注意:缓冲区大小强烈建议 采用2的n次方来选择,这样便于计算和操作

    在接收数据中,通常都是在中断中执行,中断建议只做接收: write_ringBuf(data),不做其他多余的逻辑和应用处理,这样能保证中断的快速响应,也能使系统更稳定。处理内容就放在大循环处理,处理前只需要使用:

     if (get_ringBuf_count())   //判断是否有有效数据
    {

      /*处理内容*/

    }

    当然具体情况具体分析,若使用了操作系统,情况就要稍微复杂点了,需要考虑收发线程的互斥关系等等,这里就不贴代码了。

    总结:处理好读和写(在写某块内存未操作完成,又去读的情况)

     

     

    展开全文
  • 环形缓冲区

    万次阅读 2018-10-20 15:11:46
    .... 正在造轮子,同时不停地在找哪里有这个轮子 原因: 当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释...

    https://blog.csdn.net/p23onzq/article/details/80750745

    .https://blog.csdn.net/blade2001/article/details/7094232/

    正在造轮子,同时不停地在找哪里有这个轮子

    原因:

    当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如过要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。

    640?wx_fmt=png环形队列

    目的:避免频繁的内存创建取消、分配。内存一直只用了一块。

     

    640?wx_fmt=png

     

    在发送线程使用的是普通队列。在发送任务处理的事件循环线程用的是环形队列。

    即 一个环形缓冲区.

     

     

    消息队列解决了 锁调用太频繁的问题,另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频繁的内存分配不但增加了系统开销,更使得内存碎片不断增多,非常不利于我们的服务器长期稳定运行。也许我们可以使用内存池,比如SGI STL中附带的小内存分配器。但是对于这种按照严格的先进先出顺序处理的,块大小并不算小的,而且块大小也并不统一的内存分配情况来说,更多使用的是一种叫做环形缓冲区的方案,

    按照严格的先进先出顺序进行处理,这是环形缓冲区的使用必须遵守的一项要求。也就是,大家都得遵守规定,追的人不能从桌子上跨过去,跑的人当然也不允许反过来跑。至于为什么,不需要多做解释了吧。

      环形缓冲区是一项很好的技术,不用频繁的分配内存,而且在大多数情况下,内存的反复使用也使得我们能用更少的内存块做更多的事。
     

      在网络IO线程中,我们会为每一个连接都准备一个环形缓冲区,用于临时存放接收到的数据,以应付半包及粘包的情况。在解包及解密完成后,我们会将这个数据包复制到逻辑线程消息队列中,如果我们只使用一个队列,那这里也将会是个环形缓冲区,IO线程往里写,逻辑线程在后面读,互相追逐。

    在通信程序中,经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。

    1、环形缓冲区的实现原理

    环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。

    图1、图2和图3是一个环形缓冲区的运行示意图。图1是环形缓冲区的初始状态,可以看到读指针和写指针都指向第一个缓冲区处;图2是向环形缓冲区中添加了一个数据后的情况,可以看到写指针已经移动到数据块2的位置,而读指针没有移动;图3是环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。

    环形缓冲区所有的push和pop操作都是在一个固定 的存储空间内进行。而队列缓冲区在push的时候,可能会分配存储空间用于存储新元素;在pop时,可能会释放废弃元素的存储空间。所以环形方式相比队列方式,少掉了对于缓冲区元素所用存储空间的分配、释放。这是环形缓冲区的一个主要优势。

    使用链表的方式,正好和数组相反。链表省去了头尾相连的特殊处理。但是链表在初始化的时候比较繁琐,而且在有些场合(比如后面提到的跨进程的IPC)不太方便使用。
      ◇读写操作
      环形缓冲区要维护两个索引,分别对应写入端(W)和读取端(R)。写入(push)的时候,先确保环没满,然后把数据复制到W所对应的元素,最后W指向下一个元素;读取(pop)的时候,先确保环没空,然后返回R对应的元素,最后R指向下一个元素。
      ◇判断“空”和“满”
      上述的操作并不复杂,不过有一个小小的麻烦:空环和满环的时候,R和W都指向同一个位置!这样就无法判断到底是“空”还是“满”。大体上有两种方法可以解决该问题。
      办法1:始终保持一个元素不用
      当空环的时候,R和W重叠。当W比R跑得快,追到距离R还有一个元素间隔的时候,就认为环已经满。当环内元素占用的存储空间较大的时候,这种办法显得很土(浪费空间)。
      办法2:维护额外变量
      如果不喜欢上述办法,还可以采用额外的变量来解决。比如可以用一个整数记录当前环中已经保存的元素个数(该整数>=0)。当R和W重叠的时候,通过该变量就可以知道是“空”还是“满”。
      ◇元素的存储
      由于环形缓冲区本身就是要降低存储空间分配的开销,因此缓冲区中元素的类型要选好。尽量存储值 类型的数据,而不要存储指针(引用) 类型的数据。因为指针类型的数据又会引起存储空间(比如堆内存)的分配和释放,使得环形缓冲区的效果打折扣。
     

    一个例子“”

     char RecvBuf[128 * 1024];   //接收缓冲区,读写索引方式   
     char SendBuf[MAX_SEND_CELL_NUM * sizeof(SendCell)];  //发送缓冲区,自定格式,每个节点存储 缓冲位置,缓冲长度,读取位置     
            size_t  SendReadIndex;    //环形数组
            size_t  SendWriteIndex;        
            size_t RecvReadIndex;
            size_t RecvWriteIndex;
            
            static const size_t MAX_SEND_CELL_NUM = 1536;
             
           
            size_t  TotalSendCellNum;
            /* 环形缓冲区的地址编号计算函数,如果到达唤醒缓冲区的尾部,将绕回到头部。
    
    环形缓冲区的有效地址编号为:0到(NMAX-1)
    
    */
     static void NextSendIndex(size_t& pos)
            {
                if(++pos >= MAX_SEND_CELL_NUM)
                    pos = 0;
                return;
            }

    接收到数据时需要做一个缓存的,因为处理的可能会很慢。

    串口环形缓冲区收发:在很多入门级教程中,我们知道的串口收发都是:接收一个数据,触发中断,然后把数据发回来。这种处理方式是没有缓冲的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据,那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的程序是一个致命的创伤。

     

    那么如何避免这种情况的发生呢,很显然,上面说的一些队列的特性很容易帮我们实现我们需要的情况。将接受的数据缓存一下,让处理的速度有些许缓冲,使得处理的速度赶得上接收的速度,上面又已经分析了普通队列与环形队列的优劣了,那么我们肯定是用环形队列来进行实现了。下面就是代码的实现:

     

     

    #define CIRCLEBUFFER_SIZE    8
    unsigned char CircleBuffer[CIRCLEBUFFER_SIZE];
    unsigned char WriteIndex = 0;
    unsigned char ReadIndex = 0;
    unsigned char LeftSize = 0;
    
    //检查是否有到数组末尾了
    unsigned char Check_CircleBuffer(unsigned char i)
    {
        return(i + 1) ==  CIRCLEBUFFER_SIZE?0:i+1; 
    }
    
    //从环形数组里读取数据
    unsigned char Read_CircleBuffer_Data(void)
    {
        unsigned char Pos;
        if(LeftSize > 0)
        {
            Pos = ReadIndex;
            ReadIndex = Check_CircleBuffer(ReadIndex);
            LeftSize--;
            return CircleBuffer[Pos];
        }
        return 0;
    }
    
    //往环形数组内写入数据
    void Write_CircleBuffer_Data(unsigned char Data)
    {
        if(LeftSize < CIRCLEBUFFER_SIZE)
        {
            CircleBuffer[WriteIndex] = Data;
            WriteIndex = Check_CircleBuffer(WriteIndex);
            LeftSize++;
        }
    }
    
    void main(void)
    {
        Write_CircleBuffer_Data(1);
        Read_CircleBuffer_Data();
    }

     

    展开全文
  • C语言实现环形缓冲区

    千次阅读 2018-01-02 11:11:53
    简述环形缓冲区可以把它的读出端(以下简称R)和写入端(以下简称W)想象成是两个人在体育场跑道上追逐(R追W)。当R追上W的时候,就是缓冲区为空;当W追上R的时候(W比R多跑一圈),就是缓冲区满。  为了形象起见...
  • 环形缓冲区的实现原理(ring buffer)

    万次阅读 2011-12-22 09:46:28
    消息队列锁调用太频繁的问题算是解决了,另一个让人有些苦恼...但是对于这种按照严格的先进先出顺序处理的,块大小并不算小的,而且块大小也并不统一的内存分配情况来说,更多使用的是一种叫做环形缓冲区的方案,mango
  • STM32进阶之串口环形缓冲区实现

    万次阅读 多人点赞 2019-10-15 22:22:22
    如需转载请说明出处:STM32进阶之串口环形缓冲区实现 队列的概念 在此之前,我们来回顾一下队列的基本概念: 队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),...
  • 环形缓冲区的应用ringbuffer

    万次阅读 2016-07-07 19:36:36
    在通信程序中,经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。 1、环形缓冲区的实现原理 环形缓冲区通常有一个读...
  • 什么是hadoop 环形缓冲区: Shuffle过程是MapReduce的核心,也被称为奇迹发生的地方。要想理解MapReduce, Shuffle是必须要了解的。从这周开始,开始学习shuffle的整个过程,今天带来的是hadoop环形缓冲区的理解 二....
  • 环形缓冲区的实现原理(ring buffer)   在通信程序中,经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。...
  • 环形缓冲区的设计与实现

    万次阅读 2010-07-06 08:58:00
    环形缓冲区是嵌入式系统中十分重要的一种数据结构,比如在一个视频处理的机制中,环形缓冲区就可以理解为数据码流的通道,每一个通道都对应着一个环形缓冲区,这样数据在读取和写入的时候都可以在这个缓冲区里循环...
  • C语言实现一个环形缓冲的读写,注意描述读写指针的操作,这个是今天老师问我的?感谢各位帮我解答一下
  • [3]:环形缓冲区 前一个帖子提及了队列缓冲区可能存在的性能问题及解决方法:环形缓冲区。今天就专门来描述一下这个话题。 为了防止有人给咱扣上“过度设计”的大帽子,事先声明一下:只有当存储空间的分配/释放...
  • 环形缓存bufferC语言实现

    万次阅读 2013-02-05 10:03:37
    环形缓存bufferC语言实现 消息队列锁调用太频繁的问题算是解决了,另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频繁的内存分配不但增加了系统开销,更使得内存碎片不断增多,非常不利于我们的...
  • 环形缓冲区图示: 环形缓冲区    Map的输出结果是由collector处理的,每个Map任务不断地将键值对输出到在内存中构造的一个环形数据结构中。使用环形数据结构是为了更有效地使用内存空间,在内存中放置尽可能多...
  • hadoop里shuffle中的环形缓冲区

    千次阅读 2018-07-07 16:43:24
    最近在看&lt;&lt;Hadoop技术内幕&gt;&gt;里面对shuffle中"奇迹发生的地方"有比较细致的叙述在这整理一下: 在mapper端业务逻辑走完后,调用MapOutputCollector.collect()输出结果,其中...
  • 环形队列串口(发)应用

    千次阅读 2014-04-04 12:04:21
    环形缓冲区的实现原理: 环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常...
  • 前一个帖子提及了队列缓冲区可能存在的性能问题及解决方法:环形缓冲区。今天就专门来描述一下这个话题。 为了防止有人给咱扣上“过度设计”的大帽子,事先声明一下:只有当存储空间的分配/释放非常频繁并且确实...
  • 51单片机串口通信 环形缓冲区队列(FIFO)

    千次阅读 多人点赞 2017-01-08 20:45:40
    51单片机串口通信 环形缓冲区队列最近在做毕业设计刚好涉及到51单片机,简单的研究一下发现51单片机串口只有一个字节的缓存,如果遇到单片机串口中断没有及时处理SBUF的值或者串口中断长时间未退出很容易照成数据...
  • 串口环形缓冲区实验 1.1 实验简介  最简单的串口数据处理机制是数据接收并原样回发的机制是:成功接收到一个数,触发进入中断,在中断函数中将数据读取出来,然后立即。这一种数据处理机制是“非缓冲中断方式”,...
1 2 3 4 5 ... 20
收藏数 15,984
精华内容 6,393
关键字:

环形缓冲区