RTOS: Semáforos para sincronização de tarefas

RTOS - Sincronização de tarefas com Semáforos

Sistemas operacionais multi-tarefas sofrem com um grande problema, a concorrência de recursos. Podemos ter várias tarefas usando um mesmo recurso, como um periférico, entretanto, ele só pode fazer uma coisa por vez. E isso gera problemas de concorrência, quando duas ou mais tarefas precisam do mesmo recurso ao mesmo tempo.

 

O método mais simples para resolver esses problemas é com semáforos, que são como variáveis 0 e 1. Entretanto, há mais recursos que permitem nosso código fluir incrivelmente melhor, como Timeout e Atomicidade.

 

 

O que são semáforos?

 

Semáforos, em programação paralela, são um tipo abstrato de dado que visa a sincronização de tarefas restringindo o acesso de um recurso ou comunicação entre tarefas e ISRs. Os semáforos trabalham de uma forma muito simples e parecida com os semáforos das vias urbanas, onde tentam sincronizar o fluxo de carros.

 

Os semáforos contam com duas operações básicas, que é quando a tarefa pega (take) o semáforo para uso e depois o libera (give) para outra tarefa ou ela mesmo usá-lo, que estão descritas abaixo e nas figuras 1 e 2.

 

Take (obter o semáforo): Se o semáforo tiver o valor >=1 que é quando disponível, a tarefa poderá obtê-lo, já que o semáforo está disponível para uso e a função retornará TRUE. Caso o semáforo esteja indisponível, a função retornará FALSE. Ao obter o semáforo, o valor dele é decrementado em 1.

 

Give (liberar o semáforo): Normalmente após a tarefa obter o semáforo com o “take”, no fim de seu processamento, deve liberar o semáforo para outras tarefas o utilizarem. Ao liberar o semáforo, o valor dele é incrementado em 1.

 

Atomicidade: Em sistemas multi-tarefas ou multi-cores, temos um problema comum que é quando duas tarefas ou processadores tentam usar o mesmo recurso e isso pode gerar problemas. Um processador pode obter um semáforo exatamente ao mesmo tempo que o outro processador também está obtendo, gerando conflitos. Um item ser atômico nos diz que ele é “indivisível”, ou seja, dois processadores ou tarefas não podem entrar na mesma região do código ao mesmo tempo, conhecido como “regiões críticas”.

 

Na figura 1, a “Task” está esperando a ISR liberar o semáforo para prosseguir seu processamento e, enquanto isso não ocorre, a “Task” permanece em espera (bloqueada) permitindo que outras tarefas sejam executadas.

 

Semáforos - Tarefa esperando e utilizando o semáforo.
Figura 1 - Tarefa esperando e utilizando o semáforo.

 

Na figura 2, a ”Task2” está esperando o semáforo ser liberado para ela mesmo iniciar seu processamento, enquanto isso, ela permanece “dormindo (bloqueada)”, permitindo que outras tarefas continuem sendo executadas.

 

Tarefa em espera pelo semáforo.
Figura 2 - Tarefa em espera pelo semáforo.

 

 

Tipos de semáforos

 

Binário: É o mais simples dos semáforos, como uma única variável valendo “1“ ou “0“. Quando em “1”, permite que tarefas o obtenham e quando “0”, a tarefa não conseguirá obtê-lo.

 

Mutex: Similar ao binário, entretanto, implementa a herança de prioridade. A herança de prioridade é uma implementação do FreeRTOS que aumenta a prioridade da tarefa que está usando o semáforo quando é interrompida por outra tarefa que também está tentando usar o semáforo. Isso garante que a tarefa que está usando o semáforo tenha a maior prioridade entre todas as outras que tentarem obtê-lo. Este método tenta minimizar o problema de inversão de prioridades.

 

Counting: Similar ao binário mas conta com uma fila de valores, similar a um vetor (array). É muito utilizado para minimizar problemas entre ISR e os outros semáforos, já que se ocorrer mais de uma ISR antes que a tarefa o obtenha, perderemos essa ISR visto que os outros semáforos só têm um ”espaço”. Utilizando o semáforo counting, não perdemos a ISR já que temos vários “espaços” (figura 3), sendo similar a uma Queue que vamos aprender no próximo artigo.

 

Semáforo Counting.
Figura 3 - Semáforo Counting.

 

