Skip to content
By Corsi
Picture of Corsi
Corsi

Semáforos / semaphore

TIP

Para mais informações, leia o site do FreeRTOS sobre semáforos binários:

Semáforos e mutex são uma ferramenta fundamental de sincronização em programação concorrente e sistemas operacionais, utilizados para gerenciar o acesso a recursos compartilhados por múltiplas tarefas. Eles funcionam como um contador que regula quantas tarefas podem acessar um determinado recurso simultaneamente.

Quando uma tarefa deseja acessar um recurso que é gerenciado por um semáforo, ela precisa realizar uma operação de "espera" (wait). Esta operação verifica se o contador do semáforo é maior que zero, o que indica que o recurso está disponível. Se for o caso, a tarefa decrementa o contador e ganha acesso ao recurso. Caso contrário, se o contador for zero, isso indica que não há recursos disponíveis no momento, e a tarefa é colocada em um estado de espera até que um recurso seja liberado.

Para que serve?

O semáforo veio para substituir o uso de flags. No sistema bare-metal, nós fazíamos algo como:

c
void btn_callback(uint gpio, uint32_t events) {
    if (events == 0x4) {         // fall edge
        flag_f_r = 1;
    }
} 

void main(void) {
 // ....
 
    while(1) { 
        if(flag_f_r) {
            // faz alguma coisa
            flag_f_r = 0;
        }
    }
}

Agora com semáforo iremos fazer o seguinte:

c
void btn_callback(uint gpio, uint32_t events) {
    if (events == 0x4) {         // fall edge
        xSemaphoreGiveFromISR(xSemaphore, 0);
    }
} 

void task_main(void) {
 // ....

    while(1) { 

        if(xSemaphoreTake(xSemaphore, pdMS_TO_TICKS(100))) {
            // faz alguma coisa
        } else { 
            // cai aqui se o semáforo não for liberado em 100 ms!
        }
        
    }
}

Timeout

É o tempo que iremos esperar por um dado na fila (a task dorme enquanto espera o dado).

ISR

FreeRTOS é projetado para sistemas embarcados onde a manipulação eficiente de interrupções é crucial para o desempenho em tempo real. Quando uma interrupção ocorre, a execução normal do sistema é pausada e a ISR correspondente é executada. Após a conclusão da execução da ISR, o sistema retoma sua operação normal. Como as ISRs podem interromper a execução de tarefas regulares a qualquer momento, é essencial minimizar o tempo gasto dentro de uma ISR para manter a capacidade de resposta do sistema.

Sempre que forem manipular o RTOS de uma interrupção/callback de hardware, vocês devem utilizar o conjunto de funções que terminam em FromISR. O sufixo FromISR nos nomes das funções significa "From Interrupt Service Routine" (De Rotina de Serviço de Interrupção), indicando que essas funções são seguras para serem chamadas de uma ISR (Interrupt Service Routine).

No caso do semáforo, vai ser muito comum liberar o semáforo de uma ISR, como no exemplo a seguir de um alarme de timer:

c
bool timer_0_callback(repeating_timer_t *rt) {
    xSemaphoreGiveFromISR(xSemaphore, 0);
    return true; // keep repeating
}

Mas se for liberar o semáforo de uma task_1 para outra task_2 usar:

c
void task_1(void) {
    // ..
    xSemaphoreGive(xSemaphore);

}

WARNING

Muitos alunos confundem e acham que devem chamar FromISR quando estão recebendo um dado de uma ISR, mas isso não é verdade. As funções com o sufixo devem ser usadas quando chamadas de uma ISR.

Dicas de uso

"Regra de ouro"

O semáforo vai ser utilizado no lugar do uso de flag.

Para criarmos e usarmos um semáforo é necessário:

  1. Criar a variável global que representará o semáforo
    • SemaphoreHandle_t xSemaphore;
  2. Criar o semáforo (na função main)
  3. Liberar o semáforo
  4. Esperar pelo semáforo

Snippets

Snippets de código usando semáforo

Task-Task

Analise o código a seguir, onde um semáforo binário é liberado de uma task para outra:

c
/* ... Código omitido */

SemaphoreHandle_t xSemaphore_r;

void btn_1_task(void *p) {
  gpio_init(BTN_PIN_R);
  gpio_set_dir(BTN_PIN_R, GPIO_IN);
  gpio_pull_up(BTN_PIN_R);

  while (true) {
    if (!gpio_get(BTN_PIN_R)) {
      while (!gpio_get(BTN_PIN_R)) {
        vTaskDelay(pdMS_TO_TICKS(1));
      }
      xSemaphoreGive(xSemaphore_r);
    }
  }
}

void led_1_task(void *p) {
  gpio_init(LED_PIN_R);
  gpio_set_dir(LED_PIN_R, GPIO_OUT);

  int delay = 250;
  int status = 0;

  while (true) {

    if (xSemaphoreTake(xSemaphore_r, pdMS_TO_TICKS(500)) == pdTRUE) {
      gpio_put(LED_PIN_R, 1);
      vTaskDelay(pdMS_TO_TICKS(delay));
      gpio_put(LED_PIN_R, 0);
      vTaskDelay(pdMS_TO_TICKS(delay));
    }
  }
}

int main() {
  /* ... Código omitido */
  
  xSemaphore_r = xSemaphoreCreateBinary();

  xTaskCreate(led_1_task, "LED_Task 1", 256, NULL, 1, NULL);
  xTaskCreate(btn_1_task, "BTN_Task 1", 256, NULL, 1, NULL);

  /* ... Código omitido */

IRQ - TASK

c
SemaphoreHandle_t xSemaphore;

void but_callback(void){
  // libera semáforo 
  xSemaphoreGiveFromISR(xSemaphore, 0);
}

static void task_led(void *pvParameters){
  init_led1();   // inicializa LED1
  init_but1();   // inicializa botao 1, com callback
  
  for (;;) {
      // aguarda por até 500 ms pelo se for liberado entra no if
      if( xSemaphoreTake(xSemaphore, 500 / portTICK_PERIOD_MS) == pdTRUE ){
        LED_Toggle(LED0);
      }
    }
}

void main(void) {
  // .... //

  // cria semáforo binário
  xSemaphore = xSemaphoreCreateBinary();

  // verifica se semáforo foi criado corretamente
  if (xSemaphore == NULL)
      printf("falha em criar o semaforo \n");

TIP

  1. O semáforo deve ser sempre alocado antes do seu uso, caso alguma parte do firmware tente liberar o semáforo antes dele ser criado (xSemaphoreCreateBinary()) o código irá travar.
  2. Você deve usar fromISR SEMPRE que liberar um semáforo de uma interrupção, caso contrário usar a função xSemaphoreGive()