MCU & FPGA RTOS Filas, Flags, Mutexes, Notificações e Eventos no Zephyr RTOS

Filas, Flags, Mutexes, Notificações e Eventos no Zephyr RTOS

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:

  1. k_poll() acorda a thread
  2. A thread inspeciona o estado
  3. O evento é tratado manualmente
  4. 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_take
  • k_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.


Related Post