4 — Workqueues no Zephyr
Processamento desacoplado, escalável e seguro
Se interrupções e soft timers são fontes de eventos, workqueues são onde o trabalho de verdade acontece. Elas representam um dos mecanismos mais importantes do Zephyr para firmware industrial e IoT de produção, justamente por permitirem processamento assíncrono fora de ISR, com controle de contexto, prioridade e isolamento.
Em termos arquiteturais, pense em k_work como estágios de um pipeline, e em k_work_q como linhas de produção.
4.1 O que é uma Workqueue, de fato
Uma workqueue no Zephyr é:
- Uma thread dedicada, gerenciada pelo kernel.
- Um executor de tarefas (
work items). - Um mecanismo para adiar e ordenar processamento.
- Uma fronteira segura entre eventos e lógica pesada.
Por padrão, o Zephyr oferece:
system workqueue(k_sys_work_q)- Workqueues customizadas, criadas pelo desenvolvedor.
⚠️ Usar sempre a system workqueue funciona em exemplos,
mas firmware de produção quase sempre cria workqueues próprias.
4.2 O erro comum: jogar tudo na system workqueue
Exemplo ruim (mas frequente):
k_work_submit(&my_work);
Sem controle de:
- Prioridade
- Latência
- Concorrência
- Starvation
- Dependência entre módulos
Resultado: um subsistema lento pode travar todo o sistema.
👉 Regra prática:
Se o trabalho é crítico ou específico, ele merece sua própria workqueue.
4.3 Criando uma Workqueue dedicada
Definição da workqueue
#include <zephyr/kernel.h>
#define WORKQ_STACK_SIZE 1024
#define WORKQ_PRIORITY 5
K_THREAD_STACK_DEFINE(workq_stack, WORKQ_STACK_SIZE);
static struct k_work_q processing_workq;
Inicialização
void workqueue_init(void)
{
k_work_queue_start(
&processing_workq,
workq_stack,
K_THREAD_STACK_SIZEOF(workq_stack),
WORKQ_PRIORITY,
NULL
);
}
Agora você tem um executor isolado, previsível e controlado.
4.4 Criando um work item
Um k_work representa uma unidade de processamento:
static void process_work_handler(struct k_work *work)
{
/* Aqui é contexto de thread normal */
parse_data();
run_algorithm();
store_results();
}
K_WORK_DEFINE(process_work, process_work_handler);
Submissão do trabalho:
k_work_submit_to_queue(&processing_workq, &process_work);
✔ Pode usar mutex
✔ Pode alocar memória
✔ Pode acessar drivers
✔ Pode logar
✔ Pode chamar APIs bloqueantes
4.5 Integrando eventos → workqueues
Agora conectamos tudo:
ISR → Timer → Workqueue
static void gpio_event_isr(const struct device *dev,
struct gpio_callback *cb,
uint32_t pins)
{
/* Apenas dispara o pipeline */
k_work_submit_to_queue(&processing_workq, &process_work);
}
Ou, de forma mais limpa:
static void gpio_event_isr(...)
{
k_sem_give(&event_sem);
}
void event_thread(void)
{
while (1) {
k_sem_take(&event_sem, K_FOREVER);
k_work_submit_to_queue(&processing_workq, &process_work);
}
}
👉 Importante:
Nunca submeta trabalho pesado diretamente da ISR se ele puder crescer no futuro.
4.6 Workqueues como estágios de pipeline
Em sistemas reais, você verá algo assim:
[ ISR ]
↓
[ Timer / Debounce ]
↓
[ Workqueue: Aquisição ]
↓
[ Workqueue: Processamento ]
↓
[ Workqueue: Comunicação ]
Cada estágio:
- Tem sua própria prioridade.
- Pode falhar sem quebrar o sistema.
- Pode ser monitorado.
- Pode ser desligado ou reiniciado.
Isso é arquitetura de firmware, não apenas código.
4.7 Regra de ouro das Workqueues
✔ ISR nunca processa
✔ Timer nunca processa
✔ Workqueue processa
✔ Workqueues isolam falhas
✔ Workqueues tornam o sistema escalável
Quando você domina workqueues, o firmware deixa de ser linear e passa a ser industrialmente robusto.