Notificações e k_poll: Esperando Múltiplos Eventos de Forma Elegante
4.1 Por que k_poll existe?
À medida que o sistema cresce, surge um problema clássico:
como uma única thread pode reagir a múltiplas fontes de eventos diferentes sem virar um emaranhado de código?
Exemplos reais:
- Um driver precisa esperar dados de uma fila ou um timeout
- Uma thread de controle reage a sensor, comando externo ou erro
- Um serviço precisa aguardar semáforo, evento ou mensagem
Criar uma thread por evento não escala. Usar múltiplos k_sem_take() em sequência não funciona.
É exatamente para isso que o k_poll existe.
4.2 O que é k_poll conceitualmente?
O k_poll é um mecanismo de espera multiplexada.
Ele permite que uma thread fique bloqueada aguardando qualquer um entre vários objetos do kernel.
Você pode pensar nele como:
- Um
select()do mundo POSIX - Um event loop determinístico
- Um despachador de eventos do kernel
O grande diferencial é que:
- Ele não consome CPU
- Ele é determinístico
- Ele funciona nativamente com objetos do Zephyr
4.3 Objetos suportados pelo k_poll
O k_poll trabalha com estruturas de evento (k_poll_event), que podem observar:
- Semáforos (
k_sem) - Filas (
k_queue) - Eventos (
k_event) - Sinais (
k_poll_signal)
Isso permite construir arquiteturas orientadas a eventos, muito mais limpas que superloops ou cadeias de if.
4.4 Declarando eventos de polling
Exemplo com dois tipos de evento:
- Um semáforo
- Um evento
#include <zephyr/kernel.h>
K_SEM_DEFINE(sem_rx, 0, 1);
K_EVENT_DEFINE(evt_status);
struct k_poll_event eventos[2];
Inicialização dos eventos:
void init_poll_events(void)
{
eventos[0] = K_POLL_EVENT_INITIALIZER(
K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&sem_rx
);
eventos[1] = K_POLL_EVENT_INITIALIZER(
K_POLL_TYPE_EVENT,
K_POLL_MODE_NOTIFY_ONLY,
&evt_status
);
}
Aqui já aparece um ponto importante:
o k_poll não consome o evento, ele apenas notifica que algo ocorreu.
4.5 Aguardando múltiplos eventos
Agora a thread principal pode aguardar qualquer um deles:
void thread_event_loop(void)
{
while (1) {
int ret = k_poll(eventos, 2, K_FOREVER);
if (ret == 0) {
if (eventos[0].state == K_POLL_STATE_SEM_AVAILABLE) {
k_sem_take(&sem_rx, K_NO_WAIT);
printk("Semáforo RX acionado\n");
}
if (eventos[1].state == K_POLL_STATE_EVENT_SIGNALED) {
uint32_t flags = k_event_wait(&evt_status,
0xFFFFFFFF,
true,
K_NO_WAIT);
printk("Evento recebido: 0x%x\n", flags);
}
}
}
}
Observe o padrão:
k_poll()acorda a thread- A thread inspeciona o estado
- O evento é tratado manualmente
- O sistema volta a aguardar
Esse modelo é extremamente robusto.
4.6 k_poll não é uma fila nem uma flag
Um erro conceitual comum é tratar k_poll como:
- Um substituto de fila
- Um mecanismo de comunicação
Isso está errado.
O k_poll:
- Não carrega dados
- Não mantém histórico
- Não substitui semáforos nem eventos
Ele apenas orquestra a espera.
4.7 k_poll_signal: notificações leves e diretas
O k_poll_signal é uma forma leve de notificação explícita, muito útil quando:
- Você não quer semáforos
- Não precisa de contador
- Apenas deseja “acordar” uma thread
Declaração:
struct k_poll_signal sinal;
Inicialização:
k_poll_signal_init(&sinal);
Sinalizando:
k_poll_signal_raise(&sinal, 0);
Consumindo:
if (eventos[i].signal->signaled) {
eventos[i].signal->signaled = 0;
printk("Sinal recebido\n");
}
Esse mecanismo é muito usado internamente no Zephyr e em drivers.
4.8 Arquitetura orientada a eventos com k_poll
Quando bem aplicado, o k_poll permite:
- Uma thread central de decisão
- Múltiplas fontes assíncronas
- Código linear, legível e determinístico
- Redução drástica de threads desnecessárias
Esse padrão aparece frequentemente em:
- Stacks de comunicação
- Gateways IoT
- Gerenciamento de estado
- Sistemas embarcados complexos
4.9 Quando não usar k_poll
Evite k_poll quando:
- Apenas um evento é esperado
- A lógica é trivial
- Latência mínima absoluta é necessária
Nesses casos:
k_sem_takek_event_wait
São mais simples e eficientes.
4.10 Erros comuns com k_poll
Alguns problemas recorrentes:
- Esquecer de consumir o evento após o poll
- Reutilizar estruturas sem resetar estado
- Criar polling para tudo (overengineering)
- Misturar lógica de dados com lógica de despacho
Esses erros geram sistemas difíceis de manter, não necessariamente bugs imediatos.