RTOS: Scheduler e Tarefas

Este artigo é talvez o mais importante e a base de tudo o que veremos pela frente, leia com atenção. Vamos começar definindo alguns termos do scheduler e, logo depois, como uma tarefa se comporta dentro de um RTOS. Pode ser que o entendimento de ambas partes faça mais sentido para você em ordem inversa, então leia sobre as tarefas antes do scheduler.

 

 

Scheduler

 

Scheduler (agendador ou escalonador) é o grande responsável por administrar as tarefas que irão obter o uso da CPU. Há diversos algoritmos para que o scheduler decida a tarefa e você também pode escolher o mais apropriado ao seu embarcado, como por exemplo: RR (Round Robin), SJF (Shortest Job First) e SRT (Shortest Remaining Time). Não entraremos em detalhes sobre eles.

 

Formas de trabalho do scheduler

 

Preemptivo: São algoritmos que permitem uma tarefa em execução ser interrompida antes do tempo total de sua execução, forçando a troca de contexto. Os motivos de interrupção são vários, desde uma tarefa com maior prioridade ou o Time Slicing (explicado logo abaixo):

 

Podemos observar na figura 1 como a preempção e Time Slicing funciona. As tarefas são interrompidas pois há tarefas com maiores prioridades prontas para serem executadas.

 

A tarefa “Idle” sempre estará presente, onde o RTOS executa alguns gerenciamentos do sistema, como gerenciamento de memória RAM. É a tarefa de menor prioridade (0), logo, todas suas tarefas restantes no sistema são aconselhadas a terem prioridade maior ou igual a 1.

 

Scheduler preemptivo e Time Slicing alterando tarefas.
Figura 1 - Scheduler preemptivo e Time Slicing alterando tarefas.

 

Cooperativo: São algoritmos que não permitem uma tarefa em execução ser interrompida, as tarefas precisam cooperar para que o sistema funcione. A tarefa em execução continuará em execução até que seu tempo total de execução termine e ela mesmo force a troca de contexto, assim permitindo que outras tarefas obtenham uso do CPU.

 

Podemos observar na figura 2, caso outras tarefas estejam prontas para serem executadas, não serão pelo simples fato de que a tarefa em atual execução deve exigir a troca de contexto ou terminar sua execução.

 

Scheduler cooperativo alterando tarefas.
Figura 2 - Scheduler cooperativo alterando tarefas.

 

Troca de contexto: É o ato do S.O. salvar ou recuperar o estado do CPU, como registradores, principalmente o IP (Instruction Pointer). Isso permite que o S.O. retome o processamento da tarefa de onde foi interrompida.

 

Time Slicing: É o ato do S.O. dividir o tempo de uso do CPU entre as tarefas. Cada tarefa recebe uma fatia desse tempo, chamado quantum, e só é permitida ser executada por no máximo o tempo de um quantum. Se após o término do quantum a tarefa não liberou o uso do CPU, é forçada a troca de contexto e o scheduler será executado para decidir a próxima tarefa em execução. Caso não haja outra tarefa para ser escolhida, incluindo motivos de prioridade, o scheduler retornará para a mesma tarefa anterior à preempção. Você entenderá melhor ao decorrer deste post. 

 

Ao término de todos quantum’s, é efetuada a troca de contexto e o scheduler decidirá a próxima tarefa em execução, perdendo um pequeno tempo para si, já que irá interromper a atual tarefa em execução. O Time Slicing é indicado com tarefas de mesma prioridade, já que sem o uso, as tarefas de mesma prioridade terão tempos de execução diferentes. No ESP32 em 240 MHz, o tempo para o scheduler decidir a tarefa e esta entrar em execução é <=10 us.

 

No FreeRTOS, o período do Time Slicing pode ser configurado, sendo aconselhável valores entre 10 ms e 1 ms (100-1000 Hz), cabe a você escolher o que melhor atende ao seu projeto, podendo ultrapassar os limites aconselháveis.

 

Em todos artigos desta série, utilizaremos o FreeRTOS preemptivo com Round Robin e Time Slicing em 1000 Hz, que é o padrão do nosso microcontrolador ESP32.

 

 

Tarefas

 