Na figura 4, a “Task1” e “Task2” estão esperando pelo uso do semáforo binário que é liberado pela ISR, porém, só uma conseguirá executar por vez, que será a de maior prioridade. Ou seja, podemos causar conflitos (Starvation) no sistema, já que apenas uma tarefa de várias irá obter o uso do semáforo, uma solução para isso é o semáforo counting ou criar semáforos separados para cada tarefa.

 

  • Nos tempos t2 e t3 as tarefas começam a esperar o semáforo com um Timeout “infinito” (será explicado na prática).

 

  • A ISR (interrupção por hardware) libera o semáforo. Qualquer prioridade de ISR é maior que qualquer prioridade do FreeRTOS.

 

  • Logo que o semáforo é liberado, a tarefa de maior prioridade (Task1) será escolhida pelo scheduler, ocasionando Starvation na Task2.

 

Concorrência de tarefas pelo semáforo binário.
Figura 4 - Concorrência de tarefas pelo semáforo binário.

 

Vamos finalmente botar a mão na massa e testar o semáforo binário para sincronizar tarefas a partir de uma ISR. Como no artigo anterior, vamos comparar a teoria com a prática. O método que faremos é conhecido como DIH (Deferred Interrupt Handling), onde buscamos remover o processamento da ISR e atribuí-lo a uma tarefa, visto que ISR deve ser o mais rápido possível.

 

Nosso código terá uma tarefa responsável por analisar o semáforo binário e vamos trabalhar com o Timeout do semáforo, ou seja, se o semáforo não estiver disponível dentro do tempo definido, efetuará uma ação (figura 5).

 

  • Quando a tarefa obter o semáforo dentro do tempo definido, será feito um Toggle no GPIO23.

 

  • Quando a tarefa não obter o semáforo dentro do tempo definido, será feito um Toggle no GPIO22.

 

  • t1, t2, t3, t4, t9 e t10 são marcações quando o Timeout + Delay expirou (300 ms).

 

Funcionamento teórico do código.
Figura 5 - Funcionamento teórico do código.

 

Código do projeto:

 

Agora, vamos ver o que o analisador lógico nos diz com a figura 6:

 

Funcionamento prático do código.
Figura 6 - Funcionamento prático do código.

 

Veja que funcionou como o esperado, o Toggle do GPIO22 está em 300 ms, quando ocorre o Timeout do semáforo mais o delay (canto superior direito) e quando ocorre a ISR, o semáforo é liberado e a tarefa efetua o Toggle do GPIO23.

 

Além da DIH, os semáforos são muito utilizados para sincronização entre tarefas que tentam obter o mesmo recurso, como um periférico, porém, ele só pode efetuar uma ação (ou menos que a quantidade de tarefas tentando usá-lo) por vez. Vamos entender melhor como isso funciona na figura 7.

 

Tarefas concorrendo pelo periférico.
Figura 7 - Tarefas concorrendo pelo periférico.

 

Podemos observar que o periférico é utilizado em todo o tempo de análise, entretanto, por tarefas diferentes que são sincronizadas por um semáforo binário:

 

  • No tempo t1, a Task2 obtém o semáforo para utilizar o periférico.

 

  • No tempo t2, a Task1 tenta obter o semáforo, porém, não está disponível e a tarefa entra em espera até que seja liberado.

 

  • No tempo t4, a Task2 libera o semáforo e a Task1 acordará automaticamente para obtê-lo.

 

  • No tempo t5, a Task1 libera o semáforo para outras tarefas utilizarem.

 

No próximo artigo desta série, vamos aprender sobre Filas de dados (Queues), que funcionam similarmente ao semáforo, entretanto, podemos passar valores! Isso permite, além de algo parecido com o semáforo, comunicação entre tarefas ou ISRs.

 

 

Saiba mais

 

Desenvolvendo um RTOS: Introdução

Implementando elementos de RTOS no Arduino

Criando um projeto no IAR com o FreeRTOS

 

 

Referências

 

https://freertos.org/Documentation/RTOS_book.html

http://esp-idf.readthedocs.io/en/latest/api-reference/system/freertos.html

Outros artigos da série

<< RTOS: Scheduler e TarefasRTOS: Uso de Queue para sincronização e comunicação de tarefas >>
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

José Morais
Estudante de Engenharia da Computação pela USC, pretende se aprimorar e fazer a diferença nesta imensa área da tecnologia. Apaixonado por IoT, sistemas embarcados, microcontroladores e integração da computação nos mais diversos fins práticos e didáticos.

1
Deixe um comentário

avatar
 
1 Comment threads
0 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
silva Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
silva
Visitante
silva

E eu sofrendo com gêmeos do pic