Como utilizar as Threads no mbed OS

Threads no MBED OS Semáforos no mbed OS Recurso compartilhado
Este post faz parte da série mbed OS. Leia também os outros posts da série:

Olá meu caro leitor. Neste artigo seguiremos explorando o mbed OS da ARM voltado para uso em aplicações voltadas para Internet das Coisas (o popular IoT). Devido ao concurso promovido pelo Embarcados, irei rodar os exemplos na placa da NXP fornecida aos participantes do concurso, a LPCXpresso4337 com Shield. Nesse texto falarei sobre Threads ou Tasks no mbed OS.

 

 

Threads ou tasks

 

Threads ou Tasks de forma simplista podem ser definidas como subprogramas que possuem seu próprio espaço de contexto e variáveis, porém compartilhando o mesmo Address Space, ou seja, o mesmo mapa de memória físico de um processador. Diferentemente de um processo (presente em sistemas com windows e linux), que possui seu próprio contexto e conjunto de threads, porém com seu próprio Address Space, graças à presença do popular gerenciador de memória.

 

Como são subprogramas, cada Thread roda de forma independente, ou seja, possui seu próprio conjunto de variáveis e políticas de acesso ao hardware. Porém, quando falamos de dispositivos conectados (os chamados "sensor nodes"), chegamos em processadores que possuem núcleo único, sendo capazes de rodar uma thread por vez. Logo elas concorrem pelo acesso à CPU respeitando as políticas do sistema de agendamento do kernel (o famoso scheduler), de forma que quando estão prontas e a CPU está livre para recebê-las, as Threads são executadas até que o agendador decida se coloca ou não outra em seu lugar. Graças a esse recurso de sincronização, é possível escrever subprogramas responsáveis por diferentes ramos da aplicação principal do nosso dispositivo. Vejam a figura abaixo:

 

Dispositivos concorrendo o acesso à CPU - Threads no MBED OS
Figura 1: Dispositivos concorrendo o acesso à CPU

 

A figura ilustra uma aplicação onde temos diversos periféricos, que compõem como um todo o aplicativo do nosso dispositivo. Com o uso de Threads podemos dividir esse aplicativo em outros pequenos módulos, responsáveis por uma parte específica, como receber dados do Bluetooth, tratamento de strings NMEA de um GPS, e atualização de um Display, cada uma com sua prórpria região de memória e contexto relevante.

 

 

Threads no MBED-OS

 

Apesar da explicação ai possuir um viés teórico das aulas de sistemas operacionais, criar um sistema multi-thread no mbed OS é uma das tarefas mais simples dessa ferramenta, tudo isso porque o kernel e os serviços para esse tipo de recurso já estão implementados, bastando que o usuário instancie um objeto correspondente, configure e dispare sua execução.  Vamos montar o ambiente. Primeiro acesse o mbed compiler utilizando uma conta criada previamente, e em sua workspace clique em New. Você deve ver algo assim:

 

Criando um novo programa no mbed
Figura 2: Criando um novo programa no mbed

 

Agora selecione a plataforma Hexiwear (sim isso mesmo) ou de forma que na opção template o uso do mbed esteja disponível (utilizando a LPC4337 diretamente não funciona devido a um problema no compilador online):

 

Selecionando a plataforma Hexiwear no mbed
Figura 3: Selecionando a plataforma Hexiwear

 

Selecione a opção mbed OS Blinky xxx xxx xxx. Essa opção irá criar um projeto com todos os arquivos do mbed OS já configurados e prontos para uso. Porém precisamos ainda modificar a plataforma, para isso clique no canto superior direto onde aparece o icone do Hexiwear:

 

Hexiwear
Figura 4: Hexiwear

 

Ao clicar uma Janela vai abrir permitindo que o usuário escolha a plataforma, para isso clique no icone add platform e procure pela LPC4337 que estamos utilizando no concurso:

 

LPC4337
Figura 5: LPC4337

 

Nesse momento o projeto será automaticamente reconfigurado e você, leitor, estará apto a compilar seus programas para a placa LPCXpresso4337. Antes de continuar, importe o mesmo módulo de LCD que foram usados nos artigos anteriores sobre o uso dessa placa, clique aqui e aqui para ver esses artigos. Agora com o ambiente preparado, em main.cpp, copie e cole o código abaixo:

 

 

Compile e gere o arquivo de gravação .bin utilizando o atalho CTRL + D, com o arquivo gerado, conecte a placa a uma porta USB do seu computador, e procure uma unidade de disco com nome DAPLINK ou mbed. Arraste o arquivo para essa unidade de disco, ela irá desaparecer. Quando ela reaparecer, pressione o botão reset no canto superior esquerdo da placa e o firmware irá começar a rodar. Você deve ter um resultado parecido com esse:

 

 

