Heap no FreeRTOS: Modelos, Fragmentação e Impacto no Sistema
Se a stack está ligada à execução determinística das tasks, o heap está diretamente relacionado à capacidade do sistema de crescer, se adaptar e manter estabilidade ao longo do tempo. Em projetos com FreeRTOS, entender o heap não é opcional — ele define quantas tasks, filas, semáforos e buffers o sistema suporta.
4.1 O que realmente consome heap no FreeRTOS
O heap do FreeRTOS é consumido sempre que o kernel precisa alocar estruturas dinâmicas. Entre os principais consumidores estão:
- TCB de cada task
- Stack de cada task (quando criação dinâmica)
- Filas (Queue Control Block + buffer)
- Semáforos e mutexes
- Event Groups
- Timers de software
- Stream Buffers e Message Buffers
- Estruturas internas do scheduler (Idle, Timer Service Task)
Importante destacar:
👉 A própria Idle Task consome heap
👉 A Timer Service Task também consome heap, caso configUSE_TIMERS esteja habilitado
Essas tasks são criadas automaticamente pelo kernel e precisam ser contabilizadas no dimensionamento global.
4.2 Os cinco modelos de heap do FreeRTOS
O FreeRTOS oferece cinco implementações oficiais de heap, cada uma com compromissos diferentes entre simplicidade, eficiência e segurança.
heap_1
- Alocação simples
- Não permite free
- Zero fragmentação
- Ideal para sistemas estáticos e extremamente determinísticos
heap_2
- Permite
free - Não coalesce blocos livres
- Pode fragmentar ao longo do tempo
- Pouco recomendado em projetos modernos
heap_3
- Wrapper direto para
malloc/freeda libc - Depende do heap do compilador/linker
- Pouco controle e difícil de validar
- Não recomendado para sistemas críticos
heap_4 (mais usado na prática)
- Permite
mallocefree - Coalescência de blocos livres
- Fragmentação controlada
- Bom equilíbrio entre segurança e flexibilidade
heap_5
- Suporta múltiplas regiões de memória
- Ideal para sistemas com SRAM interna + externa
- Mais complexo, porém extremamente poderoso
Em sistemas STM32 com lwIP, FATFS ou USB, heap_4 é praticamente o padrão de mercado.
4.3 Estrutura interna do heap_4
O heap_4 implementa uma lista encadeada de blocos livres. Cada bloco contém:
- Tamanho do bloco
- Flag de livre/ocupado
- Ponteiro para o próximo bloco
Cada alocação consome:
\[
\text{Tamanho solicitado} + \text{Overhead do bloco}
\]
O overhead típico gira em torno de 8 a 16 bytes, dependendo da arquitetura e alinhamento.
Exemplo:
Solicitar um mutex (80 bytes internos) pode consumir ~96 bytes de heap real.
4.4 Cálculo do heap necessário (estimativa inicial)
Uma abordagem profissional para dimensionar o heap é somar todos os consumidores conhecidos, mais margem.
Exemplo realista:
- Idle Task
- TCB: 100 bytes
- Stack: 256 words = 1024 bytes
- Timer Service Task
- TCB: 100 bytes
- Stack: 512 words = 2048 bytes
- 5 Tasks de aplicação
- TCB: 5 × 100 = 500 bytes
- Stack: 5 × 1024 = 5120 bytes
- Filas, semáforos, buffers
- Estimativa: 2 KB
Somando:
\[
Heap_{mínimo} \approx 100 + 1024 + 100 + 2048 + 500 + 5120 + 2048
\]
\[
Heap_{mínimo} \approx 10.9\text{ KB}
\]
Aplicando margem de 30%:
\[
Heap_{final} \approx 14.5\text{ KB}
\]
Configuração segura:
#define configTOTAL_HEAP_SIZE (15 * 1024)
4.5 Fragmentação: o inimigo invisível
Mesmo com heap_4, a fragmentação pode ocorrer se:
- Muitas alocações e liberações dinâmicas
- Criação/destruição frequente de tasks
- Uso intenso de filas temporárias
Boas práticas:
- Criar tasks, filas e semáforos uma única vez
- Evitar
vTaskDeleteem sistemas long-lived - Preferir alocação estática quando possível
4.6 Heap estático vs heap dinâmico
O FreeRTOS permite um modelo híbrido extremamente poderoso:
- Tasks críticas → criação estática
- Tasks auxiliares → criação dinâmica
- Buffers grandes → estáticos
- Estruturas temporárias → heap
Esse modelo reduz fragmentação e aumenta previsibilidade temporal.
Excelente, vamos consolidar tudo em método de engenharia.