1 Comentário

Desenvolvendo um RTOS: processos e tarefas

processos e tarefas
Este post faz parte da série Desenvolvendo um RTOS. Leia também os outros posts da série:

Continuando a série em parceria com Marcelo Barros, chegamos ao segundo artigo sobre o processo de criação de um kernel cooperativo, definindo processos e tarefas. Para efeito de simplificação, utilizaremos a linguagem C voltada para desktop. Deste modo todos, independente do hardware utilizado, podem replicar as atividades.

Em algumas situações queremos que o programa possa escolher qual função deseja executar, por exemplo num editor de imagens: queremos poder escolher entre usar a função blur ou função sharpen na imagem.

processos e tarefas: usando ponteiro de função

Um exemplo de implementação deste sistema pode ser dado pelo código a seguir, onde image é uma variável matricial que representa uma imagem.

O problema com essa abordagem é que a estrutura é fixa. Se forem adicionadas novas funcionalidades, a função imgEngine tem que ser alterada.

Um outro modo de implementar esse código trazendo uma maior flexibilidade é utilizar os ponteiros de função.

Os ponteiros de função são variáveis que, como ponteiros, armazenam um endereço. A diferença é que esse endereço é de uma função, e não de uma variável. A declaração de uma variável do tipo ponteiro de função, em linguagem C, é bastante diferente da declaração de uma variável normal, sendo muito parecida com a declaração de um protótipo de função. Uma variável com o nome ponteiroTeste, que aponta para uma função que recebe uma imagem e retorna uma imagem, pode ser declarado como:

Por se tratar de um ponteiro é necessário de-referenciar a variável antes de chamar a função, por exemplo:

Notar que após a de-referência são passados os parâmetros como numa função qualquer. Além da passagem por parâmetro a função pode ser armazenada.

É mais comum criar uma definição de tipo para simplificar o uso destes ponteiros no código. Reescrevendo a função imgEngine(), com a estrutura de ponteiros de função temos:

Como podemos perceber pelo código a função imgEngine() recebe dois parâmetros: um é a função e o outro a imagem que será processada. Essa função tem que ser do tipo ptrFunc, ou seja, ela deve receber um parâmetro imagem e retornar uma variável imagem.

A atribuição de uma função a um ponteiro de função, ou passagem por parâmetros como vimos no exemplo, só pode ser feita se ambas as funções tiverem a mesma assinatura (os mesmos parâmetros e o mesmo retorno). Tanto a função Blur() quanto a função Sharpen() obedecem este quesito. Por isso é possível executar o código a seguir:

As funções Blur() e Sharpen() são passadas como se fossem variáveis para serem usadas dentro da função imgEngine().

A grande vantagem desta estrutura é que agora, num mesmo local (imgEngine()), é possível executar diferentes funções, sem precisar conhecer previamente quem ela é. Isto permite inclusive que a placa receba, por exemplo, via comunicação serial, o código binário de uma nova funcionalidade e o armazene num buffer na memória. Logo em seguida passamos o endereço de início do buffer para a função imgEngine() e o código será executado. Um processo muito similar a esse acontece quando realizamos o download de um aplicativo e o executamos no celular.

Process, thread ou task?

Na área de ciências da computação definimos um thread como uma unidade de código executável que pode ser gerenciada de modo independente por um gestor (scheduler ou kernel). A principal diferença entre processos e threads é o compartilhamento de memória. Dois processos só conseguem se comunicar com sistemas dedicados de passagem de mensagens pois as memórias que ocupam são isoladas. Já as threads compartilham a região de memória que se encontram.

Em sistemas operacionais voltados para microcontroladores mais simples, principalmente aqueles que não possuem MMU, pode não ser possível implementar um processo, de modo que todas as atividades são por definição tarefas. Para evitar confusões é comum utilizar a nomenclatura task para definir uma aplicação/trecho de código executável por um sistema operacional embarcado.

Como o objetivo desta série de artigos é explicar o funcionamento de um kernel, bem como o processo de construção do mesmo, as palavras processo e tasks poderão ser utilizadas com o mesmo significado.

Por fim vamos ao que interessa, como criar um processo (ou task)?

A implementação mais simples de um processo é a criação de uma função. Executar o processo significa realizar a chamada da função. Deste modo podemos adaptar o código utilizado para a criação de um editor de imagens para criar um "executor" de funções.

O código acima utiliza a função exec() para executar os processos já criados. O próximo passo é conseguir gerenciar os processos de modo que seja possível controlar e modificar sua ordem de execução. Para isso precisamos de uma estrutura que consiga armazenar referências para os processos, adicionar essas referências e executar os processos armazenados na estrutura. Isso fica para o próximo artigo.

Outros artigos da série

<< Desenvolvendo um RTOS: IntroduçãoDesenvolvendo um RTOS: Utilizando buffer circular como gestor de processos >>
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

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

Software » Desenvolvendo um RTOS: processos e tarefas
Comentários:
Notificações
Notificar
guest
1 Comentário
recentes
antigos mais votados
Inline Feedbacks
View all comments
Marcelo Campos
Marcelo Campos
20/11/2015 18:11

Muito bom o seu artigo e a maneira abordada/ modo de explicação. Parabéns e obrigado pela aula !

Talvez você goste:

Séries



Outros da Série

Menu

WEBINAR
 

Soluções inteligentes para acionamento de MOSFETs/IGBTs com família STDRIVE

Data: 08/10 às 15:00h - Apoio: STMicroelectronics
 
INSCREVA-SE AGORA »



 
close-link