Padrões de Exclusão Mútua, Proteção de Recursos e Prevenção de Deadlock
Em FreeRTOS, concorrência não controlada não falha sempre — falha quando você menos espera. Esta seção trata de padrões que evitam corrupção de dados, inversão de prioridade e deadlocks, sem sacrificar o determinismo.
3.1 Mutex com Priority Inheritance
Problema resolvido:
Duas ou mais tarefas acessam o mesmo recurso (UART, I²C, SPI, memória compartilhada).
Risco clássico:
🔴 Priority Inversion — tarefa de baixa prioridade bloqueia uma de alta.
Ideia do padrão:
Usar Mutex (não semáforo) para herança de prioridade automática.
SemaphoreHandle_t uartMutex;
uartMutex = xSemaphoreCreateMutex();
Uso correto:
void UartTask(void *pvParameters)
{
for (;;)
{
if (xSemaphoreTake(uartMutex, portMAX_DELAY))
{
UART_Write("Hello RTOS\n");
xSemaphoreGive(uartMutex);
}
}
}
Por que não semáforo binário?
- Semáforos não fazem herança de prioridade
- Mutexes são projetados para exclusão mútua real
Regra de ouro:
🔒 Mutex protege recurso, não evento.
3.2 Gatekeeper Task (Padrão Fundamental em FreeRTOS)
Problema resolvido:
Múltiplas tarefas acessando um único periférico (UART, Flash, Display).
Ideia do padrão:
Apenas uma tarefa é dona do recurso. As demais pedem serviço via fila.
Este é um dos padrões mais importantes para firmware de produção.
Estrutura:
Tasks ──► Request Queue ──► Gatekeeper Task ──► Hardware
Mensagem de requisição:
typedef struct {
char message[64];
} UartRequest;
Gatekeeper:
void UartGatekeeperTask(void *pvParameters)
{
UartRequest req;
for (;;)
{
if (xQueueReceive(UartQueue, &req, portMAX_DELAY))
{
UART_Write(req.message);
}
}
}
Cliente:
UartRequest req = { .message = "Log message\n" };
xQueueSend(UartQueue, &req, portMAX_DELAY);
Vantagens:
- Elimina mutexes
- Elimina deadlocks
- Simplifica drivers
Trade-off:
Leve aumento de latência → ganho enorme em robustez.
3.3 Critical Section (Uso cirúrgico)
Problema resolvido:
Trechos extremamente curtos que não podem ser interrompidos.
taskENTER_CRITICAL();
sharedCounter++;
taskEXIT_CRITICAL();
O que acontece:
- Desabilita interrupções (localmente)
- Bloqueia escalonamento
Quando usar:
- Incrementos atômicos
- Flags simples
- Estruturas lock-free auxiliares
Quando NÃO usar:
🚫 Código longo
🚫 Acesso a drivers
🚫 Comunicação entre tarefas
Se você precisa de
printf()dentro de critical section, algo está errado.
3.4 Read–Modify–Write protegido (Anti-pattern clássico)
Problema comum (ERRADO):
if (flag == 0)
{
flag = 1;
}
Correção com mutex:
xSemaphoreTake(flagMutex, portMAX_DELAY);
if (flag == 0)
{
flag = 1;
}
xSemaphoreGive(flagMutex);
Ou melhor ainda:
Substituir flag por task notification ou event group.
3.5 Event Groups como Padrão de Coordenação
Problema resolvido:
Sincronizar múltiplas tarefas com condições compostas.
EventGroupHandle_t systemEvents;
#define EVT_NET_READY (1 << 0)
#define EVT_SENSOR_OK (1 << 1)
Sinalização:
xEventGroupSetBits(systemEvents, EVT_NET_READY);
Aguardar múltiplas condições:
xEventGroupWaitBits(
systemEvents,
EVT_NET_READY | EVT_SENSOR_OK,
pdFALSE,
pdTRUE,
portMAX_DELAY
);
Uso típico:
- Startup sequencial
- Estados globais do sistema
- Dependências entre subsistemas
Vantagem:
Muito mais expressivo que flags globais.
3.6 Deadlock Avoidance (Padrões de Prevenção)
Causa clássica de deadlock:
- Ordem inconsistente de aquisição de mutexes
Padrão de prevenção:
📐 Sempre adquirir recursos na mesma ordem
xSemaphoreTake(mutexA, portMAX_DELAY);
xSemaphoreTake(mutexB, portMAX_DELAY);
/* uso */
xSemaphoreGive(mutexB);
xSemaphoreGive(mutexA);
Alternativa mais segura:
Eliminar múltiplos mutexes → usar Gatekeeper.