Table of Contents
- Visão Geral dos Mecanismos de Sincronização e Comunicação
- 1.1 Por que o Zephyr oferece múltiplos mecanismos?
- 1.2 Threads no Zephyr: o pano de fundo necessário
- 1.3 Introdução às Queues no Zephyr
- 1.4 Estrutura básica de uma k_queue
- 1.5 Enviando dados para a fila (Producer)
- 1.6 Recebendo dados da fila (Consumer)
- 1.7 Quando usar k_queue (e quando não usar)
- 1.8 Erros comuns no uso de Queues no Zephyr
- Flags, Semáforos e Eventos no Zephyr: Sinalização e Coordenação de Threads
- 2.1 O que são “Flags” no contexto do Zephyr?
- 2.2 Semáforos (k_sem): a forma mais simples de flag
- 2.3 Thread aguardando um semáforo (consumidor do evento)
- 2.4 Thread ou ISR liberando o semáforo (produtor do evento)
- 2.5 Semáforo binário vs semáforo contador
- 2.6 Limitações dos semáforos como flags
- 2.7 Eventos (k_event): flags múltiplas em um único objeto
- 2.8 Definindo flags de evento
- 2.9 Sinalizando eventos
- 2.10 Aguardando eventos
- 2.11 Quando usar k_sem vs k_event
- 2.12 Erros comuns com Flags e Eventos
- Mutex no Zephyr: Exclusão Mútua, Herança de Prioridade e Uso Correto
- 3.1 Por que Mutex existem (e por que eles são perigosos)
- 3.2 O que é um recurso compartilhado de verdade?
- 3.3 Declaração e inicialização de um Mutex
- 3.4 Protegendo uma região crítica com k_mutex
- 3.5 Mutex no Zephyr não é ISR-safe
- 3.6 Inversão de prioridade: o problema clássico
- 3.7 Herança de prioridade no k_mutex
- 3.8 Timeout em mutex: quando faz sentido?
- 3.9 Deadlocks: como eles surgem
- 3.10 Boas práticas para uso de mutex no Zephyr
- 3.11 Quando não usar mutex
- Notificações e k_poll: Esperando Múltiplos Eventos de Forma Elegante
- 4.1 Por que k_poll existe?
- 4.2 O que é k_poll conceitualmente?
- 4.3 Objetos suportados pelo k_poll
- 4.4 Declarando eventos de polling
- 4.5 Aguardando múltiplos eventos
- 4.6 k_poll não é uma fila nem uma flag
- 4.7 k_poll_signal: notificações leves e diretas
- 4.8 Arquitetura orientada a eventos com k_poll
- 4.9 Quando não usar k_poll
- 4.10 Erros comuns com k_poll
- Comparativo Prático e Diretrizes Arquiteturais no Zephyr
Visão Geral dos Mecanismos de Sincronização e Comunicação
1.1 Por que o Zephyr oferece múltiplos mecanismos?
Diferente de um RTOS minimalista, o Zephyr OS foi projetado para atender desde microcontroladores simples até sistemas complexos com múltiplos cores, drivers, stacks de comunicação e middleware. Por isso, ele não impõe um único modelo de comunicação entre threads, mas oferece vários mecanismos, cada um otimizado para um tipo específico de problema.
Em termos práticos, isso significa que não existe “o melhor mecanismo”, mas sim o mecanismo correto para cada cenário. Usar k_mutex onde um k_queue seria adequado gera latência desnecessária. Usar k_poll quando um k_sem resolveria o problema aumenta complexidade sem ganho real.
De forma conceitual, os mecanismos do Zephyr podem ser classificados em três grandes grupos:
- Comunicação de dados: quando informações precisam ser transferidas entre threads
- Sincronização: quando o objetivo é coordenar execução
- Exclusão mútua: quando recursos compartilhados precisam ser protegidos
Nas próximas seções, vamos analisar como Queues, Flags, Mutexes, Notificações e Eventos se encaixam nesses grupos.
1.2 Threads no Zephyr: o pano de fundo necessário
Antes de falar de filas e eventos, é importante lembrar que o Zephyr adota um modelo de threads preemptivas, com prioridades explícitas. Threads de prioridade menor podem ser interrompidas por threads de prioridade maior a qualquer momento.
Isso cria dois desafios fundamentais:
- Compartilhamento seguro de dados
- Coordenação determinística entre tarefas concorrentes
Sem mecanismos adequados, surgem problemas clássicos:
- Race conditions
- Deadlocks
- Priority inversion
- Leitura de dados inconsistentes
Os objetos do kernel (k_*) existem exatamente para resolver esses problemas.
1.3 Introdução às Queues no Zephyr
As Queues no Zephyr são usadas quando dados precisam ser transferidos entre contextos, tipicamente entre threads produtoras e consumidoras. Diferente de simples variáveis globais, as queues:
- Garantem acesso thread-safe
- Permitem bloqueio automático
- São integradas ao escalonador do kernel
O Zephyr oferece múltiplos tipos de fila. A mais usada conceitualmente é a k_queue, que trabalha com ponteiros genéricos (void *), permitindo grande flexibilidade.
1.4 Estrutura básica de uma k_queue
Uma fila no Zephyr é declarada estaticamente ou dinamicamente. O caso mais comum em sistemas embarcados é a declaração estática, que evita alocação dinâmica em tempo de execução.
#include <zephyr/kernel.h>
K_QUEUE_DEFINE(minha_fila);
Essa macro cria:
- A estrutura de controle da fila
- A sincronização interna
- A integração com o escalonador
Não há necessidade de inicialização manual.
1.5 Enviando dados para a fila (Producer)
Um produtor insere elementos na fila usando k_queue_append() ou k_queue_prepend().
Exemplo simples: uma thread que produz mensagens.
struct mensagem {
uint32_t id;
uint32_t valor;
};
void produtor_thread(void)
{
static struct mensagem msg;
while (1) {
msg.id++;
msg.valor = k_uptime_get_32();
k_queue_append(&minha_fila, &msg);
k_sleep(K_MSEC(500));
}
}
⚠️ Ponto crítico:
A fila armazena ponteiros, não cópias.
Isso significa que:
- O objeto precisa existir enquanto estiver na fila
- Variáveis automáticas de pilha não devem ser usadas
Esse detalhe é uma das principais fontes de bugs para quem vem de FreeRTOS (onde queues normalmente copiam dados).
1.6 Recebendo dados da fila (Consumer)
O consumidor usa k_queue_get(), podendo:
- Bloquear indefinidamente
- Bloquear por tempo definido
- Não bloquear
void consumidor_thread(void)
{
struct mensagem *msg;
while (1) {
msg = k_queue_get(&minha_fila, K_FOREVER);
printk("Recebido: id=%d valor=%d\n",
msg->id, msg->valor);
}
}
Aqui o kernel:
- Coloca a thread em estado bloqueado
- Acorda automaticamente quando um item chega
- Garante que apenas um consumidor receba cada item
1.7 Quando usar k_queue (e quando não usar)
Use Queues quando:
- Dados precisam ser transferidos entre threads
- Existe um padrão produtor–consumidor
- O volume de dados não é trivial
- A ordem de processamento importa
Evite k_queue quando:
- Apenas um flag é necessário
- Não há dados associados ao evento
- Latência mínima é crítica (flags são mais rápidas)
Nesses casos, Flags e Eventos são mais apropriados — tema da próxima seção.
1.8 Erros comuns no uso de Queues no Zephyr
Alguns erros aparecem com frequência em projetos reais:
- Inserir ponteiros para variáveis locais
- Reutilizar a mesma estrutura sem controle
- Usar fila quando um simples evento resolveria
- Esquecer que múltiplos consumidores competem pelo mesmo item
Esses erros não geram warnings de compilação, mas resultam em comportamento não determinístico, especialmente sob carga.