As tarefas (Task) são como mini programas dentro do nosso embarcado, normalmente são loops infinitos que nunca retornarão um valor, onde cada tarefa efetua algo específico. Como já visto no scheduler, ele que fará todas nossas tarefas serem executadas de acordo com sua importância, e assim, conseguimos manter inúmeras tarefas em execução sem muitos problemas.

 

Em embarcados de apenas uma CPU, só existirá uma tarefa em execução por vez, porém, o scheduler fará a alternância de tarefas tão rapidamente, que nos dará a impressão de que todas estão ao mesmo tempo.

 

Prioridades

 

Toda tarefa tem sua prioridade, podendo ser igual a de outras. A prioridade de uma tarefa implica na ordem de escolha do scheduler, já que ele sempre irá escolher a tarefa de maior prioridade para ser executada, logo, se uma tarefa de alta prioridade sempre estiver em execução, isso pode gerar problemas no seu sistema, chamado Starvation (figura 4), já que as tarefas de menor prioridade nunca executarão até que sejam a de maior prioridade para o scheduler escolher. Por esse motivo, é sempre importante adicionar delay’s no fim de cada tarefa, para que o sistema tenha um tempo para “respirar”.

 

A menor prioridade do FreeRTOS é 0 e aumentará até o máximo explícito nos arquivos de configuração. Uma tarefa mais prioritária é a que têm o número maior que a outra, por exemplo na figura 3 veja as prioridades:

  • Idle task: 0;
  • Task1: 1;
  • Task2: 5.

 

A tarefa de maior prioridade é a ”Task2” e a menor é a ”Idle task”.

 

Tarefa de prioridade alta gerando Starvation.
Figura 3 - Tarefa de prioridade alta gerando Starvation.

 

No caso de tarefas com mesma prioridade, nosso scheduler com Round Robin e Time Slicing tentará deixar todas tarefas com o mesmo tempo de uso da CPU, como mostrado na figura 4.

 

Tarefas de prioridades iguais dividindo uso do CPU.
Figura 4 - Tarefas de prioridades iguais dividindo uso do CPU.

 

Estados

 

As tarefas sempre estarão em algum estado. O estado define o que a tarefa está fazendo dentro do RTOS no atual tempo de análise do sistema.

 

Bloqueada (Blocked): Uma tarefa bloqueada é quando está esperando que algum dos dois eventos abaixo ocorram. Em um sistema com muitas tarefas, a maior parte do tempo das tarefas será nesse estado, já que apenas uma pode estar em execução e todo o restante estará esperando por sua vez, no caso de sistemas single core.

 

  • Temporal (Timeout): Um evento temporal é quando a tarefa está esperando certo tempo para sair do estado bloqueado, como um Delay. Todo Delay dentro de uma tarefa no RTOS não trava o microcontrolador como em Bare Metal, o Delay apenas bloqueia a tarefa em que foi solicitado, permitindo todas outras tarefas continuar funcionando normalmente.

 

  • Sincronização (Sync): Um evento de sincronização é quando a tarefa está esperando (Timeout) a sincronização de outro lugar para sair do estado bloqueado, como Semáforos e Queues (serão explicados nos próximos artigos).

 

Suspensa (Suspended): Uma tarefa suspensa só pode existir quando for explicitamente solicitada pela função “vTaskSuspend()” e só pode sair desse estado também quando solicitado por “vTaskResume()”. É uma forma de desligar uma tarefa até que seja necessária mais tarde, entretanto, é pouco usado na maioria dos sistemas simples.

 

Pronta (Ready): Uma tarefa está pronta para ser executada quando não está nem bloqueada, suspensa ou em execução. A tarefa pode estar pronta após o Timeout de um Delay acabar, por exemplo, entretanto, ela não estará em execução e sim esperando que o scheduler à escolha. Este tempo para a tarefa ser escolhida e executar pode variar principalmente pela sua prioridade.

 

Execução (Running): Uma tarefa em execução é a tarefa atualmente alocada na CPU, é ela que está em processamento. Se seu embarcado houver mais de uma CPU, haverá mais de uma tarefa em execução ao mesmo tempo. O ESP32 conta com 3 CPU’s, entretanto, apenas 2 podem ser usadas pelo FreeRTOS na IDF, com isso, temos no máximo duas tarefas em execução ao mesmo tempo e o restante estará nos outros estados.

 

