Olá caro leitor. Com este artigo pretendemos expor um recurso muito legal de ser implementado em software embarcado que pode ser utilizado para as mais diversas aplicações, em especial para gerenciamento de dados vindos de controladores de comunicação, o Buffer Circular.
Buffer circular, deixe que ele ordene os dados por você
O buffer circular é conhecido por desenvolvedores experientes por ser uma estrutura de dados que possui uma política de inserção e retirada de dados do tipo FIFO, que em tradução livre significa: primeiro a entrar, primeiro a sair.
Em termos práticos, os pacotes de dados que são inseridos nesse buffer serão ordenados por chegada, de forma que a retirada será feita nessa mesma ordem. O grande diferencial do buffer circular está no ponto em que virtualmente não ocorre overflow (estouro) da área de dados reservada para depositar as variáveis, pois caso o controle de inserção encontre o fim da área de dados, ele automaticamente aponta para o começo dessa mesma área e sobrescreve o conteúdo mais antigo.
Com isso problemas conhecidos como exceções geradas pelo processador que ocorrem quando tentamos depositar dados numa área de memória maior do que o que foi previamente alocada deixa de ocorrer. A figura abaixo demonstra como funciona de forma intuitiva (e canônica um buffer circular).
A figura ainda aponta que os controladores de inserção (índices) se movimentam na mesma direção, de forma que o índice de remoção “persegue” o de inserção, e ambos respeitam a regra, e ao chegar no final da área de memória, voltam ao início. De forma adicional controles de estouro da quantidade de dados escritos ou retirados podem ser implementados para prevenção de perda de conteúdo.
Interessante, mas onde posso usar o buffer circular?
O comportamento do buffer circular é ideal para implementação de qualquer estrutura de dados que seja estaticamente alocada e que se comporte como FIFO. Como exemplo, mailboxes e filas podem ser implementados utilizando o buffer circular como kernel. O professor Rodrigo Almeida já deu um uso especial do buffer circular, construção de um gerenciador de tarefas para uso em um sistema operacional de tempo real. Para acessar o seu artigo sobre isso clique aqui.
Nos meus artigos sobre DSP e controle digital, você, leitor, perceberá que a implementação dos filtros e equações de diferenças ocorrem na forma de buffers circulares. As aplicações não param por aí, buffers circulares podem ser utilizados para armazenamento temporário de dados vindos de controladores de comunicação. O melhor exemplo disso é a recepção de dados de uma UART, conforme ilustrado na figura abaixo:
Com o buffer circular, podemos colocar nossa UART apenas para fazer o que ela deve fazer, receber dados. Com isso, a cada byte recebido, este é colocado no buffer circular. Dessa forma aplicações que precisem dos dados da UART, podem fazer o acesso a eles ou mesmo aguardar o preenchimento do buffer para ler os dados depois. Além disso essa abordagem mantém a linha temporal de chegada dos dados, ou seja, dados que chegam primeiro serão processados primeiro.
Quero um exemplo de Buffer Circular!
A ideia deste artigo é resolver um problema apresentando um recurso para isso, logo falar sobre as melhores implementações de buffer circular fogem ao seu escopo. Nos arquivos buffer_circ.c e .h o leitor vai encontrar uma implementação básica do buffer circular. Ele contém as rotinas para inserção, remoção e checagem de buffer cheio ou vazio. Além disso prove uma macro para declarar uma estrutura de buffer inicializada. Vejam os arquivos:
/**
* @brief Simple circular buffer implementation with basic management functions
*/
#ifndef __BUFFER_CIRC_H
#define __BUFFER_CIRC_H
#define BUFFER_SIZE 128
/** circular buffer management structure */
typedef struct {
unsigned char data[BUFFER_SIZE];
unsigned int items;
unsigned int wr_ptr;
unsigned int rd_ptr;
} buffer_circ_t;
/**
* @brief insert a stream data with size lenght to the buffer
*/
int buffer_insert(buffer_circ_t *b,void *data, unsigned int size);
/**
* @brief retrieves a stream of dat with specified size
*/
int buffer_retrieve(buffer_circ_t *b, void *data, unsigned int size);
/**
* @brief check if buffer is already full
*/
int buffer_full(buffer_circ_t *b);
/**
* @brief check if a data stream with specified size will full the buffer
*/
int buffer_will_full(buffer_circ_t *b, unsigned int size);
/**
* @brief makes the buffer empty
*/
int buffer_flush(buffer_circ_t *b);
/**
* @brief check if buffer is empty
*/
int buffer_empty(buffer_circ_t *b);
/** declare a initialized circular buffer */
#define CIRCULAR_BUFFER_DECLARE(name) \
buffer_circ_t name = { \
.data = {0}, \
.items = 0, \
.wr_ptr = 0, \
.rd_ptr = 0, \
}
#endif
A constante BUFFER_SIZE determina em bytes a quantidade de memória reservada para uso, podendo ser alterada de acordo com a necessidade do usuário.
#include "string.h"
#include "buffer_circ.h"
int buffer_insert(buffer_circ_t *b,void *data, unsigned int size)
{
int ret = -1;
unsigned char *ptr = (unsigned char *)data;
if(b == NULL) {
/* check your buffer parameter */
return(ret);
}
if(size + b->items <= BUFFER_SIZE){
/* the buffer has enough room, insert
* stream.
*/
unsigned int i;
for(i = 0; i < size; i++) {
b->data[wr_ptr] = *ptr++;
/* increment the input index in form if it
* reach the buffer end, its placed in it
* initial address again
*/
wr_ptr = (wr_ptr + 1) % BUFFER_SIZE;
b->items++;
}
ret = 0;
}
return(ret);
}
int buffer_retrieve(buffer_circ_t *b, void *data, unsigned int size)
{
int ret = 0;
unsigned char *ptr = (unsigned char *)data;
if(b == NULL) {
/* check your buffer parameter */
return((ret = -1));
}
/* if the size requested fits on
* current contents, extract
* the data stream
*/
unsigned int i;
for(i = 0; (i < size) && (b->items != 0); i++) {
*ptr++ = b->data[rd_ptr];
rd_ptr = (rd_ptr + 1) % BUFFER_SIZE;
ret++;
b->items--;
}
return(ret);
}
int buffer_full(buffer_circ_t *b)
{
int ret = 0;
if(b->items == BUFFER_SIZE) {
ret = 1;
}
return(ret);
}
int buffer_will_full(buffer_circ_t *b, unsigned int size)
{
int ret = 0;
if(b->items + size > BUFFER_SIZE ) {
ret = 1;
}
return(ret);
}
int buffer_flush(buffer_circ_t *b)
{
int ret = 0;
if(b != NULL) {
b->wr_ptr = b->rd_ptr;
b->items = 0;
} else {
ret = -1;
}
return(ret);
}
int buffer_empty(buffer_circ_t *b)
{
return((b->items == 0? 1: 0));
}
Reparem nas linhas 28 e 53 desse arquivo, essa operação é chamada de incremento circular. Sua função é incrementar o índice em uma unidade, e, em caso de encontro com fim da área de memória, o resultado dessa operação será o índice 0, ou seja, o início da área reservada.
Conclusão
Buffers circulares são recursos interessantes, seguros e evitam problemas de vazamento de memória. Por exemplo, são poderosos podendo servir de base para estruturas de dados mais complexas, são versáteis, suas aplicações vão de sistemas operacionais a processamento digital de sinais. O exemplo acima possui funções de operações básicas, além de servir de ponto de partida para implementações mais complexas.
Links úteis
Clique aqui para acessar repositório no Github com código e exemplo












