Uso prático do mutex padrão no FreeRTOS (com exemplos em C)
O mutex padrão do FreeRTOS é a ferramenta correta para proteger recursos compartilhados quando existe concorrência entre tarefas com prioridades diferentes. Nesta seção, vamos analisar um exemplo realista e didático, explorando não apenas como usar o mutex, mas por que cada decisão de projeto foi tomada.
Cenário do exemplo
Considere um sistema com duas tarefas:
- TaskSensor: coleta dados de um sensor e registra informações em uma interface serial (UART).
- TaskComms: envia dados pela mesma UART para um módulo externo.
A UART é um recurso compartilhado. Sem proteção adequada, as mensagens podem se misturar, causando corrupção de dados e dificultando o debug. Como ambas as tarefas podem ter prioridades diferentes, precisamos também evitar inversão de prioridade.
Criação do mutex
O mutex é criado uma única vez, normalmente na função de inicialização do sistema:
#include "FreeRTOS.h"
#include "semphr.h"
static SemaphoreHandle_t xUartMutex;
void SystemInitResources(void)
{
xUartMutex = xSemaphoreCreateMutex();
if (xUartMutex == NULL)
{
/* Falha ao criar o mutex: memória insuficiente */
for (;;);
}
}
Aqui, xSemaphoreCreateMutex() cria um mutex com herança de prioridade habilitada automaticamente. Internamente, o contador inicial é 1, indicando que o recurso está livre.
Uso do mutex em uma tarefa
Agora, observe como a tarefa protege o acesso à UART:
void TaskSensor(void *pvParameters)
{
for (;;)
{
/* Aguarda indefinidamente até obter o mutex */
if (xSemaphoreTake(xUartMutex, portMAX_DELAY) == pdTRUE)
{
UART_SendString("Sensor: leitura iniciada\r\n");
UART_SendString("Sensor: valor = 123\r\n");
/* Libera o mutex após o uso */
xSemaphoreGive(xUartMutex);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
O uso de portMAX_DELAY indica que a tarefa está disposta a aguardar até que o recurso esteja disponível. Em sistemas de tempo real estrito, esse valor pode ser substituído por um timeout bem definido, dependendo do requisito temporal da aplicação.
A chamada xSemaphoreGive() deve ser feita pela mesma tarefa que obteve o mutex. Caso contrário, o comportamento é indefinido e pode comprometer a integridade do sistema.
Análise temporal e boas práticas
Note que apenas as chamadas diretamente relacionadas à UART estão dentro da região protegida pelo mutex. Qualquer processamento adicional, como cálculo de dados ou acesso a sensores, ocorre fora da seção crítica. Essa prática reduz o tempo de posse do mutex e melhora o determinismo do sistema.
Além disso, não há chamadas bloqueantes (como vTaskDelay) dentro da seção crítica. Bloquear uma tarefa enquanto ela mantém um mutex é uma das causas mais comuns de latência excessiva e comportamento imprevisível.
Interação com prioridades
Se TaskSensor for de baixa prioridade e TaskComms for de alta prioridade, o FreeRTOS automaticamente elevará a prioridade de TaskSensor caso TaskComms fique bloqueada aguardando o mutex. Esse ajuste temporário garante que a UART seja liberada o mais rápido possível, sem intervenção manual do desenvolvedor.
Na próxima seção, vamos explorar um cenário mais complexo, onde o mutex recursivo é necessário, analisando quando ele deve ser usado e quais armadilhas devem ser evitadas.