Cada uma das linhas no display, representam a três Threads simples criadas, cada uma possui sua própria pilha e prioridade de execução. Vamos falar um pouco do código desenvolvido. O primeiro ponto que devemos considerar sobre o mbed OS é que ele utiliza uma política de agendamento do tipo preemptivo, de forma que a tarefa que possuir maior prioridade que as demais e estiver apta a executar (ou seja, não está esperando evento ou suspensa) é imediatamente entregue para execução na CPU e todas as outras ficam prontas aguardando o término da sua execução. Quando isso ocorre, a tarefa de prioridade mais alta dentre as demais é executada e assim sucessivamente até que a tarefa de mais baixa prioridade seja executada, ou uma nova tarefa de prioridade maior que a corrente fique pronta novamente. Essa política é valida quando as Threads possuem prioridades diferentes. Prioridades iguais são permitidas, porém a política de agendamento entre essas mudam para o sistema denominado cooperativo. Quando isso ocorrer dentre todas as tarefas de mesma prioridade que estão prontas, a que estiver no Head da lista de tarefas prontas será executada e as próximas só serão executadas quando a corrente voluntariamente encerrar sua execução (usando o famoso Yield) ou for bloqueada no aguardo de algum evento. Nesse momento a próxima tarefa da lista será executada sob os mesmos critérios e assim por diante até o esvaziamento da lista de tarefas prontas que possuem tal prioridade.

 

Sabendo disso agora voltemos ao código, nas primeiras linhas, instanciamos três objetos de Threads junto com três vetores de bytes, que serão utilizados como pilha de cada tarefa:

 

 

Cada objeto de Thread no mbed OS possui construtures com valores default, o que de início não nos interessa. Vamos demonstrar o lado preemptivo do mbed OS. Para tal criamos três Threads onde o primeiro campo do construtor é a prioridade, o segundo o tamanho da pilha em bytes, e em terceiro o ponteiro para o endereço inicial da pilha. Percebam que o campo de prioridade foi preenchido de forma diferente em cada uma das Threads, logo a função que for apontada por real_time_thread vai sempre ter maior prioridade sobre as demais, e a associada a low_prio_thread terá a menor prioridade, ou seja, só vai rodar quando as outras duas Threads estiverem suspensas ou bloqueadas. Abaixo temos as Threads propriamente ditas, são iguais, então vamos mostrar em destaque apenas uma delas:

 

 

A função em si é bem simples, como já mencionado, cada Thread é tratada como um programa independente dos outros, logo ela pode ter seu próprio loop infinito como nos sistemas bare-metal. Cada Thread então faz o seguinte procedimento, posiciona-se em uma linha, e escreve seu nome e quantas execuções essa Thread realizou, o valor execs vai sendo atualizado uma vez a cada Loop. Em seguida cada Thread se suspende por alguns instantes quando é colocada novamente no estado de pronta. O leitor pode questionar, como disparar a execução de cada Thread? Vejam a nossa função main:

 

 

Pronto! A rotina main é a responsável por disparar a execução das demais Threads, vejam que ao chamar o método xxx_xxx_thread.start(), o parâmetro enviado nada mais é do que a função correspondente a cada Thread criada. Na documentação do mbed OS o leitor vai descobrir que a própria função main é uma Thread especial do sistema. Na documentação não é citado de forma explicita, mas supõe que a função main tenha a prioridade maior de todas e qualquer outra Thread, uma vez que elas só executam quando a rotina main encerra. Acredito que isso possa ser verificado se efetuarmos o debug via GDB.

 

Para o leitor mais observador, falamos sobre prioridades, Threads e política preemptiva de agendamento, se você voltar no vídeo e observar mais atentamente, verás que o número de execuções de real_time_thread aumenta de significantemente mais rápido que a das demais. No código dessa Thread percebam que o tempo em que ela fica suspensa é maior, porém essa não é a razão principal. Associado a isso, o valor de sua prioridade por ser o mais alto do conjunto de Threads criadas permite que quando seu tempo de suspensão termine (sendo menor que o das demais) sua execução ocorra mesmo que outra tarefa já esteja em execução. Isso é o efeito da política de preempção, presente no sistema operacional mbed OS. Como sugestão deixo para que o leitor, modifique os valores de prioridade e teste também o modo cooperativo, ou seja, com todas as Threads com a mesma prioridade.

 

 

Conclusão

 

Dispor do recurso de multitarefa torna o desenvolvimento de uma aplicação de dispositivos mais simples de arquitetar, mais independente e com suporte a múltiplos desenvolvedores, cada um trabalhando em uma ponta da aplicação. Apesar de ser tido como complexo o sistema de Threads, a ARM com seu RTOS, o mbed, conseguiu prover um framework que permite criar sistemas multi-thread com elevada facilidade e recursos poderosos de gerenciamento, espero que esse texto ajude ao leitor que quer dar os primeiros passos em sistemas com RTOS mas que não sente segurança por causa da inerente complexidade envolvida. Bons projetos!

 

 

Para saber mais

 

Consulte o HANDBOOK sobre o mbed OS clicando aqui.

Meu repositório MBED contendo o projeto completo do artigo, clique aqui.

Outros artigos da série

<< mbed OS: Primeiros passosComo utilizar Semáforos no mbed OS - Sincronização >>
Este post faz da série mbed OS. Leia também os outros posts da série:
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.

Felipe Neves
Desenvolvedor de sistemas embarcados apaixonado pelo que faz, divide seu tempo entre trabalhar no Venturus desenvolvendo firmware de tudo quanto é coisa, na Aeolus Robotics se envolvendo com o que há de mais legal em robótica e na Overlay Tech desenvolvendo algumas coisas bem legais para o campo de motion control. Possui mestrado em engenharia elétrica pela Poli-USP e possui interesse em tópicos como: Software embarcado, sistemas de tempo real, controle, robótica móvel e manipuladores, Linux embedded e quase qualquer coisa por onde passe um elétron.

Deixe um comentário

avatar
 
  Notificações  
Notificar