프로그래밍 언어/유용한코드

C언어로 링버퍼 구현하기

원원 2025. 8. 31. 19:19

안녕하세요. 오늘은 C언어로 링버퍼를 구현해보겠습니다.

1) 링버퍼가 필요한 이유
펌웨어에서 링버퍼는 주로 UART 통신에서 많이 사용됩니다.  UART 수신은 일반적으로 인터럽트에서 데이터가 들어오는데,  인터럽트 안에서 곧바로 데이터를 처리하면 지연이나 오류가 발생할 수 있습니다.  
그래서 인터럽트에서는 단순히 받은 데이터를 링버퍼에 저장해 두고,  메인 루프나 다른 Task에서 필요할 때 버퍼에서 꺼내 처리하는 구조를 사용합니다.  이 방식은 데이터 손실을 막고, 시스템을 안정적으로 동작하게 해줍니다.

2) 링버퍼란
링버퍼란 FIFO(First in, First out)방식의 처리방식입니다. 일반적으로 큐와 비슷하지만 배열의 처음과 끝이 연결된 것처럼 동작하는것이 특징입니다.
예를들어서 배열의길이가 4인 배열이 있습니다. (STEP1)
여기에 값 A,B,C,D를 넣습니다 (STEP2)
그 다음 E값을 넣으면 다시 처음으로 돌아가서 인덱스0에 E가 들어갑니다(STEP3)



3) 구현코드 동작방식
배열에 데이터를 넣고 데이터를 읽어야하는 위치,데이터를 넣어야하는 위치 두 가지 변수를 가지고 버퍼에있는 데이터를 WRITE하거나 READ합니다.
데이터를 읽어야 하는 위치를 out, 데이터를 넣어야 하는 위치를 in으로 하겠습니다

(예시1)
초기상태는 IN,OUT이 모두 0입니다 (STEP1)
데이터를 두개 넣으면 IN의 위치가 2로 갑니다 (STEP2)
데이터를 두개 읽으면 OUT의 위치가 2로 갑니다(STEP3)
이때 IN의 위치와 OUT의 위치가 같다면 데이터가 없는 경우입니다.

(예시2)
초기상태는 IN,OUT 모두 0입니다 (STEP1)
데이터를 세개 넣었습니다 (STEP2)
데이터를 write하는 조건으로 "(IN+1) != (OUT)"을 사용하므로 해당상태에서는 데이터가 모두 꽉찬상태로 인식합니다.
만약 write하는 조건이 "IN != OUT"이라면 처음부터 write가 되지않을것입니다.

초기상태는 IN,OUT 모두 0입니다 (STEP1)
데이터를 세개 넣었습니다 (STEP2)
이때 남은데이터의 개수는 3개 입니다. in인덱스-out인덱스=(3-0)=3

데이터를 두개 READ하고 (A,B사용) D,E를 WRITE했습니다 (STEP3)
해당 상황에서 남은 데이터는 C,D,E 이고 IN은1, OUT은 2입니다.
이때 남은 데이터의 개수는 3개입니다. in인덱스-out인덱스=(1-2)= -1
해당내용처럼 배열이 한바퀴 돈 경우 in인덱스-out인덱스를하면 음수값이 나와서 다르게 계산을 해줘야합니다.
(배열전체개수+in인덱스-out인덱스)%배열전체개수 =(4+1-2)%4 = 3

위의 두가지 예시를 코드로 작성해보겠습니다.
사용 할 변수입니다. 버퍼의 개수는 4이고 IN은 q_in, OUT은 q_out, 링버퍼는 q_buf로 했습니다.

#define Q_BUF_MAX 4
uint8_t q_in = 0;
uint8_t q_out = 0;
uint8_t q_buf[Q_BUF_MAX];


버퍼의 개수를 확인하는 함수입니다. (예시2)

uint32_t bufAvailable()
{
  return (Q_BUF_MAX+q_in-q_out) % (Q_BUF_MAX);
}

버퍼에 write하는 함수입니다. (예시2)
in인덱스+1의 값이 out인덱스와 다를때 데이터가 저장됩니다.

void qWrite(uint8_t q_data)
{
  uint16_t q_in_next;
  q_in_next = (q_in+1)%Q_BUF_MAX;
  if(q_in_next !=q_out)
  {
    q_buf[q_in] = q_data;
    q_in = q_in_next;
  }
}

