Skip to content

Cap 05 ‐ Semáforos para Sincronização e Acesso a Recursos

Mateus Pincho de Oliveira edited this page Sep 24, 2025 · 2 revisions

Semáforos para Sincronização e Acesso a Recursos

Condições de corrida acontecem quando duas ou mais tarefas competem pelo acesso a recursos que são compartilhados entre si. Aquele que o acessa primeiro vence a corrida, mas impede a execução correta das tarefas seguintes. É necessário a utilização de mecanismos de sincronização, como mutexes e semáforos, para controlar o acesso de recursos e sincronizar processos concorrentes.

Mutexes para Exclusão Mútua

Um Mutex (de Mutual Exclusion) funciona como uma "chave" ou um "token de permissão" para uma seção crítica.

  • Apenas uma tarefa pode "possuir" o mutex por vez.
  • Se uma tarefa quer entrar na seção crítica, ela deve primeiro "pegar" (take) o mutex.
  • Se o mutex já estiver em posse de outra tarefa, a tarefa que tentou pegá-lo será bloqueada (colocada em uma fila de espera) até que o mutex seja "devolvido" (given).
  • Ao sair da seção crítica, a tarefa deve devolver o mutex para que outras possam usá-lo

Você cria um mutex usando a função xSemaphoreCreateMutex().

#include "semphr.h" // Cabeçalho necessário

// Declara uma variável para guardar o handle do mutex
SemaphoreHandle_t xMeuMutex;

void setup() {
    // Cria o mutex antes de iniciar o escalonador
    xMeuMutex = xSemaphoreCreateMutex();
    
    if (xMeuMutex != NULL) {
        // Mutex criado com sucesso
    }
}

Como Pegar e Devolver a "Chave" (Mutex)

As duas funções principais para interagir com um mutex são xSemaphoreTake() e xSemaphoreGive().

xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)

  • xSemaphore: O handle do mutex que você quer pegar.
  • xTicksToWait: O tempo máximo que a tarefa deve esperar (bloquear) se o mutex não estiver disponível.
  • Usar portMAX_DELAY fará com que a tarefa espere indefinidamente, o que é o comportamento mais comum para mutexes.
  • Retorno: Retorna pdPASS (ou pdTRUE) se o mutex foi pego com sucesso. Retorna pdFAIL (ou pdFALSE) se o tempo de espera (xTicksToWait) expirou antes do mutex se tornar disponível.

xSemaphoreGive(SemaphoreHandle_t xSemaphore)

  • xSemaphore: O handle do mutex que você está devolvendo.
  • Uma tarefa nunca deve devolver um mutex que ela não pegou.

Semáforos Binários para Sincronização

Embora a API seja quase a mesma, a finalidade de um Semáforo Binário é diferente. Ele para sincronizar a execução entre tarefas ou entre uma tarefa e uma interrupção (ISR).

Pense nele como um sinalizador. Uma tarefa pode esperar por um evento (como um botão sendo pressionado ou um dado chegando pela serial) e só continuará a execução depois que outra tarefa ou uma ISR "sinalizar" que o evento ocorreu.

Para criar um semáforo, você usa a função xSemaphoreCreateBinary().

SemaphoreHandle_t xSemaforoEvento;
xSemaforoEvento = xSemaphoreCreateBinary();

Dica: Um semáforo binário é criado no estado "vazio" ou "pego". Você precisa explicitamente dar (give) o semáforo antes que qualquer tarefa possa pegá-lo (take).

Exemplo: Sincronizar uma tarefa com uma interrupção

// Assumindo que temos uma interrupção de pino configurada para um botão
SemaphoreHandle_t xBotaoPressionadoSemaforo;

// ESTA É A ROTINA DE INTERRUPÇÃO (ISR)
void isr_callback_botao() {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    // 'Give' o semáforo para desbloquear a tarefa de processamento.
    // Use a versão "FromISR" que é segura para interrupções.
    xSemaphoreGiveFromISR(xBotaoPressionadoSemaforo, &xHigherPriorityTaskWoken);

    // Se a tarefa desbloqueada tiver maior prioridade que a atual, força uma
    // troca de contexto ao sair da ISR.
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

// ESTA É A TAREFA QUE ESPERA PELO EVENTO
void tarefaProcessaBotao(void *p) {
    for(;;) {
        // A tarefa bloqueia aqui indefinidamente até a ISR dar o semáforo.
        if (xSemaphoreTake(xBotaoPressionadoSemaforo, portMAX_DELAY) == pdPASS) {
            // A ISR sinalizou!
            printf("Botão foi pressionado! Processando...\n");
            // ... faz o processamento demorado aqui ...
        }
    }
}

void main() {
    // ...
    xBotaoPressionadoSemaforo = xSemaphoreCreateBinary();
    // ... configura a interrupção do pino para chamar isr_callback_botao() ...
    
    xTaskCreate(tarefaProcessaBotao, "TaskBotao", 256, NULL, 2, NULL); // Prioridade maior
    vTaskStartScheduler();
}

A Fila de Espera e Bloqueio de Tarefas

O Kernel do FreeRTOS adiciona tarefas na fila de espera quando uma tarefa chama xSemaphoreTake() e o semáforo (ou mutex) não está disponível. O parâmetro xTicksToWait controla este comportamento.

  • xTicksToWait = 0: A tarefa não será bloqueada. A função verifica o semáforo uma vez. Se estiver disponível, ela o pega e retorna pdPASS. Se não, ela retorna pdFAIL imediatamente e a tarefa continua sua execução. Útil para "tentar pegar" (polling). - xTicksToWait > 0 (e < portMAX_DELAY): A tarefa será colocada em uma fila de espera específica para aquele semáforo e entrará no estado Bloqueado. Ela não consumirá nenhum tempo de CPU. Se o semáforo for devolvido por outra tarefa antes que o tempo xTicksToWait expire, a tarefa é movida para o estado Pronto, pega o semáforo e continua a execução. Se o tempo expirar, a tarefa também é movida para o estado Pronto, mas xSemaphoreTake() retorna pdFAIL.
  • xTicksToWait = portMAX_DELAY: Igual ao anterior, mas a tarefa esperará na fila indefinidamente, sem timeout.

Quando xSemaphoreGive() é chamado e há tarefas na fila de espera daquele semáforo, o FreeRTOS automaticamente remove a tarefa de maior prioridade da fila (ou a que esperou por mais tempo, se as prioridades forem iguais) e a move para o estado "Pronto".