C언어 링버퍼 구현 방법(Ring Buffer)

2021. 8. 25. 15:16 컴퓨터/프로그래밍

편리한 C언어 링버퍼

프로그램 코딩에서 버퍼는 매우 중요합니다. 사용처에 따라 다양한 방식으로 구현하는데요, 특히 링버퍼(Ring Buffer)는 다목적이라고 할 수 있습니다. 통신을 통한 시스템 제어 프로그래밍에서 버퍼는 매우 중요합니다. 외부에서 제어 신호가 계속 오는데 시간이 걸려서 제 때에 처리하지 못하거나, 반대로 시스템 상태를 계속해서 서버에 전송해야 하는데 갑자기 통신 상태가 불량해지 경우 링버퍼로 문제를 해결할 수 있습니다.

링버퍼는 구현하기 쉽고 버퍼가 모두 찼을 경우 어떻게 처리할지 선택에 따라 구현하기도 수월합니다. 오래된 자료부터 버려가면서 버퍼링하거나 버퍼 오버로 판단하여 에러 처리 루틴을 호출합니다.

링버퍼는 아래와 같은 구조로 되어 있습니다.

링버퍼는 버퍼와 버퍼에 쓰기와 읽기 위치를 알려 주는 정수 태그 두 개로 구성되는데요, 쓰기 태그를 head 또는 front라고 하고, 읽기 태그를 tail 또는 rear이라고 합니다. 개인적으로 철자 개수가 같으면 변수 이름으로 쓰기에 편해서 head와 tail을 사용하기를 좋아하는데요, 정리하여 말씀드리면 아래와 같습니다.

  • head와 tail의 초기 값은 둘 다 0
  • 링버퍼의 head 위치에 데이터를 쓰고 head 값을 1 증가

  • head와 tail을 비교하여 값이 다르다면 데이터가 있음으로 판단
  • tail 위치의 버퍼에서 데이터를 읽고 tail 값을 1 증가
  • head와 tail을 비교하여 값이 같으면 데이터가 없음으로 판단

버퍼 오버 시 처리 방법 두 가지

  • head의 위치에 데이터를 추가한 후에 1 증가
  • head 값이 tail 값과 같다면 버퍼 용량을 무두 채웠다고 판단.
  • tail 값도 1 증가. 가장 오래된 데이터 제거 효과

    or

    head와 tail 값을 유지하고 버퍼 오버 에러 처리

링버퍼 예제

버퍼가 모두 찼을 경우 오래된 자료부터 제거하고 새 데이터를 넣는 링버퍼의 예제입니다. 예제를 보면 링버퍼의 값을 반환하는 ring_get() 함수의 반환 값이 0이면 링버퍼의 데이터가 없음으로 판단할 수 있습니다. 그러나 만일 데이터 사이즈가 0인 것도 데이터로써 처리한다면 head와 tail의 값을 비교하는 ring_exists() 함수를 사용하는 것이 안전합니다.

예제 파일 다운로드:

ring_buffer.c
0.00MB

에제 소스

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_RING_SIZE 200               // 링버퍼가 갖는 아이템 개수
#define MAX_RING_DATA_SIZE 2048         // 링버퍼 내의 버퍼 개별 크기

typedef struct{                            // 링버퍼 안의 개별 버퍼        
  int sz_data;                            // 개별 버퍼에 복사된 실제 데이터 길이
  char data[MAX_RING_DATA_SIZE];        // 개별 버퍼의 실제 저장 장소
} rign_item_t;    // 링버퍼 내 개별 저장소

typedef struct{
  int tag_head;                         // 쓰기 위치
  int tag_tail;                         // 읽기 위치
  rign_item_t item[MAX_RING_SIZE];        // 링버퍼 내의 저장 장소
} ring_t;        // 링버퍼

/** ----------------------------------------------------------------------------
@brief  링버퍼에 데이터 저장
@remark 저장할 데이터는 최대 MAX_RING_DATA_SIZE로 저장 
@param  ring : 링버퍼 포인터
@param  data : 저장할 데이터
@param  sz_data : 데이터 길이
@return -
 -----------------------------------------------------------------------------*/
