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

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

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)

Em sistemas concorrentes, mutexes existem para resolver um problema muito específico:
garantir acesso exclusivo a um recurso compartilhado.

No Zephyr, assim como em qualquer RTOS sério, mutex não é um mecanismo de sincronização genérico, nem uma flag, nem um meio de comunicação. Ele existe exclusivamente para proteger regiões críticas.

Usar mutex fora desse propósito costuma gerar:

  • Latência excessiva
  • Deadlocks difíceis de detectar
  • Código frágil sob carga

Por isso, entender quando e como usar k_mutex é mais importante do que saber como declarar.


3.2 O que é um recurso compartilhado de verdade?

Um recurso merece um mutex quando:

  • É acessado por mais de uma thread
  • Pode ser interrompido no meio de uma operação
  • Não é naturalmente atômico

Exemplos reais:

  • Estruturas de dados globais
  • Buffers de comunicação
  • Drivers que não são reentrantes
  • Acesso a periféricos sem proteção interna

Exemplos que não precisam de mutex:

  • Variáveis locais
  • Dados somente leitura
  • Flags simples
  • Filas e eventos do kernel (já são thread-safe)

3.3 Declaração e inicialização de um Mutex

No Zephyr, mutexes são simples de declarar:

#include <zephyr/kernel.h>

K_MUTEX_DEFINE(meu_mutex);

Não há parâmetros adicionais nem inicialização manual.


3.4 Protegendo uma região crítica com k_mutex

Exemplo clássico: duas threads acessando uma estrutura compartilhada.

struct dados_compartilhados {
    int contador;
};

static struct dados_compartilhados dados;

void thread_a(void)
{
    while (1) {
        k_mutex_lock(&meu_mutex, K_FOREVER);
        dados.contador++;
        k_mutex_unlock(&meu_mutex);

        k_sleep(K_MSEC(100));
    }
}
void thread_b(void)
{
    while (1) {
        k_mutex_lock(&meu_mutex, K_FOREVER);
        dados.contador--;
        k_mutex_unlock(&meu_mutex);

        k_sleep(K_MSEC(150));
    }
}

Aqui o mutex garante:

  • Consistência dos dados
  • Exclusão mútua real
  • Ausência de race conditions

3.5 Mutex no Zephyr não é ISR-safe

Este ponto é crítico.

Nunca use k_mutex_lock() dentro de uma ISR.

Se você precisa proteger dados compartilhados entre:

  • ISR ↔ thread

Use:

  • Semáforos
  • Eventos
  • Filas
  • Ou técnicas de lock-free com seções críticas curtas

O Zephyr explicitamente proíbe mutex em ISR, e o kernel pode travar o sistema se isso ocorrer.


3.6 Inversão de prioridade: o problema clássico

Considere:

  • Thread A → prioridade baixa
  • Thread B → prioridade alta
  • Ambas usam o mesmo mutex

Cenário:

  1. Thread A trava o mutex
  2. Thread B tenta acessar o mutex e bloqueia
  3. Thread C (prioridade média) executa
  4. Thread A não roda → mutex nunca é liberado

Isso é inversão de prioridade, um problema grave em sistemas de tempo real.


3.7 Herança de prioridade no k_mutex

O Zephyr resolve isso automaticamente.

Quando uma thread de alta prioridade bloqueia em um mutex:

  • A thread dona do mutex herda temporariamente a prioridade mais alta
  • Executa mais rápido
  • Libera o mutex
  • Volta à prioridade original

Esse mecanismo é chamado de Priority Inheritance.

Ele é:

  • Automático
  • Transparente
  • Essencial para determinismo

Sem ele, sistemas RTOS seriam praticamente inutilizáveis em aplicações críticas.


3.8 Timeout em mutex: quando faz sentido?

Você pode usar timeout ao travar um mutex:

if (k_mutex_lock(&meu_mutex, K_MSEC(50)) == 0) {
    // região crítica
    k_mutex_unlock(&meu_mutex);
} else {
    printk("Falha ao obter mutex\n");
}

Use timeout quando:

  • O sistema precisa continuar operando
  • Um bloqueio infinito é inaceitável
  • Há fallback lógico

Evite timeout quando:

  • O recurso é essencial
  • A falha gera estado inconsistente

3.9 Deadlocks: como eles surgem

Deadlocks geralmente aparecem quando:

  • Dois mutexes são usados
  • A ordem de travamento não é consistente

Exemplo clássico de erro:

k_mutex_lock(&mutex_a, K_FOREVER);
k_mutex_lock(&mutex_b, K_FOREVER);

Em outra thread:

k_mutex_lock(&mutex_b, K_FOREVER);
k_mutex_lock(&mutex_a, K_FOREVER);

Resultado: sistema travado.


3.10 Boas práticas para uso de mutex no Zephyr

Algumas regras simples evitam 90% dos problemas:

  • Use mutex apenas para proteger dados
  • Mantenha a região crítica curta
  • Nunca durma (k_sleep) dentro de um mutex
  • Nunca chame APIs bloqueantes dentro do mutex
  • Sempre destrave o mutex no mesmo contexto

Essas práticas são especialmente importantes em sistemas com FreeRTOS + Zephyr híbridos ou migrações.


3.11 Quando não usar mutex

Não use mutex para:

  • Sinalização de eventos
  • Comunicação entre threads
  • Controle de fluxo
  • Substituir filas

Para esses casos:

  • k_sem
  • k_event
  • k_queue

São escolhas mais corretas e eficientes.


Related Post

FreeRTOS na Prática: Threads, Semáforos, Filas, Mutexes, Timers e Boas Práticas em Sistemas EmbarcadosFreeRTOS na Prática: Threads, Semáforos, Filas, Mutexes, Timers e Boas Práticas em Sistemas Embarcados

Este artigo faz parte de uma série técnica semanal sobre FreeRTOS e apresenta, de forma didática e rigorosa, os principais conceitos de concorrência e sincronização em sistemas embarcados. São explicados