버퍼에 read하는 함수입니다. (예시1)
in인덱스와 out인덱스가 다를때 데이터를 읽습니다.

uint8_t qRead()
{
  uint8_t ret=0;
  if(q_out != q_in)
  {
    ret = q_buf[q_out];
    q_out = (q_out+1)%Q_BUF_MAX;
  }
  return ret;
}


테스트 main문입니다. z를 타이핑하면 프로그램이 종료됩니다.
0~9 숫자는 테스트를위한 문자이므로 버퍼에 저장되지않습니다.
1: 각 변수에 저장되어있는 변수
2: 1바이트 읽기 (q_out 이동)
3: 계속읽는모드 ON/OFF


int main()
{
  char c = 0;
  char mode = 0;
  int i;
  
  while(c != 'z')
  {
    if (_kbhit())
    {
      c = getch();
    }
    else
    {
      c = 0;
    }
    
    if(c != 0)
    {
      printf("%c",c);
    }
    
    if(c >= '0' && c<='9')
    {
      printf("\n");
      if(c == '1')
      {
        printf("BUF DISPLAY\n");
        for(i = 0; i<Q_BUF_MAX ; i++)
        {
          printf("%c ",q_buf[i]);
        }
        printf("\nq_in , q_out : %d   %d \n",q_in, q_out);    
        printf("bufAvailable : %d \n",bufAvailable()); 
      }
      if(c == '2')
      {
        printf("read : %c \n",qRead());
      }
      if(c == '3')
      {
        mode = !mode;
        printf("READ MODE : %s \n",(mode ==1 ? "ON" : "OFF" ));
      }
    }
    else
    {
      if(c!=0)
        qWrite(c);
    }
    if(mode == 1)
    {
      if(bufAvailable())
      {
        printf("\nbuf read: %c \n",qRead());
      }
    }
  }
}


예시2를 테스트했습니다.
ABC를 넣고 각 변수에 들어가있는 값을 확인했습니다.
in인덱스는 3에가있고 out인덱스는 0에 가있습니다.
데이터가 ABC 3개 들어가있으므로 Available도 3입니다.

데이터를 두번 읽었습니다 (A,B)
DE를 넣고 각 변수에 들어가있는 값을 확인했습니다.




전체코드

#include <stdio.h>
#include <stdint.h>
#include <conio.h>


#define Q_BUF_MAX 4
uint8_t q_in = 0;
uint8_t q_out = 0;
uint8_t q_buf[Q_BUF_MAX];


uint32_t bufAvailable()
{
  return (Q_BUF_MAX+q_in-q_out) % (Q_BUF_MAX);
}


void qWrite(uint8_t q_data)
{
  uint16_t q_in_next;
  q_in_next = (q_in+1)%Q_BUF_MAX;
  if(q_in_next !=q_out)
  {
    q_buf[q_in] = q_data;
    q_in = q_in_next;
  }
}

uint8_t qRead()
{
  uint8_t ret=0;
  if(q_out != q_in)
  {
    ret = q_buf[q_out];
    q_out = (q_out+1)%Q_BUF_MAX;
  }
  return ret;
}

int main()
{
  char c = 0;
  char mode = 0;
  int i;
  
  while(c != 'z')
  {
    if (_kbhit())
    {
      c = getch();
    }
    else
    {
      c = 0;
    }
    
    if(c != 0)
    {
      printf("%c",c);
    }
    
    if(c >= '0' && c<='9')
    {
      printf("\n");
      if(c == '1')
      {
        printf("BUF DISPLAY\n");
        for(i = 0; i<Q_BUF_MAX ; i++)
        {
          printf("%c ",q_buf[i]);
        }
        printf("\nq_in , q_out : %d   %d \n",q_in, q_out);    
        printf("bufAvailable : %d \n",bufAvailable()); 
      }
      if(c == '2')
      {
        printf("read : %c \n",qRead());
      }
      if(c == '3')
      {
        mode = !mode;
        printf("READ MODE : %s \n",(mode ==1 ? "ON" : "OFF" ));
      }
    }
    else
    {
      if(c!=0)
        qWrite(c);
    }
    if(mode == 1)
    {
      if(bufAvailable())
      {
        printf("\nbuf read: %c \n",qRead());
      }
    }
  }
}