void ring_put( ring_t *ring, char *data, int sz_data){

  // 링버퍼의 개별 저장소보다 데이터가 크다면
  if ( MAX_RING_DATA_SIZE < sz_data)    sz_data = MAX_RING_DATA_SIZE; 

  // ring에 데이터 저장
  ring->item[ring->tag_head].sz_data = sz_data;
  memcpy( ring->item[ring->tag_head].data, data, sizeof( ring->item[ring->tag_head].data));

  // ring tag 조정
  ring->tag_head = ( ring->tag_head +1) % MAX_RING_SIZE; // head 증가
  if ( ring->tag_head == ring->tag_tail){ // 버퍼가 모두 찼다면
    ring->tag_tail = ( ring->tag_tail +1) % MAX_RING_SIZE; // tail 증가
  }
}

/** ----------------------------------------------------------------------------
@brief  링버퍼 내의 데이터 요청
@remark 수신 버퍼는 0으로 초기화 
@remark 수신 버퍼에는 최대 sz_buff만큼 저장 
@param  ring : 링버퍼 포인터
@param  buff : 데이터 수신 버퍼
@param  sz_buff : 버퍼 크기
@return 데이터 길이
 -----------------------------------------------------------------------------*/
int ring_get( ring_t *ring, char *buff, int sz_buff){

  // 큐에 데이터가 없다면 복귀
  if ( ring->tag_head == ring->tag_tail){
    return 0; // 테이터 없음
  }

  // 큐 데이터 구하기
  memset( buff, 0, sz_buff);
  int sz_data = ring->item[ring->tag_tail].sz_data;
  if ( sz_buff < sz_data)   sz_data= sz_buff;             // 수신 버퍼 크기만큼 복사
  memcpy( buff, ring->item[ring->tag_tail].data, sz_data);

  ring->tag_tail = ( ring->tag_tail +1) % MAX_RING_SIZE;  // tail 증가

  return sz_data;
}

/** ----------------------------------------------------------------------------
@brief  링버퍼에 데이터가 있는지의 여부
@remark -
@param  ring : 링버퍼 포인터
@return TRUE == 데이터 있음
 -----------------------------------------------------------------------------*/
int ring_exists( ring_t *ring){

  return ring->tag_head != ring->tag_tail; // head 와 tail 값이 다르다면 데이터 있음
}

/** ----------------------------------------------------------------------------
@brief 링버퍼 초기화
@remark -
@param ring : 링버퍼 포인터
 -----------------------------------------------------------------------------*/
void ring_init( ring_t *ring){

  ring->tag_head = ring->tag_tail = 0; // 태그 값을 0으로 초기화
}


int main( void){
  char    *data1 = "badayak.com";
  char    *data2 = "blogger";
  char    *data3 = "tistory.com";
  ring_t   ring;

  ring_init( &ring);
  ring_put( &ring, data1, strlen( data1));
  ring_put( &ring, data2, strlen( data2));

  printf( "첫 번째 출력------------------------\n");
  while( 1){
    if ( ring_exists( &ring)){
      char buff[MAX_RING_DATA_SIZE];
      int  sz_data = ring_get( &ring, buff, sizeof( buff));
      printf( "data size=%2d data string=%s\n", sz_data, buff);
    } 
    else {
      break;
    }      
  }
  ring_put( &ring, data1, strlen( data1));
  ring_put( &ring, data2, strlen( data2));
  ring_put( &ring, data1, strlen( data1));
  ring_put( &ring, data2, strlen( data2));
  ring_put( &ring, data3, strlen( data3));

  printf( "두 번째 출력------------------------\n");
  while( 1){
    if ( ring_exists( &ring)){
      char buff[MAX_RING_DATA_SIZE];
      int  sz_data = ring_get( &ring, buff, sizeof( buff));      
      printf( "data size=%2d data string=%s\n", sz_data, buff);
    } 
    else {
      break;
    }
  }
}

실행 결과는 아래와 같습니다.

$ ./a.out
첫 번째 출력------------------------
data size=11 data string=badayak.com
data size= 7 data string=blogger
두 번째 출력------------------------
data size=11 data string=badayak.com
data size= 7 data string=blogger
data size=11 data string=badayak.com
data size= 7 data string=blogger
data size=11 data string=tistory.com
이 댓글을 비밀 댓글로

티스토리 로그인이 풀리면 여기를 클릭하세요.

error: Content is protected !!