MCU & FPGA RTOS FreeRTOS: Stream Buffers, comparação com Queues e critérios de escolha

FreeRTOS: Stream Buffers, comparação com Queues e critérios de escolha

Queues no FreeRTOS: modelo de dados, garantias e custos ocultos

As Queues são um dos mecanismos mais tradicionais e amplamente utilizados no FreeRTOS para comunicação e sincronização entre tarefas e interrupções. Conceitualmente, uma queue representa uma fila de elementos discretos, onde cada elemento possui tamanho fixo, definido no momento da criação. Esse modelo é extremamente poderoso quando o sistema trabalha com eventos bem definidos, estruturas de dados completas ou mensagens semânticas claras, como comandos, estados ou pacotes de informação.

Internamente, uma queue mantém um buffer contíguo de memória capaz de armazenar N elementos, cada um com itemSize bytes. A cada operação de envio (xQueueSend) ou recepção (xQueueReceive), o kernel realiza uma cópia completa do item, garantindo isolamento total entre produtor e consumidor. Essa característica é fundamental para segurança e previsibilidade: após o envio, o produtor pode reutilizar ou modificar sua variável local sem qualquer risco de corromper os dados que já estão na fila.

Do ponto de vista de sincronização, as queues são muito mais flexíveis do que os stream buffers. Elas suportam múltiplos produtores e múltiplos consumidores, com controle robusto de concorrência. O FreeRTOS mantém listas internas de tarefas bloqueadas tanto para envio quanto para recepção, acordando a tarefa de maior prioridade quando as condições são satisfeitas. Essa flexibilidade, no entanto, vem acompanhada de um custo adicional em termos de overhead de CPU e uso de memória.

Um ponto frequentemente negligenciado é que cada operação de envio ou recebimento em uma queue envolve operações de cópia de memória, cujo custo cresce proporcionalmente ao tamanho do item. Em sistemas onde dados grandes ou frequentes precisam ser transferidos — como buffers de áudio, streams UART ou dados de sensores em alta taxa — esse custo pode se tornar significativo, impactando a latência e o consumo de energia.

Exemplo básico: criação de uma Queue

#include "FreeRTOS.h"
#include "queue.h"

#define QUEUE_LENGTH   10

typedef struct
{
    uint32_t id;
    uint16_t value;
} Message_t;

QueueHandle_t xQueue;

void init_queue(void)
{
    xQueue = xQueueCreate(
        QUEUE_LENGTH,
        sizeof(Message_t)
    );

    configASSERT(xQueue != NULL);
}

Nesse exemplo, a queue é capaz de armazenar até 10 mensagens completas do tipo Message_t. Cada envio copiará toda a estrutura para dentro do buffer interno da fila.

Envio de dados para a Queue

void vProducerTask(void *pvParameters)
{
    Message_t msg;

    for (;;)
    {
        msg.id = 1;
        msg.value = 1234;

        xQueueSend(
            xQueue,
            &msg,
            portMAX_DELAY
        );

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

Observe que o endereço da estrutura (&msg) é passado para a função, mas o FreeRTOS copia o conteúdo, não o ponteiro. Esse detalhe elimina uma classe inteira de bugs relacionados a ponteiros pendentes (dangling pointers).

Recepção de dados da Queue

void vConsumerTask(void *pvParameters)
{
    Message_t receivedMsg;

    for (;;)
    {
        if (xQueueReceive(
                xQueue,
                &receivedMsg,
                portMAX_DELAY
            ) == pdPASS)
        {
            // Processa a mensagem recebida
        }
    }
}

Aqui, a tarefa consumidora recebe uma cópia completa da estrutura enviada, preservando o isolamento entre tarefas e facilitando a análise e o debug do sistema.

Custos ocultos das Queues

Apesar de sua robustez, as queues carregam alguns custos que precisam ser considerados em arquiteturas mais exigentes:

  1. Overhead de cópia: cada envio e recepção envolve cópia de memória proporcional ao tamanho do item.
  2. Fragmentação lógica: fluxos contínuos de dados precisam ser artificialmente segmentados em mensagens.
  3. Latência adicional: o custo de cópia e gerenciamento interno pode ser significativo em sistemas de alta taxa de dados.
  4. Uso de memória previsível, porém rígido: o tamanho da fila e dos itens é fixo após a criação.

Esses fatores não tornam as queues inadequadas — pelo contrário, elas continuam sendo a melhor escolha em muitos cenários —, mas explicam por que os stream buffers surgem como uma alternativa mais eficiente em casos específicos.


0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x