Muito interessante o artigo!
Creio que o incremento circular dos índices de leitura e escrita do buffer se encontra nas linhas 28 e 53.
Obrigado Fernando, artigo corrigido 🙂
Felipe
Boa tarde, receio não ter entendido o porque do segundo IF na função “buffer_retrieve” presente no arquivo .C. Por favor me corrija se eu estiver errado, mas você copiou esse teste da função “buffer_insert” que inseri dados no buffer e esse mesmo teste verifica se com o número atual de itens + o número de itens a serem “Guardados” no Buffer não irá causar um Overflow. Neste caso eu consigo compreender o teste. No caso da função “buffer_retrieve” esse teste não teria lógica ao meu ver, visto que se o meu buffer é de 10 posições de memória (BUFFER_SIZE =… Leia mais »
Leonardo, obrigado pela leitura, você está corretísssimo, subi a versão de desenvolvimento e atualizei apenas o github. Atualizei o código que está inline no artigo.
O teste realmente não faz sentido, o que se pode ocorrer é se o usuário passar um size maior do que o existente a retirada de bytes vai se repetir até o número de items caia a zero.
Felipe
“Com isso problemas conhecidos como “memory leak” que ocorre quando
tentamos depositar dados numa área de memória maior do que o que foi
previamente alocada deixa de ocorrer”
Na verdade seria “buffer overflow”
Zeh, obrigado pela leitura.
Voce esta correto, buffer overflow é um tipo de vazamento de memória. A unica diferença sutil é que o oveflow mais se refere a ultrapassagem do limite reservado logicamente para um dado stream ao passo que o memory leak é a consequência desse evento.
memory leak é quando você aloca memória dinâmica(heap) e nunca libera ela.
buffer overflow é quando você esta acessando mais memória do que você alocou, alterando a memória de outra variável do seu processo ou causando um crash no aplicativo quando você tenta acessar um endereço de memória que o sistema operacional não alocou para o seu processo.
Zeh, obrigado pelo comentário, a confusão de conceitos foi minha 🙂
O Artigo será corrigido.
Felipe