Observe na figura 5 o ciclo de vida de uma tarefa, atente-se que apenas tarefas prontas para execução podem entrar em execução diretamente. Uma tarefa bloqueada ou suspensa nunca irá para execução diretamente.

 

Ciclo de vida de uma tarefa.
Figura 5 - Ciclo de vida de uma tarefa.

 

Agora que já sabemos como funciona a base do scheduler e suas tarefas, vamos finalmente botar a mão na massa e testar essa maravilha funcionando na prática. Como já foi dito, vamos utilizar o ESP32 com a IDF e FreeRTOS para nossos testes, mas você pode testar em seu embarcado como ARM, talvez mudando apenas alguns detalhes!

 

Vamos fazer um teste simples com três tarefas para mostrar os principais itens acima. O princípio dessas três tarefas é apenas mostrar duas tarefas com mesma prioridade dividindo o uso da CPU enquanto a terceira tarefa é executada poucas vezes para mostrar a preempção.

 

Código do projeto:

 

 

Primeiramente, vamos analisar o que a teoria acima nos diz. Com duas tarefas compartilhando a mesma prioridade, nosso gráfico irá se comportar igual à figura 4, entretanto, nós temos uma terceira tarefa que tem prioridade maior que as outras duas. Essa última é executada menos frequentemente, porém por mais tempo que as outras. Nosso gráfico deve ficar parecido com a figura 6:

 

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

 

Observe que as duas tarefas de prioridades iguais (Task1 e Task2) compartilham o tempo de uso do CPU enquanto a outra tarefa (Task3) permanece bloqueada por um delay. Logo que a Task3 está pronta para ser executada, o scheduler irá executá-la até que encontre o delay novamente, que é quando a tarefa fica bloqueada, permitindo tarefas de prioridade menor serem executadas. Lembre-se que delay não trava todo o microcontrolador, apenas a tarefa em que foi solicitado.

 

Ok, vamos ver se a teoria bate com a prática? Cada tarefa faz um simples Toggle em um pino nos permitindo analisar com o Analisador Lógico nas figuras 7,8 e 9.

 

Aplicando as tarefas na prática.
Figura 7 - Aplicando as tarefas na prática.

 

As tasks 1 e 2 compartilham o uso do CPU até que a task3 seja desbloqueada e, quando isso ocorre, a task3 que tem maior prioridade, será executada até o término do seu código (quando encontra o delay). Observe o tempo de 200 ms no canto superior direito da figura 7, é nosso delay de 200 ms da tarefa 3, ou seja, ela realmente permaneceu bloqueada por 200 ms.

 

Dando um zoom, podemos observar as duas tarefas “brigando” pela CPU, veja na figura 8. No canto superior direito da figura 8, a tarefa usa a CPU por 1 ms, que é o tempo do Time Slicing configurado em 1000 Hz.

 

Tarefas de prioridade igual concorrendo pelo CPU.
Figura 8 - Tarefas de prioridade igual concorrendo pelo CPU.

 

Observando o gráfico numa base de tempo “humana” (figura 9), para nós parece que ambas estão executando simultaneamente, entretanto, podemos provar que as duas compartilham a CPU (figura 8), atente-se a isso em seus projetos de “Time Critical”.

 

Podemos ir mais longe já que nosso microcontrolador permite 2 das 3 CPU’s serem usadas pelo FreeRTOS. Atribuindo a tarefa 3 no outro CPU (1), podemos ver na figura 9 que ela executará realmente em simultâneo enquanto as tarefas 1 e 2 “brigam” pelo CPU (0).

 

FreeRTOS em embarcado Multi-Core.
Figura 9 - FreeRTOS em embarcado Multi-Core.

 

 

No próximo artigo desta série vamos abordar os semáforos, que funcionam para sincronizar eventos e tarefas dentro do RTOS.

 

 

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: Um ambiente multi-tarefas para Sistemas EmbarcadosRTOS: Semáforos para sincronizaçã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.

2
Deixe um comentário

avatar
 
1 Comment threads
1 Thread replies
2 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
José MoraisPedro Jorge Lima da Silva Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Pedro Jorge Lima da Silva
Membro
Pedro Jorge Lima da Silva

Qual ambiente de programação que vc está usando??