Ferramenta para Desenvolvimento de Sistemas - LaPSI
Transcrição
Ferramenta para Desenvolvimento de Sistemas - LaPSI
UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL ESCOLA DE ENGENHARIA INSTITUTO DE INFORMÁTICA CURSO DE GRADUAÇÃO EM ENGENHARIA DE COMPUTAÇÃO FRANCISCO ROBERTO PEIXOTO SOCAL Ferramenta para Desenvolvimento de Sistemas Embarcados Utilizando Linguagem de Alto Nı́vel Projeto de Diplomação Prof. Dr. Altamiro Amadeu Susin Orientador Porto Alegre, dezembro de 2003 SUMÁRIO LISTA DE FIGURAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 LISTA DE TABELAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1 INTRODUÇÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 A ferramenta proposta . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Organização deste documento . . . . . . . . . . . . . . . . . . . . . 6 7 7 2 DESENVOLVIMENTO DE SISTEMAS EMBARCADOS . . . . . . . . . . . . . 2.1 Possibilidades de implementação . 2.2 Desenvolvimento de software . . . 2.3 Metodologias alternativas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 . 9 . 9 . 10 3 A LINGUAGEM LUA . . . . . . . . . 3.1 Aplicações . . . . . . . . . . . . . . 3.2 Implementação . . . . . . . . . . . 3.2.1 Conceituação . . . . . . . . . . . . 3.2.2 A Máquina Virtual Lua . . . . . . . 3.3 Utilização . . . . . . . . . . . . . . . 3.3.1 Utilização em Sistemas Embarcados 3.4 Exemplos de código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 13 13 13 13 14 14 15 4 A FERRAMENTA PICOLUA . . . . . . . . . 4.1 O uso da Máquina Virtual Lua . . . . . 4.2 Arquiteturas alvo e a geração de código 4.3 Implementação . . . . . . . . . . . . . . . 4.3.1 O módulo nula . . . . . . . . . . . . . . . 4.3.2 O analisador de código . . . . . . . . . . . 4.3.3 O gerador de código para PIC . . . . . . . 4.3.4 Extensões à linguagem Lua . . . . . . . . . 4.4 Metodologia proposta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 19 20 21 22 24 25 27 5 ESTUDOS DE CASO . . . . . . . . . . . 5.1 Multiplicação de inteiros . . . . . . . 5.2 Comunicação serial . . . . . . . . . . . 5.3 Sistema de medição de vibração . . . 5.4 Modelo para aplicações distribuı́das . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 29 31 31 32 . . . . . . . . . . . . . . . . . . 5.4.1 Plataformas utilizadas 5.4.2 Conectividade . . . . . 5.4.3 Benefı́cios . . . . . . . 5.5 Resultados . . . . . . . . . . . 32 32 33 33 CONCLUSÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 REFERÊNCIAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 APÊNDICES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . LISTA DE FIGURAS Figura 2.1: Fluxo de desenvolvimento de software . . . . . . . . . . . . . . . 10 Figura Figura Figura Figura Figura 3.1: 3.2: 3.3: 3.4: 3.5: Fluxo de desenvolvimento com a linguagem Lua . . Cálculo da seqüência de Fibonacci em Lua . . . . . Utilização de tabelas Lua como containers de dados Suporte a orientação a objetos em Lua . . . . . . . Extensão de semântica na linguagem Lua . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 15 16 17 18 Figura Figura Figura Figura Figura Figura 4.1: 4.2: 4.3: 4.4: 4.5: 4.6: Estruturação da ferramenta picoLua . . . . . . . . . . . . . Mapa de utilização de memória no PIC . . . . . . . . . . . . Exemplo de uso da tabela regs . . . . . . . . . . . . . . . Exemplo de uso de assembly inline . . . . . . . . . . . . . . Exemplo de definição do target e da palavra de configuração Metodologia proposta com a ferramenta picoLua . . . . . . . . . . . . . . . . . . . . . . . . 20 25 26 26 27 28 Figura 5.1: Codificações em Lua, bytecodes Lua e assembly do PIC . . . . . . 30 Figura 5.2: Sistema tı́pico de medição de vibração . . . . . . . . . . . . . . . 31 Figura 5.3: Arquitetura para aplicações distribuı́das . . . . . . . . . . . . . . 33 LISTA DE TABELAS Tabela 4.1: Representação de uma função Lua gerada pelo nula . . . . . . . . 21 Tabela 4.2: Representação de uma instrução gerada pelo nula . . . . . . . . . 22 Tabela 4.3: Comparação entre as metodologias picoLua e SASHIMI . . . . . . 27 Tabela 5.1: Comparação entre implementações em Lua e Assembly . . . . . . 34 6 1 INTRODUÇÃO O avanço constante dos processos de fabricação de circuitos integrados tem possibilitado o desenvolvimento de sistemas computacionais cada vez mais complexos. Neste cenário, destacam-se os sistemas embarcados que ganham espaço em produtos de eletrônica de consumo, mas que em muitas vezes apresentam restrições de custos ou de recursos disponı́veis. Como conseqüência, o projeto e o teste destes sistemas têm se mostrado igualmente difı́ceis. Neste sentido, existem diversas metodologias e ferramentas para projeto, descrição e sı́ntese de tais sistemas. Pode-se citar os ambientes de desenvolvimento, com compiladores, simuladores, etc. Embora sejam freqüentemente especı́ficos para determinadas tecnologias ou fabricantes (e conseqüentemente incompatı́veis entre si), são freqüentes no cenário atual. Existe também a possibilidade de sı́ntese automática de hardware e software para integração em um SOC (System On a Chip), dada uma descrição em uma linguagem de alto nı́vel. Por outro lado, com o crescente aumento da complexidade dos softwares desenvolvidos, a engenharia de software se faz cada vez mais necessária. Busca-se o desenvolvimento de software manutenı́vel e portável, mas também com rápida prototipação e com a possibilidade de ter seu teste integrado. Linguagens de programação tradicionais como C++ e Java podem ser utilizadas para atingir tais ideais, mas deixam a desejar quando comparadas com as possibilidades oferecidas por linguagens de mais alto nı́vel como Scheme, Python ou Lua. Caracterı́sticas tipicamente encontradas em linguagens interpretadas, como flexibilidade, facilidade de integração com demais linguagens e programas, tratamento de funções como valores de primeira classe e migração de código, podem se tornar caracterı́sticas interessantes no desenvolvimento e principalmente na prototipação e nos testes de sistemas embarcados. Neste contexto, a linguagem de programação Lua destaca-se pela simplicidade que oferece tais caracterı́sticas e também por ser extremamente portável e compacta (FIG 1996). Adicionalmente, a linguagem mostra-se interessante por ser software livre, o que possibilita sua expansão e o desenvolvimento de trabalhos nela baseados. Apesar de ser uma linguagem concebida originalmente para extensão de aplicações — onde tradicionalmente interpreta-se diretamente o código fonte — a linguagem Lua adota um abordagem mais eficiente, similar à de Smalltalk e Java. Foi definida uma máquina virtual que atende às necessidades da linguagem e para a qual os programas são compilados, gerando os chamados bytecodes. Os programas são então executados através da simulação da máquina virtual, interpretando os bytecodes gerados (FIG 1994). Assim, obtém-se algumas vantagens, tanto sobre linguagens compiladas convencionais como sobre linguagens interpretadas. Pode-se citar a portabilidade dos 7 programas, desde que se tenha implementações da máquinas virtuais para as plataformas desejadas. Já sobre linguagens interpretadas, tem-se principalmente a maior velocidade de execução, uma vez que a análise léxica e sintática do código fonte só é feita uma vez. Fora isso, de posse da especificação da máquina virtual, pode-se partir para abordagens alternativas, como a implementação de máquinas reais ou como a recompilação de bytecodes para outras plataformas (STOL 2003). 1.1 A ferramenta proposta Baseado nestes argumentos, este trabalho apresenta uma ferramenta para o desenvolvimento e testes de sistemas embarcados baseada na linguagem Lua. Nela são exploradas as potencialidades da linguagem, sendo esta estendida e portada para algumas plataformas computacionais. Adicionalmente, uma vez que a linguagem Lua já tem sua máquina virtual implementada e suportada por uma vasta gama de arquiteturas que dispõem de sistemas operacionais e demais recursos, optou-se pelo aprimoramento da ferramenta, tornando possı́vel o uso da linguagem em sistemas computacionais com recursos mı́nimos. Por recursos mı́nimos entende-se a não disponibilidade de um sistema operacional completo, com facilidades de gerência de memória, manipulação de arquivos, etc. Assim, teve-se como alvo sistemas profundamente embarcados, sejam eles com microcontroladores, com cores sintetizáveis, ou mesmo em SmartCards e tecnologias similares. Como estudo de caso, optou-se por desenvolver a ferramenta visando a famı́lia de microcontroladores PIC, da Microchip. Tal escolha foi motivada tanto pela difusão destes microcontroladores na indústria e por oferecerem uma arquitetura similar à RISC, quanto pelo desafio imposto por sua limitação de recursos. Sobre sua arquitetura, esta facilita a geração automática de código, o que mostra-se interessante na concepção de ferramentas como esta sendo proposta. Finalmente, dado um programa escrito num subconjunto bem definido da linguagem Lua, a ferramenta desenvolvida, batizada como picoLua, é capaz de gerar um programa equivalente na linguagem assembly para estes microcontroladores. Com isso, é possı́vel o desenvolvimento de sistemas embarcados ou mesmo de aplicações distribuı́das envolvendo uma variedade de plataformas — desde um simples microcontrolador até computadores pessoais, handhelds e servidores de grande porte. Beneficiando-se da uniformidade e portabilidade da linguagem, é possı́vel ter uma especificação única do sistema, independentemente do local onde cada parte é efetivamente implementada. É possı́vel também utilizar as potencialidades oferecidas pela linguagem, como a não diferenciação entre programas e dados, para realizar migração de código entre os nós do sistema. Pode-se assim ter estratégias como a carga dinâmica de código, o disparo automático de testes remotamente e tantas outras. 1.2 Organização deste documento Este documento descreve o desenvolvimento da ferramenta, apresentando tanto seus objetivos e o contexto onde é inserida, quanto sua implementação e algumas aplicações práticas. No capı́tulo 2, é abordado de uma maneira geral o desenvolvimento de sistemas 8 embarcados, dando ênfase ao desenvolvimento de software. Nele são mostradas algumas metodologias e possibilidades de projeto. Já o capı́tulo 3 aborda a linguagem Lua, apresentando suas principais caracterı́sticas e potencialidades, identificando o que a torna interessante para o uso em sistemas embarcados. O capı́tulo 4 apresenta o desenvolvimento da ferramenta em si, mostrando seus princı́pios de funcionamento e identificando suas funcionalidades. São apresentados também os recursos da linguagem Lua que podem ser mapeadas para os microcontroladores escolhidos. No capı́tulo 5 são apresentados exemplos de utilização da linguagem Lua e da ferramenta proposta em sistemas embarcados. São feitas também comparações com soluções equivalentes, implementadas diretamente em linguagem de montagem. Finalmente, o capı́tulo 6 traz as conclusões obtidas, fazendo uma análise crı́tica e apresentando alguns tópicos que poderão ser explorados em trabalhos futuros. 9 2 DESENVOLVIMENTO DE SISTEMAS EMBARCADOS Sistemas embarcados podem ser considerados como exemplos de sistemas dedicados ou de aplicação especı́fica. Estão normalmente inseridos em sistemas maiores e são concebidos para a realização de tarefas especı́ficas, bem conhecidas e determinadas em tempo de projeto. De uma maneira geral, apresentam restrições nos recursos computacionais disponı́veis, nos custos ou mesmo nas condições de operação. Como conseqüência, diferem-se dos sistemas ditos de propósito geral por estas peculiaridades. 2.1 Possibilidades de implementação Do ponto de vista da implementação, os sistemas embarcados apresentam diversas possibilidades, como as listadas a seguir: • Uso de microprocessadores comerciais, codificando o software em linguagem de montagem (assembly) ou mesmo em linguagens de mais alto nı́vel, como a linguagem C e similares; • Uso de processadores digitais de sinais (DSPs), novamente com software descrito em linguagem de montagem ou, mais recentemente, em linguagens como C; • Desenvolvimento de hardware especı́fico, seja através de ASICs ou de ASIPs; • Uso de FPGAs, acomodando hardware especı́fico, memórias ou mesmo cores de processadores sintetizáveis; • Integração em sistemas em um chip (SOC), acomodando CPU, memórias, conversores, módulos analógicos, etc. É importante salientar que tais soluções não são necessariamente mutuamente exclusivas e são muitas vezes encontradas integradas. 2.2 Desenvolvimento de software Do ponto de vista do software, o desenvolvimento de sistemas embarcados não difere significativamente do dos demais, com exceção às metodologias que visam 10 Figura 2.1: Fluxo de desenvolvimento de software o desenvolvimento em conjunto de hardware e software (hardware e software codesign). Em geral, parte-se de uma especificação geral do sistema e tenta-se delimitar o que será implementado em software. Busca-se então a prototipação do sistema, validando-a através de simulações ou mesmo utilizando-a em plataformas reais. Uma vez validado, o sistema pode ser implementado, desde que neste ponto sejam consideradas as restrições impostas pela plataforma alvo. Isto inclui os recursos disponı́veis, as restrições temporais, a potência a ser consumida e outras. Durante este ciclo, diversas ferramentas são utilizadas com fins de simulação, testes, validação ou mesmo de tradução entre os diversos nı́veis de representação do sistema. Tradicionalmente, ferramentas como simuladores, compiladores, ligadores e ferramentas de sı́ntese são utilizadas, como ilustrado na figura 2.1. 2.3 Metodologias alternativas No contexto de desenvolvimento exposto, notavelmente diversas linguagens de programação ou de descrição são utilizadas, o que contribui significativamente para o aumento da complexidade envolvida. Neste sentido, diversas abordagens são propostas visando a simplificação e unificação do processo de desenvolvimento. Uma atenção especial deve ser dada às alternativas que buscam a integração do desenvolvimento de software e de hardware. Destaca-se assim a metodologia SASHIMI, que mostra-se interessante por permitir a sı́ntese automática de um ASIP e seu programa a partir de uma descrição na linguagem Java. Ao utilizar a tecnologia Java, é possı́vel a adoção de suas ferramentas padrão para o desenvolvimento, compilação e validação dos sistemas. Como conseqüência, tem-se todas vantagens que elas oferecem, como a independência de plataforma e interoperabilidade. Finalmente, tomam-se os bytecodes gerados para a Máquina Virtual Java (JVM) como ponto de partida para a geração de um microprocessador dedicado e o respectivo código a ser executado (ITO 2000). 11 Adicionalmente, o processo de codificação pode ser significativamente melhorado no momento em que são utilizadas ferramentas e linguagens compatı́veis entre si e com as utilizadas durante a prototipação do sistema. Similarmente, os testes de tais sistemas, embora não explicitados na figura 2.1, podem ser igualmente beneficiados. Em geral, tal facilidade é oferecida em ambientes de desenvolvimento proprietários, especı́ficos para determinadas tecnologias. Finalmente, sistemas embarcados inseridos em aplicações maiores, como aplicações distribuı́das, com diversos nós de processamento, podem ser beneficiados diretamente pela migração de código. Por migração de código entende-se a possibilidade de transferir uma determinada tarefa, juntamente com seu contexto de execução, para que seja realizada remotamente. Tal caracterı́stica pode também ser útil durante o processo de desenvolvimento e prototipação, pois acelera o ciclo de codificação-compilação-carga-verificação ao diminuir significativamente o tempo de carga do software no sistema alvo. Por outro lado, para que isto seja possı́vel, é necessário existir um núcleo de software no sistema alvo capaz de aceitar conexões remotas e instanciar as tarefas solicitadas. Várias soluções existem que oferecem tal recurso, em especial o JavaParty para a linguagem Java (HAU 2003). 12 3 A LINGUAGEM LUA Conforme exposto anteriormente, a Linguagem Lua foi especialmente concebida para extensão e customização de aplicações. Assim, uma série de atributos desejáveis para esta classe de linguagens foi levada em conta durante sua implementação (FIG 1994): • Sintaxe simples, limpa e familiar; • Implementação e código pequenos, para que o custo de adicioná-la a um sistema hospedeiro seja mı́nimo; • Expressibilidade e facilidade de descrição de dados, por ser utilizada primariamente como uma linguagem de configuração; • Extensibilidade adequada, permitindo seu uso em diversos domı́nios e nı́veis de abstração; Além disso, a linguagem Lua tem recursos especiais que a tornam poderosa, extensı́vel e de alto nı́vel, como os listados a seguir. Exemplos de utilização destes recursos são dados ao final do capı́tulo, na seção 3.4. • Tabelas associativas, o que permite a implementação de containers de dados de uma maneira única e ortogonal; • Habilidade de definir e manipular funções como valores de primeira classe, o que simplifica significativamente a implementação de facilidades como orientação a objetos; • Coleta de lixo (garbage collection), eliminando a necessidade de gerência explı́cita de alocação de memória; • Um mecanismo de extensão da semântica da linguagem através de metatabelas (similar à sobrecarga de operadores em C++). Por outro lado, como conseqüência de não ter sido planejada para uso em sistemas maiores, foram abolidos alguns mecanismos, como o encapsulamento de dados (information hiding), a tipagem estática e a ligação incremental de código. Porém, a ausência de uma tipagem estática, aliada à natureza interpretada da linguagem, provê uma flexibilidade extra na descrição de dados e na estruturação de software, o que é dificilmente atingido em linguagens convencionais. 13 3.1 Aplicações A linguagem Lua tem se destacado em diversas áreas de aplicação, sendo as principais: • Em jogos, como engines de inteligência artificial e como “lógica de cola” entre demais módulos; • Como CGI, em aplicações Web; • Como linguagem de configuração de aplicativos e de equipamentos. 3.2 3.2.1 Implementação Conceituação Conceitualmente, o suporte à linguagem Lua é disponibilizada como uma pequena biblioteca de funções C que é ligada a uma aplicação hospedeira. A aplicação instância então ambientes de execução Lua, nos quais são definidos e manipulados dados através de uma API. É definido também um mecanismo para as aplicações realizarem chamadas às funções do Lua, seja chamando-as explicitamente através da API ou interpretando scripts previamente elaborados. Finalmente, é possı́vel ter funções implementadas pelas aplicações (codificadas em C, por exemplo) exportadas para o ambiente Lua. Com isto, possibilita-se o desenvolvimento de bibliotecas de funções para o Lua, implementando-as em outras linguagens. Estende-se assim a linguagem, oferecendo novas primitivas, otimizando funcionalidades com desempenho crı́tico ou mesmo provendo acesso a dispositivos especı́ficos de uma dada arquitetura. Como exemplo trivial de aplicação que utiliza a linguagem Lua, tem-se o interpretador de comandos Lua. Codificado em C, o interpretador consiste basicamente em um laço infinito, que lê uma string da entrada padrão e passa-a para a biblioteca Lua para sua execução. Os resultados da execução — ou as indicações de erro — são devolvidas para a aplicação que se encarrega de exibi-los na saı́da padrão. 3.2.2 A Máquina Virtual Lua Do ponto de vista de sua implementação, a linguagem Lua adota uma estratégia baseada em uma máquina virtual. Tal estratégia é um meio termo entre as adotadas por linguagens puramente interpretadas e as adotadas por linguagens compiladas. Por sua vez, a Máquina Virtual Lua (Lua Virtual Machine, ou LVM) utiliza uma arquitetura baseada em um set de registradores, ao invés da abordagem convencional baseada em pilhas, utilizada por muitos interpretadores. Em especial, o uso desta abordagem na especificação mais recente da LVM (versão 5.0) resultou em um aumento de 30% na velocidade de execução (IER 2003). Esta caracterı́stica da LVM mostra-se também interessante em processos de recompilação de código Lua, especialmente na ferramenta picoLua. Uma vez que os bytecodes Lua manipulam dados diretamente num conjunto de registradores de propósito geral, o mapeamento para o modelo de memória dos microcontroladores escolhidos é imediato. 14 3.3 Utilização A figura 3.1 mostra um exemplo tı́pico de utilização da linguagem Lua no contexto de desenvolvimento de software. Nele, a linguagem é utilizada como standalone, diferentemente de seu propósito original de utilização embutida em aplicações. Percebe-se como a linguagem pode ser aproveitada para especificar, prototipar e implementar um sistema a partir de uma descrição única de software. Figura 3.1: Fluxo de desenvolvimento com a linguagem Lua 3.3.1 Utilização em Sistemas Embarcados Apesar da linguagem Lua não ter sido originalmente planejada para uso em sistemas embarcados, muitas de suas caracterı́sticas já apresentadas se destacam e tornam esta possibilidade interessante. A seguir são re-apresentadas algumas caracterı́sticas da linguagem, relacionando-as com as vantagens que podem ser atingidas em sistemas embarcados. Sintaxe simples e familiar Diminui a curva de aprendizado, simplifica o processo de codificação e facilita a manutenção de código; Implementação compacta e eficiente Permite o uso em sistemas com recursos limitados, reduzindo custos e energia consumida; Construções de alto nı́vel Permite expressar conceitos abstratos de uma maneira direta, sem fazer uso de recursos adicionais. Minimiza a possibilidade de erros de codificação e facilita a manutenção do software; Facilidade de extensão Permite a utilização em diversos domı́nios: nos sistemas embarcados em si, durante o processo de desenvolvimento e mesmo nos sistemas maiores que interajam com eles; 15 Especificação e implementação como software livre Oferece uma flexibilidade adicional para se adequar a caracterı́sticas especı́ficas do sistema alvo e permite a fácil integração com outros módulos e sistemas. 3.4 Exemplos de código Nesta seção são apresentados alguns trechos de código como exemplo de codificação na linguagem Lua. Com eles, busca-se ilustrar a sintaxe e algumas caracterı́sticas interessantes da linguagem. A figura 3.2 apresenta um pequeno programa que calcula os números da seqüência de Fibonacci. Nele, a criação de funções dinamicamente é explorada pela função cache. Dada uma função f , uma nova função f f é criada e retornada. A função f f é basicamente a função f original modificada para manter uma tabela com os resultados de suas execuções anteriores. No caso da função f ib, consegue-se reduzir a complexidade envolvida, de O(cn ) para O(n), através de um truque de programação codificado de uma maneira simples e elegante. -- Funç~ ao de Fibonacci convencional fib = function( n ) if n<2 then return n else return fib(n-1)+fib(n-2) end end -- Criaç~ ao din^ amica de funç~ oes com memória das -- últimas execuç~ oes cache = function( f ) local c = {} local ff = function( x ) local y = c[x] if not y then y = f(x) c[x] = y end return y end return ff end -- cria-se uma vers~ ao cached para ’fib’, que é -- executada para comparaç~ ao com a original cached_fib = cache( fib ) print( fib(N), cached_fib(N) ) Figura 3.2: Cálculo da seqüência de Fibonacci em Lua A figura 3.3 mostra a definição de algumas estruturas de dados básicas, ilustrando a implementação de containers dados. Nela, tabelas Lua servem como mecanismo de implementação. 16 -- tabela como lista: cada entrada -- ganha um ı́ndice numérico list = { "Lua", "C", "C++", "Java" } -- mapas, dicionário ou tabela associativa: -- para cada chave está associado um valor map = {} map["Lua"] = "TeCGraf, PUC-Rio" map["C++"] = "Bjarne Stroustrup" map["Java"] = "Sun Microsystems" -- uso de iteradores para percorrer a tabela for lang, author in map do print( lang, "by", author ) end -- estrutura: cada campo é na verdade -- uma chave para uma entrada na tabela picoLua = { source = "Lua", implementation = "Lua/LVM", target = "Assembly/PIC" } Figura 3.3: Utilização de tabelas Lua como containers de dados A figura 3.4 mostra a construção de objetos, dentro do conceito de orientação a objetos. Graças à não diferenciação entre dados e funções, é possı́vel utilizar tabelas para armazenar métodos e variáveis membro. Finalmente é mostrado o mecanismo de extensão da semântica da linguagem através de meta-tabelas, na figura 3.5. Nela é definido o comportamento do operador de adição para números complexos. 17 -- ’Circle’ é a funç~ ao construtora, que cria e -- retorna um objeto cı́rculo Circle = function( r ) -- um objeto é na verdade uma tabela que -- guarda seus membros e métodos local c = {} c.radius = r -- self é passado como par^ ametro implicito -- ao utilizar a sintaxe obj:method( args ) c.area = function( self ) return math.pi * self.radius ^ 2 end return c end -- instanciaç~ ao de um objeto e chamada de método c = Circle( 10 ) a = c:area() Figura 3.4: Suporte a orientação a objetos em Lua 18 -- ’Complex’ é o construtor de objetos que -- represetam números complexos Complex = function( r, i ) -- o número complexo é uma tabela, -- com os componentes real e imaginário local c = {} c.real = r c.imag = i -- ’ops’ é uma tabela com as operaç~ oes -- sobre números complexos local ops = {} -- o nome ’__add’ é predefinido, para -- implementar a sem^ antica do operador ’+’ ops.__add = function( a, b ) return Complex( a.re+b.re, a.im+b.im ) end -- finalmente, a tabela ’ops’ é associada à -- meta-tabela de ’c’, que será consultada -- pela LVM setmetatable( c, ops ) end -- criaç~ ao de complexos i = Complex( 0, 1 ) x = Complex( 3, 0 ) -- operaç~ ao de soma: chamará o méotodo ’__add’ -- da meta-tabela de ’x’ y = x + i Figura 3.5: Extensão de semântica na linguagem Lua 19 4 A FERRAMENTA PICOLUA Este capı́tulo apresenta a ferramenta picoLua, desenvolvida para permitir o uso da linguagem Lua em microcontroladores e microprocessadores embarcados. Tal objetivo é atingido fazendo uma recompilação dos programas escritos na linguagem Lua: a ferramenta é responsável por analisar os bytecodes gerados e por transformálos em programas na linguagem de montagem para a arquitetura alvo escolhida. Elimina-se assim a necessidade de se ter a Máquina Virtual Lua implementada para tais plataformas e a conseqüente utilização de um sistema operacional completo. 4.1 O uso da Máquina Virtual Lua A não utilização da LVM permite o porte da linguagem para sistemas menores. Mas, por outro lado, a linguagem perde algumas das suas caracterı́sticas marcantes, como a tipagem dinâmica e o garbage collection. Assim, optou-se por definir um subconjunto da linguagem suportado pela ferramenta, que delimita tanto a sintaxe reconhecida quanto a semântica passı́vel de ser mapeada para a arquitetura alvo. Dentre as caracterı́sticas da linguagem não suportadas diretamente pela ferramenta, destaca-se a tipagem dinâmica de dados, imprescindı́vel para a diferenciação entre dados e programas durante a interpretação dos programas. Tal diferenciação é realizada implicitamente pela LVM em tempo de execução, mas se faz necessária em tempo de compilação ao se utilizar a ferramenta para a geração de código em linguagem de montagem. 4.2 Arquiteturas alvo e a geração de código Como estudo de caso de arquitetura alvo, escolheu-se a famı́lia de microcontroladores PIC da Microchip. Como conseqüência, tem-se a geração de código voltada para a linguagem de montagem desta arquitetura. Porém, a ferramenta tem sua implementação modularizada, separando explicitamente as partes responsáveis por realizar a descompilação, a análise dos bytecodes e geração de código em si. Com isto, é perfeitamente possı́vel a substituição do módulo de geração de código por outro, dedicado a diferentes arquiteturas. Esta possibilidade pode ainda ser levada ao extremo, gerando código em outras linguagens de alto nı́vel, em especial na linguagem C. Apesar de parecer contraditória, a idéia da utilização de uma ferramenta para tradução de programas escritos na linguagem Lua para outra linguagem de alto nı́vel é válida por diversos motivos: • Escrito em Lua, o software se beneficia de um série de as vantagens sobre a 20 linguagem C, como exposto anteriormente; • A não utilização da máquina virtual e a conseqüente tipagem estática dos dados pode representar um ganho de desempenho sobre sistemas que utilizem a linguagem Lua em sua forma interpretada; • O número de compiladores C disponı́veis para plataformas embarcadas é crescente; • Muitos compiladores C permitem a otimização de código, explorando possı́veis paralelismos e caracterı́sticas especiais das arquiteturas alvo; • A implementação da ferramenta é sensivelmente simplificada pelo fato do compilador C já abstrair detalhes da arquitetura, com destaque para o modelo de memória a ser seguido; Adicionalmente, a ferramenta picoLua é implementada quase totalmente na própria linguagem Lua, o que reforça a versatilidade e expressibilidade da linguagem. Uma exceção, porém, deve ser feita ao módulo que realiza a descompilação dos bytecodes e que gera uma representação interna do programa sendo analisado. Tal módulo é implementado em C, uma vez que é inspirado fortemente no código da Máquina Virtual Lua já existente. Contudo, é perfeitamente viável sua implementação em Lua, com o custo da rescrita do código. 4.3 Implementação A ferramenta picoLua tem sua implementação dividida em módulos, conforme ilustrado pela figura 4.1. Nela é exposto também o fluxo de dados sugerido para a utilização da ferramenta. Figura 4.1: Estruturação da ferramenta picoLua 21 Basicamente, a ferramenta é composta por três módulos: o nula, que faz a descompilação dos bytecodes, gerando uma representação interna passı́vel de ser tratada na linguagem Lua; o analisador de código em si, que identifica as diversas operações realizadas pelo programa fonte e que chama as primitivas para geração de código; o backend, ou gerador de código, que é chamado pelo analisador de código e que gera o código propriamente dito. Finalmente, o código fonte completo da ferramenta picoLua é apresentado em anexo. 4.3.1 O módulo nula A tradução de um dado programa com código fonte Lua, de sua forma textual para sua representação em bytecodes, pode ser realizada pelo compilador Lua, o luac. No caso da ferramenta, tal tarefa é realizada pela função loadf ile da biblioteca padrão Lua. O resultado de sua execução é uma nova função que pode ser executada na Máquina Virtual Lua. Tal função pode ser então atribuı́da a uma variável e executada posteriormente. No caso da ferramenta picoLua, esta função serve como entrada para o módulo nula. Codificado em C, o módulo traduz uma determinada função representada no formato próprio para execução na LVM para uma representação proprietária, interpretável no ambiente Lua pelos demais módulos da ferramenta. Tal representação, ilustrada na tabela 4.1, é dada através de uma tabela Lua cujos campos descrevem o comportamento e as caracterı́sticas da função original. O código executável das funções, em instruções para a LVM, está presente no campo code desta tabela e também é tratado pelo nula: cada instrução relacionada no código executável é representada conforme a estrutura apresentada na tabela 4.2. Tabela 4.1: Representação de uma função Lua gerada pelo nula Campo Descrição is main Flag que indica se a função corresponde à função “main” do programa source String com o nome do arquivo fonte original line def ined Linha na qual a função é definida num param Número de parâmetros recebidos pela função is vararg Flag que indica se a função recebe um número variável de argumentos stack size Número máximo de registradores utilizados pela função code Tabela com o código da função, em uma representação interna para os bytecodes constants Tabela com as constantes definidas na função locals Tabela com as variáveis locais definidas na função upvalues Tabela com as upvalues utilizadas pela função (variáveis locais de uma função pai, dentro da qual a função em questão é definida) f unctions Tabela com protótipos de funções, definidas dentro da função em questão 22 Tabela 4.2: Representação de uma instrução gerada pelo nula Campo Descrição opcode Opcode da instrução utilizado pela LVM a Operando a utilizado pela instrução b Operando b c Operando c b Operando b sbx Operando sbx line Linha onde a instrução é definida 4.3.2 O analisador de código Dada a representação de uma função Lua, utilizável como dado por outra função Lua, é possı́vel prever estaticamente seu funcionamento e algumas de suas caracterı́sticas de execução. Na ferramenta picoLua, tal tarefa é realizada pelo analisador de código, responsável por identificar operações como atribuição de variáveis, operações aritméticas, testes, manipulação de tabelas, etc, chamando o gerador de código em si. Porém, apenas um subconjunto da linguagem é suportado e conseqüentemente algumas construções não são suportadas pela ferramenta. Novamente, cabe ao analisador de código identificá-las e reportar ao usuário a impossibilidade de utilizá-las. De posse da especificação das instruções suportadas pela LVM, é definido precisamente o escopo de atuação do analisador de código. Em um primeiro momento, a tradução é feita tomando-se instrução por instrução, sem que o contexto em que elas são inseridas seja levado em consideração. Em um segundo momento, são tratadas as construções parcialmente suportadas pela ferramenta e que eventualmente têm suas semânticas alteradas para acomodar as restrições impostas pela arquitetura alvo escolhida. Neste grupo estão inseridas a manipulação de tabelas, a criação de funções e o acesso a variáveis globais. A seguir é dada uma relação completa dos opcodes definidos pela LVM e que caracterizam as classes de instruções gerados pelo Lua. É dada também uma pequena descrição para cada opcode, juntamente com alguns comentários sobre a abordagem adotada pela ferramenta para tratá-lo — ou não — e traduzi-lo corretamente para a arquitetura alvo. MOVE Move dados entre registradores. Sua implementação pela ferramenta é imediata, pois corresponde ao modelo de memória adotado pelos microcontroladores utilizados. LOADK Atribui uma constante ao um dado registrador; implementado pela ferramenta como carga de um valor literal a um registrador. LOADBOOL Similar à carga de uma constante, com a diferença de que um salto condicional adicional e opcional é especificado. Tal salto é utilizado para permitir a utilização de duas instruções consecutivas com LOADBOOL. É implementado como um salto convencional ou simplesmente omitido, pois a condição verificada é conhecida em tempo de compilação. 23 LOADNIL Também similar à carga de uma constante, mas modificado pois o conceito de valor nulo, utilizado pela LVM, é redefinido para ser representado como um valor puramente numérico na arquitetura alvo. GETUPVAL, SETUPVAL Obtém ou atribui o valor de uma upvalue (variável local definida em uma função pai da função sendo analisada). Não é suportado pela ferramenta uma vez que o suporte a criação de funções dinamicamente dado pela ferramenta não abrange a especialização de funções. GETGLOBAL, SETGLOBAL Obtém ou atribui o valor de uma variável global, transferindo-a de/para um registrador. Sua implementação é razoavelmente imediata, pois deve-se manter uma relação de variáveis globais a serem alocadas pela ferramenta. Adicionalmente, um cuidado especial deve ser tomado pois chamadas a funções (por instruções CALL e TAILCALL) acabam sendo codificadas utilizando estas instruções para recuperar uma referência ao corpo da função e armazená-la em um registrador. Tal ação deve ser mapeada para uma chamada de função convencional, do ponto de vista da plataforma a ter seu código gerado pela ferramenta. GETTABLE, SETTABLE Obtém ou define um campo de uma tabela, dado um ı́ndice ou uma chave. Não é suportada pela ferramenta. Porém, às extensões definidas pela ferramenta para acomodar o acesso direto aos recursos da plataforma acabam sendo codificadas com tais opcodes. Assim, uma análise criteriosa dos argumentos é feita para identificar tais funcionalidades e tratá-las da maneira correta, como exposto na seção 4.3.4. NEWTABLE Cria uma nova tabela e armazena-a em um registrador. Não suportado pela ferramenta, devido à pequena capacidade de armazenamento da plataforma escolhida; SELF Similar a GETTABLE, com a diferença que a referência à tabela é mantida na pilha para ser passada como parâmetro a uma função, implementando assim, dentro dos conceitos da orientação à objetos, uma chamada de método de um objeto. Não é suportado pela ferramenta em sua versão atual. ADD, SUB, UNM, NOT Operações aritméticas, que operam diretamente sobre registradores ou constantes. O mapeamento para instruções da linguagem de montagem alvo é imediato, pelos mesmos motivos apresentados no opcode MOVE. MUL, DIV, POW Também operações aritméticas, mas não suportadas diretamente pela arquitetura. Conseqüentemente não são suportadas pela ferramenta. CONCAT Concatenação de registradores, com uso especı́fico para strings. Como o suporte a strings não está previsto pela ferramenta, tal opcode não é suportado. JMP Salto relativo incondicional. Faz referência à numeração interna das instruções da função e assim o deslocamento deve ser recalculado pois não há uma correspondência direta entre o número de instruções Lua e da arquitetura alvo. 24 EQ, LT, LE, TEST Testes e comparações entre dois registradores ou constantes, seguido de um salto condicional. Também tem um mapeamento imediato, visto que a arquitetura prevê comparações similares. CALL, TAILCALL Chamadas de funções Lua. Representam o maior desafio imposto à codificação da ferramenta, uma vez que as funções a serem chamadas não são explicitadas nas instruções. As funções são referenciadas a partir de registradores, previamente preenchidos. Assim, para fins de simplificação, apenas funções definidas no escopo global podem ser chamadas, fazendo com que as referências presentes nos registradores sejam obtidas a partir de variáveis globais. Finalmente um mecanismo especial para tratamento a chamadas de funções é realizado em conjunto com o opcode GETGLOBAL. RETURN Retorno de uma função, traduzido para uma instrução similar na geração de código. FORLOOP Iteração em um laço f or numérico. Tem uma semântica associada sofisticada, uma vez que uma série de atribuições e testes são definidos para as instruções. Seu tratamento é simples, pois tais operações são diretamente tratadas pela geração de código. TFORLOOP, TFORPREP Iteração em um laço f or percorrendo elementos de uma tabela. Não suportados, dado que tabelas não o são. SETLIST, SETLISTO Atribuição e inicialização de tabelas, tratando-as como listas. Novamente, não suportados. CLOSE Finaliza o contexto de execução em questão, especificamente o set de registradores. É simplesmente ignorado pela ferramenta, pois ao contrário da LVM, o set de registradores da arquitetura alvo é fixo e não pode ser redimensionado. CLOSURE Instancia (cria) uma nova função a partir de um dado protótipo précompilado. Tal operação é utilizada pois funções podem ser criadas dinamicamente e eventualmente acessam upvalues que devem ser determinadas em tempo de execução. Como resultado, tem-se uma referência à função criada em um registrador. Assim, geralmente instruções desta classe são seguidas por instruções que armazenam a referência em variáveis ou em tabelas. Na ferramenta, tais instruções são utilizadas internamente para associar os protótipos de funções aos nomes de funções utilizados no restante do código, preenchendo-os na tabela de sı́mbolos. Tal esquema é processado em conjunto com o tratamento às instruções com o opcode SETGLOBAL. 4.3.3 O gerador de código para PIC Definido o conjunto de instruções e funcionalidades da linguagem Lua tratadas pela ferramenta, a geração de código é realizada pelo chamado backend. Mais do que a geração de código, o backend é responsável pelo suporte à arquitetura alvo como um todo. Isto envolve a correta utilização de seu modelo de memória, a definição de polı́ticas para alocação de recursos (no caso de registradores e de variáveis globais) e para passagem e retorno de parâmetros a funções. Quanto ao modelo de memória, a famı́lia de microcontroladores PIC oferece apenas um espaço de endereçamento de dados, visto pelo software como um set 25 registradores para uso geral. Este acaba por ser utilizado pela ferramenta para acomodar tanto o set de registradores da LVM, quanto as variáveis globais e ainda uma pilha mantida em software para passagem de parâmetros. Foi então definida a polı́tica para alocação de registradores: • As posições de memória iniciais são utilizadas para acomodar as variáveis globais. Uma vez que este número é determinado em tempo de compilação, tem-se de antemão o tamanho em memória por elas ocupado. • As posições seguintes são reservadas para acomodar o set de registradores utilizado pela Máquina Virtual Lua. Como o tamanho do set de registradores é variável e dependente da função em execução, seu limite não é definido e tem seu espaço compartilhado com a pilha definida em seqüência. • As últimas posições de memória são utilizadas para acomodar uma pilha mantida em software para passagem de parâmetros e salvamento de contexto entre chamadas de funções. Sendo uma pilha, tal região tem seu tamanho modificado dinamicamente e compartilhado com o espaço reservado para o set de comandos. A figura 4.2 ilustra esta polı́tica de alocação, mostrando valores reais de posições de memória para o PIC16F876. Figura 4.2: Mapa de utilização de memória no PIC Finalmente, é definida a polı́tica de passagem de parâmetros entre funções e de salvamento de contexto. Tal esquema segue o modelo clássico de passagem de parâmetros por pilha, o que permite a definição de funções reentrantes e mesmo recursivas. Adicionalmente, o contexto de execução de uma função (do ponto de vista do Lua) é seu set de registradores. Assim,prólogos e epı́logos são adicionados aos códigos gerados para as funções, tratando o salvamento e a recuperação dos registradores utilizados de uma maneira transparente. 4.3.4 Extensões à linguagem Lua A linguagem Lua define uma série de operações e funcionalidades para manipulação de dados. Tais dados são manipulados sobre um set de registradores genéricos, que acabam sendo mapeados automaticamente para o set de registradores do microcontrolador. Tal polı́tica é limitada, uma vez que impede que dados sejam obtidos 26 e gerados para o meio externo. Assim, foi definido um mecanismo para acesso direto ao hardware do microcontrolador. Pode-se então manipular diretamente suas portas de entrada e saı́da, além de seus registradores especiais para configuração e manipulação de periféricos. Do ponto de vista dos programas em escritos em Lua, uma tabela global regs é disponibilizada, cujos elementos correspondem exatamente aos registradores internos do microcontrolador. Adicionalmente, cada registrador pode ter seus bits manipulados individualmente. Tratando-os como tabela, seus bits são acessı́veis tanto por mnemônicos como por seus ı́ndices. O trecho de código presente na figura 4.3 ilustra um pouco das possibilidades de sua utilização. __regs.PORTA = 0xFF __regs.PORTB[7] = 0 -- seta todos os bits da porta A -- limpa o bit 7 da porta B saved_rcif = __regs.PIR1.RCIF -- guarda a flag de interrupç~ ao Figura 4.3: Exemplo de uso da tabela regs Além disso, um mecanismo para codificação de instruções em linguagem de montagem, embutidas em código fonte Lua também é previsto. A função especial asm recebe como argumento uma string que é copiada literalmente no código gerado, para uso exclusivo na ferramenta de montagem. Pode-se assim ter trechos com desempenho crı́tico codificados diretamente em assembly. Tem-se ainda a possibilidade de utilização de instruções especiais da arquitetura, não sintetizáveis pela ferramenta picoLua. Um exemplo de utilização desta funcionalidade é mostrada na figura 4.4. ------- Divisao por dois, implementada com um rotate-right. O assembly inline é necessário para emitir a instruç~ ao ’rrf’, n~ ao gerada pelo picoLua. Os argumentos das funç~ oes s~ ao mapeados automaticamente para os registradores R0, R1, R2, ... Sabendo-se isso, pode-se manipular seus valores diretamente em assembly! divide_by_2 = function( x ) __asm__( "rrf R0, f" ) return x end -- Coloca o microcontrolador em modo standby, através -- da instruç~ ao ’sleep’ gerada por assembler inline sleep = function() __asm__( "sleep" ) end Figura 4.4: Exemplo de uso de assembly inline Tem-se ainda a possibilidade de passar parâmetros para o montador que processará os dados gerados pela ferramenta picoLua. Tais parâmetros envolvem o exato modelo do microprocessador a ser utilizado, bem como sua palavra de configuração. O modelo é importante também para definir a polı́tica de alocação de memória. Já a 27 palavra de configuração traz informações utilizadas pelo montador ao gerar imagens para programação dos microcontroladores. Para acomodar tais informações, foram definidas as funções reservadas target e conf ig , que recebem como parâmetro, respectivamente, uma string que define o modelo do microcontrolador e a palavra de configuração a ser embutida na imagem para programação. A figura 4.5 traz exemplos de utilização de tais funcionalidades na ferramenta picoLua. __target__( "p16f876" ) __config__( "_HS_OSC & _WDT_OFF & _PWRTE_ON &" "_BODEN_ON & _LVP_OFF & _CPD_OFF &" "_WRT_ENABLE_OFF & _DEBUG_OFF & _CP_OFF " ) Figura 4.5: Exemplo de definição do target e da palavra de configuração Por fim, permite-se a associação de uma função ao tratamento de interrupções. Tal facilidade é novamente oferecida através de uma função especial, no caso a função interrupt . Tal função recebe como parâmetro o nome da função que efetivamente tratará as interrupções e se encarrega de gerar o código de entrada e saı́da de interrupção. O salvamento de contexto não é implementado, ficando a cargo da aplicação lidar com tais detalhes. Exemplos de utilização deste recurso são as aplicações que utilizam o módulo de comunicação serial, apresentadas no capı́tulo 5. 4.4 Metodologia proposta A ferramenta proposta tem uso ilustrado pela figura 4.6. Percebe-se a possibilidade de utilização da linguagem Lua durante a fase de desenvolvimento e prototipação do software, através de um ambiente de simulação em um computador pessoal, por exemplo. A mesma descrição pode então, uma vez simulada e validada, ser utilizada como entrada da ferramenta picoLua, gerando assim o código final a ser programado no sistema alvo. É ilustrado também a possibilidade de se ter o alvo da ferramenta redefinido, gerando código em linguagem C, por exemplo. Finalmente, é apresentada uma comparação entre a metodologia proposta através da ferramenta picoLua e a metodologia SASHIMI. A tabela 4.3 mostra algumas de suas caracterı́sticas, principalmente no que diz respeitos às arquiteturas, linguagens e ambientes envolvidos. Tabela 4.3: Comparação entre as metodologias picoLua e SASHIMI Caracterı́stica picoLua SASHIMI Linguagem Lua Java Ambiente de execução LVM JVM Aplicação SW embarcado HW/SW embarcados Ambiente de desenvolvimento JDK — Entrada bytecodes Lua bytecodes Java Saı́da assembly PIC ASIP femtoJava (VHDL) + programa 28 Figura 4.6: Metodologia proposta com a ferramenta picoLua 29 5 ESTUDOS DE CASO Este capı́tulo apresenta algumas aplicações que ilustram o uso da ferramenta picoLua e da linguagem Lua em sistemas embarcados. Em um primeiro momento, a ferramenta picoLua é utilizada para a codificação isolada de funções básicas para o microcontrolador PIC na linguagem Lua. Tais funções permitem comparações diretas com implementações equivalentes em linguagem assembly, o que é interessante para avaliar a eficiência da geração de código pela ferramenta. Em um segundo momento, a ferramenta é utilizada para codificar o software de um sistema de aquisição de dados, com comunicação em tempo real com um computador pessoal. São explorados então alguns conceitos da engenharia de software, como a modularização e a reutilização de código. Finalmente, é apresentado um modelo para desenvolvimento de aplicações distribuı́das, onde a linguagem Lua é utilizada para descrever o software de seus diversos componentes. Nele podem ser exploradas algumas das caracterı́sticas interessantes da linguagem, como a migração de código e a utilização da mesma em múltiplas plataformas. 5.1 Multiplicação de inteiros Neste exemplo é realizada a multiplicação de dois números inteiros. Tais números são representados por variáveis sob o ponto de vista da linguagem Lua, mas que são mapeados automaticamente para registradores de 8 bits do microprocessador. O resultado obtido é armazenado em outra variável, cuja representação também é dada em 8 bits. Neste sentido, nenhum controle de overflow é realizado. O exemplo é útil por explorar a codificação de operações aritméticas e de laços em Lua. A figura 5.1 apresenta o programa elaborado em suas três representações tratadas pela ferramenta: a primeira coluna contém o programa original, codificado na linguagem Lua; a segunda coluna, os bytecodes gerados pelo compilador Lua, identificando o opcode e seus operandos; finalmente, a terceira coluna mostra o código assembly gerado, resultado da recompilação dos bytecodes pela ferramenta picloLua. Com este exemplo, percebe-se nitidamente o efeito do tratamento individualizado de instruções por parte da ferramenta. Uma vez que o contexto em que uma instrução está inserida não é levado em consideração, é definida uma correspondência um-para-um entre uma dada instrução da LVM e o código gerado pela ferramenta. O que por um lado simplifica a implementação da ferramenta, por outro leva à geração de código ineficiente, passı́vel de ser otimizado. Isto pode ser percebido nos casos onde valores são movidos do registrador de trabalho do microprocessador (registrador w) para registradores de uso geral (R0, R1, ...) e na instrução seguinte 30 Código Lua original Bytecodes Lua (LVM) Assembly PIC --------------------------------------------------------------------------------local a, b LOADNIL 0, 1, 0 clrf R0 clrf R1 local res = 0 LOADK 2, 2, 0 movlw 0x00 movwf R2 for bit = 0,7 do LOADK 3, 2, 0 movlw 0x00 movwf R3 LOADK 4, 3, 7 movlw 0x07 movwf R4 LOADK 5, 4, 1 movlw 0x01 movwf R5 SUB 3, 3, 5 movf R3, w subwf R5, w movwf R3 JMP 0, 12 goto L0P22 __asm__( "rrf R0, f" ) GETGLOBAL 6, 5 L0P10: LOADK 7, 6 CALL 6, 2, 1 rrf R0, f if __regs.STATUS.C then GETGLOBAL 6, 7 movf STATUS, w GETTABLE 6, 6, 258 movwf R6 GETTABLE 6, 6, 259 clrf R6 btfsc STATUS, C comf R6, f TEST 6, 6 0 movf R6, w btfsc STATUS, Z JMP 0, 1 goto L0P19 res = res + b ADD 2, 2, 1 movf R2, w addwf R1, w movwf R2 end __asm__( "rlf R1, f" ) GETGLOBAL 6, 5 LOADK 7, 10 CALL 6, 2, 1 rlf R1, f FORLOOP 3,-13 L0P22: movf R5, w addwf R3, f movf R3, w subwf R4, w btfsc STATUS, C goto L0P10 end RETURN 0, 1, 0 return Figura 5.1: Codificações em Lua, bytecodes Lua e assembly do PIC 31 são trazidos novamente para o registrador de trabalho. Obviamente, tal overhead não é gerado codificando-se diretamente na linguagem de montagem, mas, por outro lado, pode ser reduzido drasticamente através de ferramentas de otimização de código automatizadas ou pela análise semântica durante o processo de geração de código. Por fim, o mesmo programa foi codificado diretamente em assembly e teve seu tamanho avaliado para fins de comparação com o código gerado pela ferramenta. O resultado desta comparação é apresentado na seção 5.5, juntamente com as comparações dos demais programas de teste. 5.2 Comunicação serial Este exemplo configura e utiliza o módulo de comunicação serial (USART) presente no microcontrolador, para realizar um loop local em uma comunicação com um microcomputador pessoal. São utilizadas as funcionalidades da ferramenta picoLua para acesso direto ao hardware e aos registradores do PIC. Novamente, é feita uma comparação com o mesmo sendo descrito diretamente em assembly. Os resultados são também apresentados na seção 5.5. 5.3 Sistema de medição de vibração O Sistema de Medição de Vibração com Acelerômetros de Estado Sólido, desenvolvido no Laboratório de Processamento de Sinais e Imagens, LaPSI, da Universidade Federal do Rio Grande do Sul, é um sistema de aquisição de dados de aceleração obtidos através de acelerômetros de estado sólido. O sistema é capaz de transmiti-los em tempo real para um computador PC, onde a comunicação pode ser feita via interface serial ou paralela. O sistema, batizado de AXPC, tem seu uso ilustrado na figura 5.2, onde é inserido em um sistema tı́pico de medição de vibração (SOC 2002). Figura 5.2: Sistema tı́pico de medição de vibração O AXPC faz uso de um microntrolador PIC e tem seu firmware originalmente codificado na linguagem assembly. Seu firmware foi então reescrito na linguagem Lua com a ferramenta picoLua. Assim, foi explorada a modularização de código através da criação de módulos independentes para configuração e inicialização de dispositivos, para atendimento a interrupções e para recebimento e transmissão de dados. 32 A possibilidade de reuso de código foi validada utilizando as funções previamente criadas para o programa de exemplo exposto na seção 5.2. O código fonte para o firmware do AXPC escrito em Lua é apresentado em anexo e serve como exemplo completo da utilização da ferramenta. São explorados praticamente todo os recursos suportados da linguagem, além das construções especı́ficas da ferramenta picoLua para utilização nos microcontroladores PIC. 5.4 Modelo para aplicações distribuı́das Finalmente, é apresentado um modelo para desenvolvido de aplicações distribuı́das que envolvam diferentes arquiteturas computacionais e que façam uso da linguagem Lua. Apesar de extremamente simples, o modelo torna-se interessante por ser baseado em uma rede heterogênea e por demonstrar a versatilidade da linguagem. 5.4.1 Plataformas utilizadas As plataformas utilizadas são normalmente associadas à domı́nios de aplicações diferentes, o que permite a construção de um sistema significativo do ponto de vista da utilização da linguagem. São elas: • Computador pessoal (PC) com o sistema operacional Linux. A linguagem Lua é utilizada em sua implementação tradicional, baseado na sua máquina virtual. São utilizadas suas bibliotecas padrão, com algumas modificações para acomodar o suporte a sockets TCP/IP (NEH 2003) e à comunicação serial. Tais facilidades são utilizadas para comunicação com os demais dispositivos envolvidos na aplicação. • Handheld iPAQ, com o sistema operacional PocketPC (originalmente conhecido como Microsoft WindowsCE). O suporte à linguagem Lua é dado através do porte da linguagem e da máquina virtual (BLA 2003), especialmente modificadas para utilizarem a API deste sistema operacional (Win32 API). É acrescentado o suporte a sockets TCP/IP e à interface gráfica com o usuário (GUI). É conectado ao PC via interface USB, sobre a qual é definida uma interface PPP para transporte de TCP/IP; • Kit microcontrolado (PIC16F876), onde a ferramenta picoLua é utilizada como suporte à linguagem Lua. A interface serial é utilizada para comunicação com o PC e, indiretamente, com o iPAQ. 5.4.2 Conectividade A comunicação entre os elementos do sistema é dada quase integralmente através de sockets. A exceção é dada ao PIC, que não possui uma implementação da pilha TCP/IP. Assim, para tornar a comunicação entre os nós perfeitamente simétrica, foi criado um pequeno daemon que, executado no PC, faz o intermédio entre a interface serial para comunicação com o PIC e um socket para comunicação com os demais elementos. Por fim, tem-se uma rede que, embora heterogênea, tem seus elementos acessı́veis por uma maneira única. A figura figura 5.3 apresenta tal modelo de comunicação. 33 Figura 5.3: Arquitetura para aplicações distribuı́das 5.4.3 Benefı́cios Ao se utilizar um modelo como o proposto para aplicações distribuı́das, uma série de benefı́cios são atingidos, além das vantagens já mostradas na seção 3.3.1. Pode-se citar: Metodologia de projeto única Por se tratar de uma linguagem com implementações para diversas plataformas, tem-se possibilidade de seguir uma metodologia de projeto única, compartilhando ferramentas de projeto, descrições de sistemas e mesmo bibliotecas e código-fonte; Descrição única do sistema No momento em que todos os nós são codificados na mesma linguagem e seguem a mesma metodologia de projeto, pode-se utilizar uma descrição única do sistema. Pode-se decidir, em tempo de projeto ou mesmo de execução, o que será executado em cada nó, sem a necessidade de se ter implementações diferentes para as diferentes arquiteturas envolvidas; Migração de código/carga dinâmica de aplicações Pode-se utilizar a migração de código para implementar abordagens como a carga dinâmica de programas. Como conseqüência, tem-se a possibilidade de se implementar arquiteturas reconfiguráveis ou mesmo de reduzir o as necessidades de memória dos elementos do sistema, tornando-os simples agentes que recebam e executem programas remotamente. 5.5 Resultados Com o propósito de avaliar o código gerado pela ferramenta e comparar seu desempenho com soluções equivalentes, descritas diretamente em assembly, alguns programas de exemplo foram escolhidos. O tamanho, medido em palavras da memória de programa, foi escolhido como critério para a avaliação de tais programas. Esta métrica, além de avaliar a eficiência na geração de código por parte da ferramenta, é válida também para avaliar o tempo de execução dos programas. Isto porque a especificação do microcontrolador define o tempo de execução por instrução como sendo único e conhecido em tempo de desenvolvimento. 34 Por outro lado, foi avaliado também o número de linhas dos programas fonte originais. Isto é útil para quantificar e ilustrar as vantagens da utilização de uma linguagem como Lua em sistemas embarcados. Percebe-se nitidamente uma redução no número de linhas de código escritas, o tem que como conseqüência a simplificação do processo de desenvolvimento. Os resultados obtidos são mostrados na tabela 5.1, juntamente com estimativas para as possı́veis otimizações sobre os programas gerados pela ferramenta picoLua. Esta estimativa é baseada em otimizações manuais, realizadas sobre o código assembly gerado pela ferramenta. Tabela 5.1: Comparação entre implementações em Lua e Assembly Programa Multiplicação inteiros Comunicação serial Firmware AXPC Linhas de código Assembly Lua 17 11 102 55 283 157 Código gerado (palavras de memória) Assembly Lua Lua otimizado 17 34 29 80 179 133 174 482 251 35 6 CONCLUSÃO Este trabalho propõe o uso da linguagem Lua em sistemas embarcados. Para isto foi desenvolvida a ferramenta picoLua, que possibilita o uso da linguagem Lua em microcontroladores através da recompilação de bytecodes. A ferramenta mostrouse interessante por fazer a ligação entre dois domı́nios distintos: o dos sistemas embarcados, utilizando processadores simples, com restrições de recursos e de projeto bem definidas, com o das linguagens de scripting de alto nı́vel, tradicionalmente interpretadas e utilizadas em aplicações maiores. Adicionalmente, a linguagem Lua foi explorada em outros sistemas, já com recursos adicionais que permitam o uso de um sistema operacional. A prototipação de sistemas com a linguagem Lua também foi visada com este trabalho. Ao tratar o desenvolvimento dos programas de exemplo, foram postas em prática algumas idéias como a modularização e o reuso de código, importantes para acelerar o processo de desenvolvimento de software. Além disso, a utilização de um código fonte único, com partes sendo implementadas na plataforma de desenvolvimento e aos poucos sendo migradas para a plataforma alvo mostrou-se uma possibilidade interessante oferecida pela linguagem Lua. Embora pouco explorado neste trabalho, o teste de sistemas embarcados pode ser diretamente beneficiado pelo uso da linguagem Lua. Com a flexibilidade de descrição de dados e de integração de módulos, a linguagem Lua mostra-se uma alternativa para a elaboração de estratégias de teste. Parte-se de uma descrição única e centralizada dos testes a serem realizados no sistema como um todo, para então dispará-los remotamente, possivelmente em plataformas variadas. O modelo para desenvolvimento de aplicações distribuı́das, apresentado como exemplo de utilização da linguagem e da ferramenta, apresenta esta idéia ao permitir a migração de código entre os nós, eliminando a necessidade de se ter os testes previamente codificados e armazenados em todos os dispositivos envolvidos. Do ponto de vista de sua implementação, a ferramenta picoLua é igualmente interessante por ser codificada na própria linguagem Lua. Utiliza os programas em Lua já compilados em bytecodes, o que dispensa a implementação de analisadores léxico e sintático, já tratados pela linguagem em sua forma tradicional. Com isto, simplificou-se o desenvolvimento da ferramenta, pois os analisadores representam grande parte da complexidade de um compilador convencional. Permitiu-se ainda focar os esforços na geração de código e nas extensões à linguagem para sua utilização em sistemas embarcados. Por outro lado, a ferramenta ainda é um trabalho em andamento. Há muito a ser feito para torná-la uma alternativa concreta para o desenvolvimento de sistemas reais perfeitamente utilizável. O tratamento e a verificação de erros são deficitários, 36 embora já seja dada uma atenção especial aos erros de sintaxe por parte das ferramentas padrão da linguagem Lua. Além disso, o código gerado é ineficiente e muitas das construções significativas da linguagem Lua não são suportadas, como o uso de meta-tabelas para extensões de semântica, o próprio uso de tabelas e o tratamento de funções como dados. Há também a possibilidade de geração de código para outras plataformas que não a famı́lia de microcontroladores PIC atualmente suportada. Deixa-se então tais melhorias como sugestões para trabalhos futuros, juntamente com a idéia da utilização da linguagem e da ferramenta em cores como o RISCO ou os da famı́lia ARM. 37 REFERÊNCIAS [FIG 1996] FIGUEIREDO, L; IERUSALIMSCHY, R; CELES, W. Lua: an extensible embedded language. In: Dr. Dobb’s Journal 21 #12 (Dec 1996). p.26-33. [FIG 1994] FIGUEIREDO, L; IERUSALIMSCHY, R; CELES, W. The design and implementation of a language for extending applications. In: Proceedings of XXI Brazilian Seminar on Software and Hardware (1994) 273-283. [STOL 2003] STOL, K. Pirate: A Compiler for Lua targeting the Parrot Virtual Machine. Hamburg, 2003. 96p. [ITO 2000] ITO, Sergio. Projeto de Aplicações Especı́ficas com Microcontroladores Java Dedicados. Porto Alegre: PPGC da UFRGS, 2000. 84p. [HAU 2003] HAUMACHER, B. et al. Transparent Distributed Threads for Java. In: Proceedings of the 5th International Workshop on Java for Parallel and Distributed Computing (IPDPS 2003). Nice: France, IEEE Computer Society, 2003. [IER 2003] IERUSALIMSCHY, R. The Virtual Machine of Lua In: Lightweight Languages 2003 (LL3). Cambridge: MIT, 2003. [SOC 2002] SOCAL, F. Sistema de Medição de Vibração com Acelerômetros de Estado Sólido. Porto Alegre: LaPSI-DELET-UFRGS, 2002. 14p. [NEH 2003] NEHAB, D. LuaSocket: IPv4 Sockets support for the Lua language. Disponı́vel em http://www.tecgraf.puc-rio.br/ diego/luasocket/new/. Acesso em: dez. 2003. [BLA 2003] BLANDING, S. Lua on the PocketPC. Disponı́vel http://www.uoam.net/lua.html. Acesso em: dez. 2003. em 38 APÊNDICES Código fonte da ferramenta picoLua Código fonte do firmware do AXPC