Stack no FreeRTOS: Funcionamento Interno, Cortex-M e Impacto das Interrupções
Nesta seção entramos no nível microarquitetural da stack, algo essencial para dimensionamento correto. Muitos problemas atribuídos ao FreeRTOS, na prática, são erros de entendimento do modelo de empilhamento do Cortex-M combinado com o escalonador.
3.1 O que realmente vai para a stack de uma task
A stack de uma task no FreeRTOS não armazena apenas variáveis locais. Em um Cortex-M, ela precisa comportar, de forma segura:
- Variáveis locais das funções
- Endereços de retorno (call stack)
- Contexto salvo pelo FreeRTOS durante troca de contexto
- Contexto salvo automaticamente pelo hardware em interrupções
- Chamadas de funções de biblioteca (printf, sprintf, lwIP, etc.)
Esse último item é o mais subestimado. Funções como printf, snprintf, conversões de ponto flutuante e rotinas de rede podem consumir centenas ou até milhares de bytes de stack.
3.2 Empilhamento automático no Cortex-M (hardware stacking)
Quando ocorre uma interrupção no Cortex-M, o próprio hardware empilha automaticamente os seguintes registradores:
- R0
- R1
- R2
- R3
- R12
- LR
- PC
- xPSR
Isso totaliza:
8 registradores × 4 bytes = 32 bytes
Se o FPU estiver habilitado (Cortex-M4F, M7, M33 com FPU), e se a interrupção utilizar ponto flutuante, o hardware também empilha:
- S0 a S15
- FPSCR
O que adiciona:
18 palavras × 4 bytes = 72 bytes
Portanto, uma única interrupção pode consumir até 104 bytes de stack automaticamente, sem contar o empilhamento adicional feito pelo compilador dentro da ISR.
3.3 Stack da Task vs Stack da Interrupção
Aqui está um ponto crítico que gera confusão.
- Interrupções NÃO usam a stack da task
- Elas usam a Main Stack Pointer (MSP)
- As tasks usam a Process Stack Pointer (PSP)
O FreeRTOS configura isso de forma explícita:
- MSP → usado por ISRs
- PSP → usado pelas tasks
Porém, durante uma troca de contexto, o FreeRTOS salva parte do contexto da task na stack da própria task, incluindo:
- Registradores R4 a R11
- Ponteiros internos do kernel
Isso adiciona mais consumo temporário à stack da task, principalmente em sistemas com alta taxa de preempção.
3.4 Fórmula prática para estimativa inicial de stack
Uma forma profissional de iniciar o dimensionamento de stack é decompor o consumo:
\[
\text{Stack}{task} =
S{locais} +
S_{chamadas} +
S_{contexto} +
S_{margem}
\]
Onde:
- \(S_{locais}\): variáveis locais máximas (structs, arrays)
- \(S_{chamadas}\): profundidade máxima de chamadas
- \(S_{contexto}\): contexto salvo pelo RTOS
- \(S_{margem}\): fator de segurança (30–50%)
Exemplo prático:
- Variáveis locais: 256 bytes
- Cadeia de chamadas: 4 níveis × 64 bytes = 256 bytes
- Contexto RTOS: ~100 bytes
- Margem de segurança (40%): ~240 bytes
\[
\text{Stack total} \approx 850 \text{ bytes}
\]
Convertendo para palavras (Cortex-M):
\[
850 / 4 \approx 212 \text{ words}
\]
Arredonda-se para:
#define STACK_TASK_EXAMPLE 256
3.5 Erros clássicos de dimensionamento de stack
Alguns padrões de erro recorrentes em projetos reais:
- Usar
printfem tasks com stack de 128 words - Criar arrays locais grandes (
uint8_t buffer[1024]) - Usar recursão (quase sempre inviável em RTOS)
- Ativar FPU sem revisar stacks
- Copiar exemplos genéricos sem medir consumo real
Todos esses levam a stack overflow silencioso, corrupção de heap e comportamento errático.
3.6 Detecção e medição de stack no FreeRTOS
O FreeRTOS fornece mecanismos robustos para medir stack real usada:
uxTaskGetStackHighWaterMark()configCHECK_FOR_STACK_OVERFLOWvApplicationStackOverflowHook()
Exemplo:
UBaseType_t watermark;
watermark = uxTaskGetStackHighWaterMark(NULL);
/* Retorna o menor valor já observado de stack livre */
Se o valor retornado for, por exemplo, 40, significa que apenas 40 words sobraram no pior caso, indicando stack muito justa.