Sandro Emanuel Salgado Pinto
Sistema Operativo Orientado a Objetos:porting, expansão e configuração
Sand
ro E
man
uel S
alga
do P
into
Outubro de 2012UMin
ho |
201
2Si
stem
a O
pera
tivo
Ori
enta
do a
Obj
etos
:po
rtin
g, e
xpan
são
e co
nfig
uraç
ão
Universidade do MinhoEscola de Engenharia
Outubro de 2012
Tese de MestradoCiclo de Estudos Integrados Conducentes ao Grau deMestre em Engenharia Eletrónica Industrial e Computadores
Trabalho efetuado sob a orientação doProfessor Doutor José Mendes
Sandro Emanuel Salgado Pinto
Sistema Operativo Orientado a Objetos:porting, expansão e configuração
Universidade do MinhoEscola de Engenharia
Agradecimentos
As primeiras palavras de agradecimento sao direcionadas aos meus pais, Manuel
Pinto e Paula Salgado, por todo o apoio educacional, psicologico e financeiro, prestado
durante todo o meu percurso academico, pois sem eles esta dissertacao nunca seria
uma realidade.
Ao meu orientador Professor Doutor Jose Mendes, bem como aos Professores
Doutores Adriano Tavares e Jorge Cabral, por todo o apoio prestado e por toda
confianca depositada em mim para a concretizacao deste trabalho.
Ao Embedded System Research Group do Departamento de Eletronica Industrial
da Universidade do Minho, que me acolheu e proporcionou todas as condicoes ne-
cessarias para a elaboracao da dissertacao. Um obrigado especial para o mestre Nuno
Cardoso que sempre se mostrou disponıvel para me ajudar, esclarecer e partilhar co-
nhecimentos.
Aos meus colegas de curso que me acompanharam ao longo destes anos, em espe-
cial ao Tiago Castro e Vıtor Veiga que estiveram envolvidos no projeto onde se integra
a dissertacao, bem como ao Filipe Alves por todos os momentos de companheirismo
vividos.
Finalmente, e nao menos importante, a minha namorada, Barbara Fernandes,
e ao meu grupo de amigos, CN (Filtros, Fox, Maia, Marco, Milu, Moura, Peste,
Rica, Rojao, Slim), por me terem alegrado, compreendido e apoiado sobretudo nos
momentos de maior angustia e desilusao.
A todos, um muito obrigado!
iii
Resumo
Nos ultimos anos, cerca de 98% da producao anual de microprocessadores teve
como finalidade os sistemas embebidos [1]. No entanto, o desenvolvimento de software
e aplicacoes bare-metal pode tornar-se complexo, provocando uma enorme pressao
no time-to-market, aumento do tempo e esforco (colaboradores/hora) de desenvol-
vimento, e deficiente qualidade do sistema final. A estrategia passa entao por usar
sistemas operativos, tornando o desenvolvimento mais simples, rapido e seguro.
Normalmente, os sistemas operativos monolıticos nao se adequam as necessidades
e limitacoes dos sistemas embebidos, pois maximizam o numero de plataformas e
funcionalidades oferecidas, o que se traduz num aumento no consumo de recursos.
Por isso, a tendencia recai sobre sistemas operativos de tempo-real (baseados em
microkernel) desenvolvidos e adaptados a arquitetura do processador, e aos requisitos
e restricoes da aplicacao.
No entanto, com o aumento da complexidade dos sistemas atuais, existe uma pro-
cura crescente na configurabilidade, variabilidade e reutilizacao dos sistemas embebi-
dos. A maioria desses sistemas gere a variabilidade utilizando compilacao condicional
ou programacao orientado a objetos. A primeira aumenta a complexidade de gestao
do codigo. A ultima providencia a modularidade e adaptabilidade necessarios para
simplificar a tarefa de desenvolvimento de software reutilizavel e customizavel, no
entanto, degrada o desempenho e os recursos de memoria do sistema.
Neste sentido, a presente dissertacao propoe a utilizacao de C++ template me-
taprogramming como a metodologia para a gestao da variabilidade de um sistema
operativo orientado a objetos. Utilizando esta tecnica de programacao, e possıvel
gerar apenas as funcionalidades pretendidas, garantindo assim codigo otimizado e
ajustado as necessidades da aplicacao e aos recursos de hardware.
v
Abstract
In recent years, approximately 98% of microprocessors annual production was
aimed at embedded systems [1]. However, the development of bare-metal application
software can become complex, leading to a tremendous pressure on time-to-market,
increased time and effort development (staff / hour), and poor final system quality.
So, the strategy is to use operating systems, making development easier, faster and
safer.
Typically, monolithic operating systems do not fit the requirements and limitati-
ons of embedded systems since they attempt to maximize the number of supported
platforms and functionalities offered, which results in an increase in the consumption
of resources. Therefore, the trend became using real-time operating systems (mi-
crokernel based) developed and adapted to processor architecture and to application
requirements and constraints.
However, with the growing complexity of current systems, there is an increasing
demand for configurability, variability and reuse of embedded systems. Most of these
systems manage variability using conditional compilation or object oriented program-
ming. The former paradigm increases the management complexity of code. The latter
provides the modularity and adaptability needed to simplify the task of developing
reusable and customizable software; however, it degrades performance and memory
resources.
In this context, this thesis proposes the use of C++ template metaprogramming
as a methodology for managing the variability of an object-oriented operating system.
Using this advanced programming technique, it is possible to generate only the desired
functionalities, thus ensuring that code is optimized and adjusted to application
requirements and hardware resources.
vii
Conteudo
1 Introducao 1
1.1 Contextualizacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Organizacao da Dissertacao . . . . . . . . . . . . . . . . . . . . . . . 4
2 Estado da Arte 5
2.1 Programacao Orientada a Objetos . . . . . . . . . . . . . . . . . . . . 5
2.1.1 Paradigmas de Programacao . . . . . . . . . . . . . . . . . . . 6
2.1.2 Objetos e Classes . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.3 Princıpios Fundamentais . . . . . . . . . . . . . . . . . . . . . 8
2.2 Sistemas Operativos . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.2.1 Arquitetura dos Sistemas Operativos . . . . . . . . . . . . . . 12
2.2.2 Sistemas Operativos de Tempo-Real . . . . . . . . . . . . . . . 14
2.2.3 Sistemas Operativos Orientados a Objetos . . . . . . . . . . . 15
2.3 Configurabilidade e Variabilidade no Software: tecnicas de programacao 21
2.3.1 Compilacao Condicional . . . . . . . . . . . . . . . . . . . . . 22
2.3.2 Orientacao a Objetos . . . . . . . . . . . . . . . . . . . . . . . 23
2.3.3 Orientacao a Componentes . . . . . . . . . . . . . . . . . . . . 23
2.3.4 Orientacao a Funcionalidades . . . . . . . . . . . . . . . . . . 24
2.3.5 Orientacao a Aspetos . . . . . . . . . . . . . . . . . . . . . . . 25
2.3.6 Programacao Generativa . . . . . . . . . . . . . . . . . . . . . 26
2.4 Conclusoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3 Especificacao do Sistema 29
3.1 Microcontrolador 8051 . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.1.1 Arquitetura de Memoria . . . . . . . . . . . . . . . . . . . . . 31
ix
3.1.2 Registos Basicos . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.1.3 Perifericos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.1.4 Interrupcoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.1.5 Arquitetura do Conjunto de Instrucoes . . . . . . . . . . . . . 35
3.2 ADEOS: A Decent Embedded Operating System . . . . . . . . . . . . 37
3.2.1 Tarefas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2.2 Escalonador . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.2.3 Sincronizacao de Tarefas . . . . . . . . . . . . . . . . . . . . . 45
3.3 Template MetaProgramming . . . . . . . . . . . . . . . . . . . . . . . 47
3.3.1 Blocos Basicos do Template Metaprogramming . . . . . . . . . 47
3.3.2 O Fatorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.3.3 Lista Ligada Estatica . . . . . . . . . . . . . . . . . . . . . . . 51
3.4 Ambiente de Desenvolvimento . . . . . . . . . . . . . . . . . . . . . . 52
3.4.1 Compilador IAR C/C++ para o 8051 . . . . . . . . . . . . . . 55
4 Implementacao do Sistema 65
4.1 Porting do ADEOS para a Plataforma MCS-51 . . . . . . . . . . . . 65
4.1.1 Analise do Codigo Dependente do Processador . . . . . . . . . 67
4.1.2 Porting do Codigo Dependente do Processador . . . . . . . . . 75
4.2 Upgrade do ADEOS . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.2.1 Upgrade: clock-tick no escalonador . . . . . . . . . . . . . . . 83
4.2.2 Upgrade: device drivers . . . . . . . . . . . . . . . . . . . . . . 85
4.2.3 Upgrade: escalonador power-aware . . . . . . . . . . . . . . . 108
4.3 Refactoring do ADEOS . . . . . . . . . . . . . . . . . . . . . . . . . . 114
4.3.1 Diagrama de Funcionalidades . . . . . . . . . . . . . . . . . . 114
4.3.2 Estrategia de Gestao da Variabilidade . . . . . . . . . . . . . . 117
4.3.3 Reestruturacao do ADEOS . . . . . . . . . . . . . . . . . . . . 120
5 Resultados Experimentais 127
5.1 Ambiente de Testes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
5.2 Metricas de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
5.3 Testes Realizados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
5.3.1 Teste ao Sistema Operativo . . . . . . . . . . . . . . . . . . . 131
5.3.2 Teste ao driver USART . . . . . . . . . . . . . . . . . . . . . 134
x
6 Conclusoes 139
6.1 Conclusao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
6.2 Trabalho Futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
A Placa Circuito Impresso: spi2c 145
xi
Lista de Figuras
2.1 Modelos arquiteturais de um sistema operativo: monolıtico e microkernel 13
2.2 Diagrama de classes do core do sistema operativo BOSS . . . . . . . 20
2.3 Orientacao a funcionalidades: hierarquia de classes e modelo de funci-
onalidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.4 Juncao do codigo aspeto no codigo dos componentes . . . . . . . . . . 27
3.1 Diagrama de blocos do microcontrolador 8051 classico . . . . . . . . . 30
3.2 Mapa de memoria do 8051 generico . . . . . . . . . . . . . . . . . . . 32
3.3 Diagrama de classes do ADEOS . . . . . . . . . . . . . . . . . . . . . 38
3.4 Relacao dos estados das tarefas no ADEOS . . . . . . . . . . . . . . . 40
3.5 Ilustracao da lista de tarefas prontas a executar (readyList) . . . . . . 44
3.6 Resolucao dos templates no calculo do fatorial . . . . . . . . . . . . . 51
3.7 Processo de compilacao de codigo fonte em codigo executavel/maquina 54
4.1 Arquitetura de software do ADEOS . . . . . . . . . . . . . . . . . . . 66
4.2 Pilha do sistema apos entrada na funcao contextInit . . . . . . . . . . 69
4.3 Pilha da tarefa apos inicializacao . . . . . . . . . . . . . . . . . . . . 71
4.4 Diagrama de classes do driver PWM . . . . . . . . . . . . . . . . . . 87
4.5 Diagrama de classes do driver UART . . . . . . . . . . . . . . . . . . 92
4.6 Diagrama de classes do driver GPIO . . . . . . . . . . . . . . . . . . 96
4.7 Formato da trama I2C . . . . . . . . . . . . . . . . . . . . . . . . . . 99
4.8 Diagrama de classes do driver I2C . . . . . . . . . . . . . . . . . . . 100
4.9 SPI: barramento e diagrama temporal . . . . . . . . . . . . . . . . . . 104
4.10 Diagrama de classes do driver SPI . . . . . . . . . . . . . . . . . . . . 105
4.11 Diagrama de funcionalidades do ADEOS . . . . . . . . . . . . . . . . 115
5.1 Placa de desenvolvimento 8051DKUSB . . . . . . . . . . . . . . . . . 128
xiii
5.2 Diagrama de funcionalidades do sistema operativo (teste ao sistema
operativo) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
5.3 Resultados de desempenho e footprint de memoria (teste ao sistema
operativo) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
5.4 Resultados de gestao do codigo (teste ao sistema operativo) . . . . . . 134
5.5 Resultados de desempenho e footprint de memoria (teste ao driver
USART) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
5.6 Resultados de gestao do codigo (teste ao driver USART) . . . . . . . 137
A.1 PCB spi2c: esquematico . . . . . . . . . . . . . . . . . . . . . . . . . 146
A.2 PCB spi2c: layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
xiv
Lista de Tabelas
2.1 Implementacao da classe Shape em C++ e Java . . . . . . . . . . . . 8
3.1 Vetores de interrupcao na famılia MCS-51 . . . . . . . . . . . . . . . 34
3.2 Modos de enderecamento do 8051 . . . . . . . . . . . . . . . . . . . . 35
3.3 Resultados de desempenho e memoria das aplicacoes Fatorial (C++
dinamico) e Fatorial (TMP) . . . . . . . . . . . . . . . . . . . . . . . 51
3.4 Codigo C++ TMP e codigo assembly da aplicacao estatica do fatorial 53
3.5 Convencoes de chamada de funcoes no compilador C/C++ 8051 da IAR 60
3.6 Registos utilizados nos parametros das funcoes . . . . . . . . . . . . . 61
3.7 Registos utilizados no retorno das funcoes . . . . . . . . . . . . . . . 62
4.1 Rotina de interrupcao do temporizador 1 . . . . . . . . . . . . . . . . 84
4.2 Configuracao do temporizador 1 . . . . . . . . . . . . . . . . . . . . . 85
4.3 Inicializacao da contagem do temporizador 1 . . . . . . . . . . . . . . 85
4.4 Implementacao C++ e assembly da selecao da frequencia . . . . . . . 114
4.5 Classes especificas da funcionalidade example . . . . . . . . . . . . . 117
4.6 Declaracao da classe template da funcionalidade Sched . . . . . . . . 120
4.7 Definicao das templates generica e especificas da funcionalidade Sched 121
4.8 Declaracao da classe template da funcionalidade Task . . . . . . . . . 123
4.9 Definicao das templates generica e especificas da funcionalidade Task 125
5.1 Caracterısticas de hardware da placa de desenvolvimento 8051DKUSB 128
5.2 Configuracao usada no teste ao sistema operativo . . . . . . . . . . . 131
xv
Lista de Listagens
2.1 Implementacao C (iterativa) do calculo do fatorial de um numero . . 6
2.2 Implementacao Haskell do calculo do fatorial de um numero . . . . . 6
2.3 Definicao da classe Triangle como herdeira da classe Shape . . . . . . 9
2.4 Exemplo de polimorfismo dinamico na classe Triangle . . . . . . . . . 10
2.5 Declaracao da classe Shape como abstrata . . . . . . . . . . . . . . . 11
2.6 Exemplo da utilizacao de compilacao condicional . . . . . . . . . . . 22
3.1 Declaracao da classe Task . . . . . . . . . . . . . . . . . . . . . . . . 39
3.2 Funcao de iniciacao das tarefas - run . . . . . . . . . . . . . . . . . . 41
3.3 Construtor da classe Task . . . . . . . . . . . . . . . . . . . . . . . . 41
3.4 Metodo schedule da classe Sched . . . . . . . . . . . . . . . . . . . . 44
3.5 Construtor da classe Mutex . . . . . . . . . . . . . . . . . . . . . . . 46
3.6 Valores em template metaprogramming . . . . . . . . . . . . . . . . . 47
3.7 Funcoes em template metaprogramming . . . . . . . . . . . . . . . . . 48
3.8 Saltos condicionais em template metaprogramming . . . . . . . . . . . 48
3.9 Recursividade em template metaprogramming . . . . . . . . . . . . . 49
3.10 Implementacao C++ recursiva do calculo do fatorial . . . . . . . . . . 49
3.11 Implementacao C++ TMP recursiva do calculo do fatorial . . . . . . 50
3.12 Implementacao C++ TMP de uma lista ligada estatica de inteiros . . 51
3.13 Metafuncao Length da lista ligada estatica . . . . . . . . . . . . . . . 52
3.14 Funcao de interrupcao de overflow do timer 0 . . . . . . . . . . . . . 57
3.15 Exemplo de utilizacao de inline assembler no compilador IAR . . . . 58
3.16 Definicao de uma funcao implementada num ficheiro assembly externo 59
3.17 Estrutura de um ficheiro assembly gerado pelo compilador IAR . . . . 59
4.1 Ficheiro bsp.h para a arquitetura 80188 . . . . . . . . . . . . . . . . . 67
4.2 Prototipo da funcao contextInit . . . . . . . . . . . . . . . . . . . . . 68
4.3 Prototipo da funcao contextSwitch . . . . . . . . . . . . . . . . . . . . 73
xvii
4.4 Definicao da estrutura do estado da maquina (8051) de cada tarefa . 76
4.5 Macros para delimitacao de uma seccao crıtica . . . . . . . . . . . . . 76
4.6 Macro para comutacao de contexto (ContextSwitch) . . . . . . . . . . 77
4.7 Configuracao do clock-tick do escalonador . . . . . . . . . . . . . . . 85
4.8 Declaracao da classe Pwm8051 . . . . . . . . . . . . . . . . . . . . . 88
4.9 Estrutura de configuracao da classe Pwm8051 . . . . . . . . . . . . . 89
4.10 Enumeracoes da classe Pwm8051 . . . . . . . . . . . . . . . . . . . . 89
4.11 Metodo config da classe Pwm8051 . . . . . . . . . . . . . . . . . . . . 90
4.12 Declaracao da classe Uart8051 . . . . . . . . . . . . . . . . . . . . . . 93
4.13 Construtor da classe Uart8051 com configuracao . . . . . . . . . . . . 94
4.14 Metodos txStart e rxStart da classe Uart8051 . . . . . . . . . . . . . 94
4.15 Declaracao da classe Gpio8051 . . . . . . . . . . . . . . . . . . . . . 97
4.16 Metodo config da classe Gpio8051 . . . . . . . . . . . . . . . . . . . . 97
4.17 Declaracao da classe I2c051 . . . . . . . . . . . . . . . . . . . . . . . 101
4.18 Construtor por defeito da classe I2c051 . . . . . . . . . . . . . . . . . 102
4.19 Metodos start e write char da classe I2c8051 . . . . . . . . . . . . . 102
4.20 Declaracao da classe Spi8051 . . . . . . . . . . . . . . . . . . . . . . 106
4.21 Construtor da classe Spi8051 com configuracao . . . . . . . . . . . . 107
4.22 Metodos read char da classe Spi8051 . . . . . . . . . . . . . . . . . . 107
4.23 Construtor da classe do escalonador power-aware . . . . . . . . . . . 111
4.24 Alteracoes na ISR do clock-tick do escalonador . . . . . . . . . . . . . 112
4.25 Implementacao do metodo defer . . . . . . . . . . . . . . . . . . . . . 113
4.26 Ficheiro example tmp.h . . . . . . . . . . . . . . . . . . . . . . . . . . 118
4.27 Transparencia no codigo de acesso a funcionalidade example . . . . . 119
4.28 Transparencia no codigo de acesso a funcionalidade Sched . . . . . . . 122
4.29 Transparencia no codigo de acesso a funcionalidade Task . . . . . . . 124
xviii
Capıtulo 1
Introducao
Neste capıtulo e contextualizado o ambito desta dissertacao, sao definidos os ob-
jetivos a atingir, finalizando o capıtulo com a organizacao da dissertacao.
1.1 Contextualizacao
Vivemos na era tecnologica. As sociedades modernas atuais estao cada dia mais
dependentes de sistemas eletronicos e informaticos responsaveis por substituir e sim-
plificar as tarefas do quotidiano. Seja na industria, na medicina, na aviacao, ou
simplesmente em casa, cada vez mais existe uma necessidade de desenvolver solucoes
computacionais que auxiliem a realizacao dessas tarefas.
Neste sentido, acompanhando essa tendencia, tem-se verificado um crescimento
exponencial na utilizacao de microcontroladores no desenvolvimento e concepcao de
sistemas embebidos. Nos ultimos anos, cerca de 98% da producao anual de micropro-
cessadores teve como finalidade esse tipo de sistemas [1]. No entanto, o desenvolvi-
mento de software e aplicacoes bare-metal pode tornar-se complexo, provocando uma
enorme pressao no time-to-market, aumento do tempo e esforco (colaboradores/hora)
de desenvolvimento, e deficiente qualidade do sistema final. Isto e extremamente des-
favoravel numa sociedade extremamente competitiva e capitalista como a atual, pois
traduz-se num aumento dos custos, provavel diminuicao das vendas e consequente mi-
nimizacao dos lucros. Assim sendo, a estrategia passa por usar sistemas operativos,
de forma a que o processo de desenvolvimento seja mais simples, rapido e seguro.
Um sistema operativo e uma camada de software que abstrai o utilizador das
especificidades do hardware, atuando como um intermediario entre o utilizador e os
1
1.1. Contextualizacao
dispositivos [2]. Tem como principal objetivo, fornecer os recursos e meios ao uti-
lizador para que este desenvolva e execute os programas de forma simplificada e
eficiente. Normalmente, os sistemas operativos monolıticos nao se adequam as ne-
cessidades e limitacoes do domınio embebido. Isto porque estes sistemas operativos
procuram maximizar nao so o numero de plataformas alvo, mas tambem as funcionali-
dades oferecidas ao utilizador, o que se traduz num aumento no consumo de recursos
(memoria). Neste caso, a tendencia recai sobre sistemas operativos de tempo real
(baseados em microkernel) desenvolvidos e adaptados a arquitetura do processador,
e aos requisitos e restricoes da aplicacao.
No entanto, com o aumento da complexidade dos sistemas atuais, existe uma pro-
cura crescente na configurabilidade, variabilidade e reutilizacao dos sistemas embebi-
dos. Um bom exemplo e o sistema operativo embebido especificado pela AUTOSAR
[3]. A maioria desses sistemas gere a variabilidade utilizando compilacao condicional.
A implementacao das funcionalidades configuraveis e embebida em blocos ]ifdef -
]endif, aumentando a complexidade na compreensao e manutencao do codigo. Uma
outra abordagem consiste na utilizacao do paradigma orientado a objetos. Esta me-
todologia providencia a abstracao, modularidade, e adaptabilidade necessarios para
simplificar a tarefa de desenvolvimento de software reutilizavel, com funcionalidades
variaveis e configuraveis.
Apesar de todos os benefıcios associados a programacao orientada a objetos, ca-
racterısticas como a multipla heranca, o polimorfismo dinamico, e a abstracao di-
minuem o desempenho e introduzem um enorme overhead de memoria [4, 5]. Este
fator torna-se extremamente crıtico sobretudo em sistemas com escassez de recursos
como os sistemas embebidos. Daı que seja necessario utilizar tecnicas de programacao
avancada que contornem e resolvam este problema.
Programacao generativa, nomeadamente template metaprogramming (TMP) [6,
7], e uma das tecnicas que apresenta resultados muito promissores [8, 9, 10, 11, 12].
Com esta metodologia toda a variabilidade e processada pelo compilador durante a
fase de resolucao dos templates, ou seja, todo o processamento e realizado em tempo
de compilacao e nao em tempo de execucao. Desta forma, e possıvel gerar apenas
as funcionalidades pretendidas, garantindo assim codigo otimizado e ajustado as ne-
cessidades da aplicacao e dos recursos de hardware. A biblioteca Boost.MPL [13] e
um exemplo de uma framework que usa C++ template metaprogramming para imple-
mentar algoritmos e metafuncoes de forma estatica, isto e, no instante de compilacao.
2
Capıtulo 1. Introducao
Assim sendo, o trabalho da presente dissertacao pode ser dividido em duas partes:
primeiro (i) avaliar os sistemas operativos orientados a objetos, de modo a efetuar o
porting e expandir o sistema operativo que mais se adeque aos recursos disponibiliza-
dos pela arquitetura da famılia MCS-51; e posteriormente (ii) reestruturar o sistema
operativo de modo a permitir a sua customizacao, gerindo a variabilidade do sistema
sem porventura comprometer o seu desempenho e consumo de memoria.
Finalmente, e para terminar, importa referir que este trabalho e apenas uma fracao
de um projeto de maior dimensao que consiste tambem (i) no desenvolvimento de um
microcontrolador da famılia MCS-51 low-power customizavel, assim como (ii) um IDE
capaz de gerar o sistema operativo e o microcontrolador, configurados de acordo com
as necessidades e especificacoes do utilizador. Basicamente, o microcontrolador low-
power permite executar o sistema operativo, que e customizado e gerado de acordo
com a configuracao desejada pelo utilizador no IDE.
1.2 Objetivos
O principal objetivo da presente dissertacao esta bastante claro no tıtulo da
mesma. Assim, este passa por fazer o porting, o upgrade e a customizacao de um sis-
tema operativo orientado a objetos para a arquitetura do microcontrolador MCS-51.
Com base neste objetivo central, e possıvel ramifica-lo em varios objetivos parciais.
Assim sendo, o primeiro passa por selecionar e efetuar o porting de um sistema
operativo orientado a objetos para a plataforma MCS-51. Com base nos sistemas
operativos orientados objetos disponıveis, e necessario perceber qual ou quais sao
mais propensos para os sistemas embebidos, e qual o que se torna a solucao mais
adequada para a finalidade da dissertacao.
O segundo objetivo consiste em melhorar e aumentar o conjunto de funcionalida-
des do sistema operativo escolhido. Otimizar o codigo dependente do processador,
desenvolver um conjunto de device drivers para os varios perifericos do microcontro-
lador, e expandir as funcionalidades (escalonadores, IPCs, etc.) do sistema operativo,
sao algumas das tarefas necessarias a concretizacao deste objectivo.
O terceiro objetivo consiste na aplicacao de tecnicas de programacao avancadas
para customizar, gerir a variabilidade e minimizar o overhead do sistema operativo
orientado a objetos. Pretende-se, portanto, realizar o refactoring do sistema utili-
zando template metaprogramming.
3
1.3. Organizacao da Dissertacao
Finalmente, o quarto e ultimo objectivo passa por avaliar quais os ganhos obtidos
com a aplicacao do template metaprogramming na gestao da variabilidade, desem-
penho e overhead de memoria do sistema, em comparacao com a implementacao
utilizando polimorfismo dinamico.
1.3 Organizacao da Dissertacao
No capıtulo 1 e feita uma pequena introducao onde e contextualizado o trabalho,
sao especificados os objetivos e e descrita a estrutura da presente dissertacao.
O capıtulo 2 apresenta a fundamentacao teorica dos conceitos abordados na dis-
sertacao, nomeadamente a programacao orientada a objetos, os sistemas operativos,
e as metodologias para a gestao da variabilidade do software. Alem dos conceitos
teoricos, e discutida e justificada a escolha do sistema operativo orientado a objetos,
bem como a tecnica de programacao utilizada na gestao de variabilidade do sistema.
No capıtulo 3 sao explicados, numa aproximacao bottom-up, cada uma das ca-
madas e componentes que compoe a base do sistema a implementar. Sera analisada
e explicada a arquitetura do microcontrolador 8051, o sistema operativo ADEOS, a
tecnica de template metaprogramming e compilador C++ da IAR para o 8051.
O capıtulo 4 descreve o desenvolvimento dos componentes do sistema. Basica-
mente, descreve o trabalho propriamente desenvolvido, nomeadamente o porting do
sistema operativo ADEOS para a arquitetura MCS-51, as melhorias introduzidas e,
no final, a reestruturacao do sistema com template metaprogramming, de modo a
gerir a variabilidade e permitir a sua customizacao.
No capıtulo 5 sao apresentados os resultados experimentais dos testes realizados.
Para avaliar as metricas de desempenho e gestao do codigo, foram efetuados dois
testes distintos. No primeiro, o sistema operativo e as diversas funcionalidades foram
implementadas utilizado template metaprogramming e polimorfismo dinamico. No
segundo, apenas foi testado um modulo driver, isto para comparar os resultados
com uma implementacao na linguagem de programacao mais utilizada em sistemas
embebidos (C).
O documento termina com o capıtulo 6, que traduz as principais conclusoes do
trabalho realizado, assim como enuncia algumas sugestoes para trabalho futuro, que
visam melhorar e expandir o trabalho desenvolvido.
4
Capıtulo 2
Estado da Arte
Este capıtulo apresenta uma visao geral dos principais conceitos abordados nesta
dissertacao. Sendo o objeto de estudo desta dissertacao a customizacao e gestao da
variabilidade de um sistema operativo orientado objetos, torna-se portanto essencial
clarificar e explicar os conceitos fundamentais sobre as tres principais tematicas ad-
jacentes ao trabalho a desenvolver: (i) a programacao orientada a objetos; (ii) os
sistemas operativos; e (iii) as diferentes metodologias de programacao para a gestao
da variabilidade do software. Para alem de todo o fundamento teorico, sao tambem
apresentadas as decisoes relativamente ao sistema operativo orientado objetos, bem
como a tecnica de gestao de variabilidade, a adoptar para concretizar o trabalho
proposto.
2.1 Programacao Orientada a Objetos
A Programacao Orientada a Objetos (POO) surgiu com a necessidade de tornar
as aplicacoes de software mais proximas do modelo usado pelas pessoas para pensar
e lidar com o mundo. Em metodologias e paradigmas de programacao mais antigos,
sempre que um programador se depara com um problema, a sua preocupacao consiste
em identificar uma tarefa de computacao responsavel por solucionar esse problema.
Assim sendo, a programacao consiste apenas em encontrar uma sequencia de ins-
trucoes capaz de realizar a tarefa pretendida. No entanto, o conceito de programacao
orientada a objetos e totalmente diferente. Em vez de tarefas, existem objetos, ou
seja, entidades que tem comportamentos, retem informacao, e que podem interagir
com outros objetos. Segundo este paradigma, programar consiste em desenhar um
5
2.1. Programacao Orientada a Objetos
conjunto de objetos responsavel por modelar e resolver o problema pretendido. Es-
tes objetos podem representar entidades reais ou abstratas no domınio do problema.
Desta forma, e suposto tornar o processo de desenvolvimento mais simples e natural,
e por isso, mais facil de entender.
Resumindo, a programacao orientada a objetos fornece um conjunto de ferramen-
tas e metodos que possibilitam aos programadores desenvolver software confiavel,
sustentavel, reutilizavel e bem documentado, e que simultaneamente cumpre os re-
quisitos pretendidos pelos utilizadores.
2.1.1 Paradigmas de Programacao
Alem do paradigma orientado a objetos, existem outros paradigmas de pro-
gramacao: (i) a programacao imperativa (por exemplo, utilizado em linguagens como
C[14], Basic[15] e Pascal[16]); (ii) a programacao logica (por exemplo, Prolog[17]); e
(iii) a programacao funcional (por exemplo, Haskell[18] ou Lisp[19]).
As linguagens de programacao imperativas concebem um programa como um
conjunto de funcoes e sub-rotinas que realizam uma determinada tarefa. Por exemplo,
o codigo escrito em C da listagem 2.1 pode ser utilizado para calcular, iterativamente,
o fatorial de um numero.
int fatorial (int num)
{int result = 1;
for (int count = 1; count <= num; count++)
result ∗= count;
return result;
}
Listagem 2.1: Implementacao C (iterativa) do calculo do fatorial de um numero
A programacao funcional e um paradigma de programacao que trata a computacao
como uma avaliacao de funcoes matematicas, o que evita estados ou dados mutaveis.
Este tipo de paradigma enfatiza a definicao de funcoes, em contraste com a pro-
gramacao imperativa, que enfatiza a execucao de comandos sequenciais. O codigo
apresentado na listagem 2.2 calcula o fatorial de um numero em Haskell.
factorial :: Int −> Int
factorial 0 = 1
factorial n = n ∗ factorial (n − 1)
Listagem 2.2: Implementacao Haskell do calculo do fatorial de um numero
6
Capıtulo 2. Estado da Arte
Prolog (PROgramming in LOGic) e a linguagem de programacao mais usada se-
gundo o paradigma da programacao logica. Este paradigma e baseado em ideais
matematicos de relacoes e inferencia logica. Isto significa que, mais do que descrever
como computorizar uma solucao, um programa consiste numa base de dados de re-
gras logicas que descrevem as relacoes para uma determinada aplicacao. Por outras
palavras, quando se executa um programa para obter uma solucao, o utilizador res-
ponde a uma questao, e com base nessa resposta, o sistema procura (em tempo de
execucao), na base de dados, as regras que determinam (atraves de deducao logica)
a resposta pretendida.
Resumindo:
• em linguagens imperativas, utilizam-se procedimentos;
• em linguagens funcionais, utilizam-se funcoes;
• em linguagens de programacao logicas, utilizam-se expressoes logicas;
• em linguagens orientadas a objetos, utilizam-se objetos.
2.1.2 Objetos e Classes
Na programacao orientada a objetos, tal como designacao sugere, sao criados
objetos de software que modelam e representam os objetos do mundo real. Assim, tal
como os objetos reais, os objetos de software sao caracterizados por um determinado
estado e comportamento. Esse estado e conservado nos atributos. Cada atributo
e denominado por um identificador e e responsavel por armazenar a informacao e
dados desse estado. Por sua vez, o comportamento do objeto e implementado atraves
de metodos. Um metodo e entao uma funcao associada a um determinado objeto.
Portanto, um objeto e um componente de software que contem variaveis e metodos
intrınsecos. Alem disso, muitas vezes um objeto e designado por instancia, uma vez
que uma instancia refere-se a um objeto em particular. Por exemplo, um Porsche
Panamera e uma instancia de um carro, pois refere-se a um carro em particular.
Objetos e classes estao intrinsecamente relacionados. As classes sao as entidades
usadas para produzir e criar os objetos. Assim sendo, uma classe declara as variaveis
necessarias para reter o estado de cada objeto, assim como fornece as implementacoes
dos metodos necessarios para operar sobre o estado do objecto. Portanto, so depois
de ser criada a classe e que e possıvel criar ou instanciar objetos dessa classe. Por
outras palavras, uma classe e uma especie de planta para construir objetos. As partes
7
2.1. Programacao Orientada a Objetos
Tabela 2.1: Implementacao da classe Shape em C++ e Java
Linguagem Codigo Exemplo
C++
class Shape{
public:Shape(int h, int w) { h = h; w = w; }void setH(int h) { h = h; }int getH() { return h; }void setW(int w) { w = w; }int getW() { return w; }
private:int h, w;
};
Java
class Shape{
public Shape(int h, int w) { h = h; w = w; }public void setH(int h) { h = h; }public int getH() { return h; }public void setW(int w) { w = w; }public int getW() { return w; }
private int h;private int w;
};
nao-estaticas da classe especificam ou descrevem que variaveis e metodos os objetos
irao ter. Isto permite entao estabelecer a distincao entre os dois conceitos: os objetos
sao criados e destruıdos ao executar o programa, podendo ter a mesma estrutura,
desde que sejam criados usando a mesma classe.
A tabela 2.1 ilustra a definicao de uma classe nas duas linguagens de programacao
orientadas a objetos mais utilizadas: C++ [20] e Java [21]. A classe exemplo repre-
senta um objeto do mundo concreto, nomeadamente, uma figura geometrica (Shape).
2.1.3 Princıpios Fundamentais
Os princıpios fundamentais de qualquer linguagem orientada a objetos sao: (i)
encapsulamento; (ii) heranca; (iii) polimorfismo; e (iv) abstracao.
Encapsulamento
O encapsulamento e uma caracteristica da POO que consiste em proteger as
variaveis dos objetos atraves dos seus metodos. Basicamente, definindo-se os atribu-
tos como privados e os metodos como publicos, garante-se assim que os valores dos
atributos apenas poderao ser modificados pelas regras que definem os metodos. Isto
8
Capıtulo 2. Estado da Arte
proporciona entao grandes vantagens ao desenvolvimento de software sobretudo em
dois aspectos:
• Modularidade: o codigo fonte de um objeto pode ser escrito e gerido inde-
pendetemente do codigo fonte de outros objetos. Alem disso, um objeto pode
ser facilmente passado no sistema.
• Ocultacao da Informacao: um objeto tem uma interface publica que os
outros objetos podem usar para comunicar com este. Assim, o objeto contem a
informacao privada e metodos que podem ser modificados a qualquer momento
sem que os outros objetos dependam disso.
O codigo da tabela 2.1 e um exemplo da utilizacao do encapsulamento em dife-
rentes linguagens de programacao. A classe Shape e constituıda por dois atributos:
w e h. O atributo w define o valor da largura (width) da figura, e o atributo h
define o valor da altura (height). Os atributos da classe sao definidos como privados
(private), para que nao seja possıvel a qualquer objeto externo aceder diretamente a
cada uma das variaveis. Daı que sejam definidos os metodos setH, getH, setW e getW,
para ler e escrever os valores de cada uma das variaveis. Os metodos sao definidos
como publicos (public), de forma a que sejam acessıveis a entidades externas a classe.
Heranca
Na POO, a heranca e uma metodologia de reutilizacao de software usado sempre
que uma classe herda a estrutura e o comportamento de outra classe. Por outras
palavras, atraves do mecanismo de subclasse, e possıvel herdar atributos e compor-
tamentos (metodos) comuns da classe base (tambem designada superclasse ou classe
pai), e acrescentar as especificidades a cada uma das subclasses. Portanto, pode-se
dizer que a heranca permite a customizacao e o refinamento incremental, isto e, cada
subclasse para alem dos metodos e atributos comuns pode ter metodos e atributos
especıficos.
Seguindo o exemplo da figura geometrica, o codigo da listagem 2.3 define a classe
Triangle, que implementa uma figura geometrica triangulo.
class Shape
{...
protected:
int h, w;
9
2.1. Programacao Orientada a Objetos
};
class Triangle : public Shape
{public:
Triangle(int h, int w) : Shape(h,w) {}double area() { return ( h∗ w)/2; }
};
Listagem 2.3: Definicao da classe Triangle como herdeira da classe Shape
Como a classe Triangle herda da classe Shape, todas as propriedades da classe
Shape sao herdadas pela classe Triangle. A classe Triangle define um novo metodo
(area), que implementa o calculo da area de um triangulo. Os atributos w e h sao
definidos pela classe base Shape, bem como os metodos publicos que permitem aceder
a esses atributos. No exemplo apresentado, a subclasse herda apenas de uma classe
base, no entanto e possıvel herdar de varias classes base (multipla heranca).
Polimorfismo
A palavra polimorfismo vem do grego e significa ”pode tomar varias formas”.
Assim, enquanto que a heranca se refere as classes (e respectiva hierarquia), o po-
limorfismo diz respeito aos metodos dos objetos. Existem essencialmente tres tipos
de polimorfismo: (i) o polimorfismo ad-hoc, (ii) o polimorfismo parametrico e (iii)
o polimorfismo de heranca ou dinamico. O polimorfismo ad-hoc permite entao ter
funcoes com mesmo nome, com funcionalidades semelhantes, em classes sem nenhuma
relacao entre elas. O polimorfismo parametrico representa a possibilidade de definir
varias funcoes do mesmo nome mas possuindo parametros diferentes (em numero
e/ou tipo). O polimorfismo dinamico permite redefinir um metodo (overwriting) em
classes que sao herdeiras de uma classe base, isto e, e possıvel fazer a especializacao
desse metodo.
A listagem 2.4 apresenta um exemplo de polimorfismo dinamico onde e definida
classe Triangle que reimplementa a funcao virtual definida na classe Shape.
class Shape
{public:
...
virtual double area() { }...
};
class Triangle : public Shape
10
Capıtulo 2. Estado da Arte
{public:
...
double area() { return ( h∗ w)/2; }};
Listagem 2.4: Exemplo de polimorfismo dinamico na classe Triangle
A classe Triangle ao herdar da classe Shape reimplementa o metodo area. O
metodo e reimplementado pela subclasse porque a classe base define o metodo como
virtual. Assim, se o objeto criado for do tipo Shape, e chamado o metodo area da
classe Shape. Caso contrario, se for criado um objeto do tipo Triangle, e chamado
o metodo area da classe Triangle.
Abstracao
A abstracao e mais uma das caracterısticas fundamentais da POO. Consiste numa
forma de generalizacao que permite gerir melhor a complexidade. Assim sendo, sig-
nifica que devemos considerar as qualidades e comportamentos independentemente
dos objetos a que pertencem, e daı isolarmos os atributos que um determinado grupo
de objetos tem em comum.
Em C++, uma classe e considerada abstrata desde que contenha pelo menos um
metodo virtual puro (pure virtual). Um metodo e considerado virtual puro quando
e um metodo virtual igualado a zero, ou seja, e um metodo que pode ser reescrito na
subclasse (com a mesma assinatura) igualado a zero. O codigo da listagem 2.5 e se-
melhante ao do exemplo anterior (listagem 2.4), no entanto a classe Shape e declarada
como abstrata, uma vez que o metodo area e declarado como virtual puro (listagem
2.5). Neste exemplo, pode-se dizer que a classe Shape isola as caracterısticas de um
triangulo, como de tantas outras figuras geometricas. A classe Shape e entao utili-
zada pela subclasse Triangle para herdar os seus atributos e metodos, no entanto os
ultimos devem ser implementados especificamente. Por outras palavras, a classe abs-
trata Shape serve como base para outras classes que queiram ser do mesmo grupo de
objetos (Triangle). Por isso, a classe Shape nao pode ser instanciada, daı que todos
os metodos declarados como abstratos deveram ser implementados pelas subclasses
(Triangle).
class Shape
{public:
...
11
2.2. Sistemas Operativos
virtual double area() = 0;//pure virtual
...
};
Listagem 2.5: Declaracao da classe Shape como abstrata
2.2 Sistemas Operativos
A seccao anterior terminou com a definicao do conceito de abstracao no ambito da
programacao orientada a objetos. Um sistema operativo e tambem uma abstracao,
pois fornece uma camada de software que abstrai o utilizador das especificidades
do hardware subjacente. Basicamente, este atua como um intermediario entre o
utilizador e o hardware do computador [22]. Assim sendo, o objectivo de um sistema
operativo passa por fornecer recursos e meios ao utilizador para que este execute
os programas de forma simplificada e eficiente. Basicamente, este e responsavel por
controlar cada ficheiro, cada dispositivo, cada endereco de memoria, e cada unidade
de tempo de processamento.
2.2.1 Arquitetura dos Sistemas Operativos
O kernel constitui o nucleo de um sistema operativo. Este representa a parte
mais importante, e indispensavel, do sistema. Basicamente, um sistema operativo
esta dividido em duas partes: o espaco do kernel (modo privilegiado) e o espaco
do utilizador (modo sem privilegios). Sem isso, a protecao entre os processos seria
impossıvel. Existem essencialmente dois modelos arquiteturais (conceitos de kernel)
que permitem caracterizar os sistemas operativos: (i) monolıticos; e (ii) microkernel.
A primeira arquitetura, monolıtica, executa cada um dos servicos basicos, como
o gestor de tarefas, gestor de memoria, gestor de interrupcoes, gestor de dispositivos,
sistema de ficheiros, etc., em modo kernel (figura 2.1). Este modelo encontra-se
organizado em camadas, construıdas a partir do gestor de tarefas (modo privilegiado)
ate as interfaces com o resto do sistema operativo - bibliotecas e por cima delas
as aplicacoes (modo sem privilegios). A inclusao de todos os servicos basicos no
espaco do kernel tem tres grandes inconvenientes: o tamanho do kernel, a falta de
extensibilidade e a dificuldade de manutencao. Sempre que se pretender corrigir um
bug ou a adicionar um novo recurso, e necessario recompilar o kernel todo. Esta
12
Capıtulo 2. Estado da Arte
operacao consome muito tempo e recursos, pois a compilacao pode levar varias horas
e consumir avultadas quantidades de memoria.
Figura 2.1: Modelos arquiteturais de um sistema operativo: monolıtico e microkernel
Para superar as limitacoes de extensibilidade e facilidade de manutencao, surgiu
o modelo baseado em microkernel. A estrategia (figura 2.1) consistiu na reducao dos
servicos implementados no espaco do kernel. Apenas servicos basicos de comunicacao
entre processos, escalonador e gestor de memoria virtual foram implementados em
modo privilegiado. Os outros servicos do sistema (sistema de ficheiros, device dri-
vers, pager) residem no espaco do utilizador em forma de processos normais (como
servidores de chamadas). Como os servidores deixam de ser executados no espaco
do kernel, entao e necessario implementar as chamadas ”trocas de contexto”, para
permitir aos processos de utilizador entrar e sair em modo privilegiado. Como a
comunicacao deixa de ser feita de forma direta, foi necessario introduzir um sistema
de mensagens que permite a comunicacao independente e favorece a extensibilidade.
Sistema Operativo monolıtico: GNU/Linux
O sistema operativo GNU/Linux [23] e uma implementacao open source do sis-
tema Unix, desenvolvido por milhares de pessoas. Este sistema representa uma im-
plementacao tıpica de um kernel monolıtico. Todas as funcoes do sistema, incluindo
gestor de tarefas, gestor de memoria, escalonador, funcionalidades I/O e drivers sao
13
2.2. Sistemas Operativos
implementados no espaco do kernel. O tamanho estimado do kernel monolıtico deste
sistema e de algumas dezenas de megabytes, o que resulta num processo de manu-
tencao bastante complexo e fatigante.
Sistema Operativo baseado em microkernel: QNX
O QNX (Quick Unix ) [24] e uma das implementacoes mais populares de um
sistema operativo baseado em microkernel desenvolvido para aplicacoes em tempo
real. Apenas os servicos mais basicos, como escalonador, temporizadores e signals
residem dentro do espaco do kernel, o que resulta num tamanho do kernel de 64k-byte.
Todos os outros componentes, por exemplo, pilhas de protocolos, drivers e sistema
de ficheiros, sao executado no espaco do utilizador. O kernel do QNX (designado
neutrino) e implementado em C e, portanto, pode ser facilmente adaptado para
diferentes plataformas.
2.2.2 Sistemas Operativos de Tempo-Real
Um sistema operativo de tempo-real (RTOS) e concebido para atender as necessi-
dades de aplicacoes de tempo-real. Estes sistemas sao caracterizados pelo tempo que
demoram a completar uma determinada tarefa. A finalidade neste tipo de sistemas
nao e o throughput, mas sim a garantia de cumprimento das deadlines. Num sistema
em que o incumprimento ocasional numa deadline seja aceitavel e nao cause qual-
quer dano permanente ao sistema, e designado como soft real-time, no entanto caso
seja necessario garantir determinismo e satisfacao de todos os deadlines, este e desig-
nado como hard real-time. Sistemas de audio e sistemas multimedia enquadram-se
na primeira designacao. Sistemas para controlo de processos industriais, de aviacao
e militares enquadram-se na segunda categoria.
Com efeito, nos sistemas operativos de tempo-real e mais valorizado a rapidez
e a previsibilidade da resposta do sistema, do que propriamente a quantidade de
tarefas realizadas num determinado perıodo de tempo. Portanto, a minimizacao
da latencia de interrupcao e da latencia de comutacao entre tarefas, sao aspectos
preponderantes na concepcao deste tipo de sistemas operativos. Daı que os algoritmos
de escalonamento dos RTOS sejam um pouco complexos. Alguns exemplos comuns
sao: rate-monotonic (RM), earliest deadline first (EDF) e highest priority first (HPF).
Os sistemas operativos de tempo-real e a sua aplicabilidade em sistemas embebidos
14
Capıtulo 2. Estado da Arte
estao intrinsecamente relacionados. A grande maioria destes sistemas operativos
sao desenvolvidos para o domınio embebido. Isto porque geralmente os RTOS sao
baseados em microkernel, o que vai de encontro a escassez de recursos dos sistemas
embebidos. Alem disso, os sistemas embebidos sao sistemas desenvolvidos com um
proposito especıfico, para realizar um conjunto restrito e dedicado de tarefas com
deadlines concretas [25]. Resumindo, existe uma correlacao estrita entre os sistemas
operativos de tempo-real e a sua aplicacao no domınio embebido. Alguns exemplos
de sistemas operativos de tempo-real para o domınio embebido sao o LynxOS [26], o
FreeRTOS [27] e QNX (referido anteriormente).
2.2.3 Sistemas Operativos Orientados a Objetos
Um sistema operativo orientado a objetos distingue-se dos sistemas operativos
tradicionais (implementados com linguagens de programacao imperativas) essencial-
mente por duas caracterısticas fundamentais.
Primeiro, porque o sistema operativo orientado a objetos deve ser desenhado e
implementado segundo o paradigma orientado a objetos. Isto significa que todo o
sistema operativo deve ser implementado atraves de um conjunto de objetos, que
representam uma instancia de cada uma das classes que o constituem. Alem disso,
os princıpios fundamentais da programacao orientada objetos (encapsulamento, he-
ranca, polimorfismo) devem ser utilizados para organizar as classes e as respectivas
inter-relacoes. Principalmente, a heranca e o polimorfismo parametrico devem ser
usados para facilitar a partilha e reutilizacao do codigo, assim como a configuracao
do sistema operativo.
Um sistema operativo disponibiliza um conjunto de interfaces/primitivas que per-
mitem as aplicacoes invocar funcoes do sistema (system calls) que implementam os
servicos pretendidos. Portanto, um sistema operativo orientado a objetos distingue-se
dos demais pois disponibiliza os seus servicos ou primitivas atraves de mensagens tro-
cadas entre objetos. Por outras palavras, num sistema operativo orientado a objetos
todas as entidades sao representadas por objetos que sao instancias das respectivas
classes. Alguns exemplos de classes a utilizar na implementacao do sistema opera-
tivo podem ser: Processor, para representar fisicamente o processador; Scheduler,
para representar o escalonador; Task, para representar uma tarefa de execucao; e
DeviceDriver, para representar um periferico.
15
2.2. Sistemas Operativos
Vantagens dos sistemas operativos orientados a objetos
Tal como a utilizacao do paradigma da orientacao a objetos tem enumeras vanta-
gens no desenho e concepcao de aplicacoes de software, tambem os sistemas operativos
beneficiam da utilizacao deste paradigma. Assim, desenhando e concebendo um sis-
tema operativo orientado a objetos e possıvel obter vantagens sobretudo ao nıvel
da portabilidade, reutilizacao de codigo, e gestao da complexidade e manutencao do
codigo [28].
Quando se pretende desenvolver um sistema operativo portavel, e essencial iso-
lar as especificidades de determinados dispositivos em modulos separados das partes
do sistema que sao independentes da arquitetura (architecture independent modu-
les). Para isso e necessario disponibilizar interfaces dos modulos dependentes da
arquitetura (architecture dependent modules) para permitir aos projetistas desenhar
e implementar o resto do sistema operativo sem a necessidade de saber os detalhes de
implementacao desses modulos. Desta forma, e possıvel reimplementar os modulos
especıficos sempre que se pretenda alterar a arquitetura, sem contudo modificar o
resto do sistema operativo. A programacao orientada a objetos permite implementar
a portabilidade atraves das classes abstratas. Assim, estas podem ser usadas para
definir as interfaces enquanto as classes concretas implementam as especificidades dos
modulos dependentes das arquiteturas. Isto significa que criando as classes abstratas
para definir as interfaces das entidades dependentes da arquitetura e possıvel desen-
volver os algoritmos e a estrutura do sistema operativo sem conhecimento detalhado
do hardware a ser usado.
Outra das vantagens obtidas com a utilizacao do paradigma da orientacao a obje-
tos nos sistemas operativos e a reutilizacao de codigo. Geralmente, dispositivos seme-
lhantes tem caracterısticas comuns e por isso implementacoes semelhantes. Atraves
do conceito de heranca e classe abstrata e possıvel desenhar um sistema operativo
reduzindo o codigo escrito e consequentemente aumentado a produtividade. Ou seja,
as caracterısticas comuns de um recurso podem ser abstraıdas numa nova classe e as
diferencas representadas em cada uma das classes derivadas ou subclasses. Exem-
plificando, imaginemos uma classe chamada DeviceDrivers e duas subclasses dessa
classe base designadas Timer1 e Timer2 que tem caracterısticas comuns, mas cujo
codigo e repetido em cada uma das classes. Assim definindo uma classe abstrata
designada por Timer que contem tudo que e comum e derivando duas subclasses
dessa classe abstrata, e possıvel partilhar o codigo comum dos dispositivos. Alem
16
Capıtulo 2. Estado da Arte
disso, ainda existe o benefıcio de que efetuando qualquer modificacao na superclasse
abstrata, por exemplo, a correcao de um bug ou uma melhoria no desempenho de
uma implementacao, sera automaticamente herdada pelas subclasses concretas.
Combinando a heranca e o polimorfismo e possıvel obter no sistema operativo
benefıcios ao nıvel da optimizacao da gestao de complexidade e da manutencao do
codigo. Para entender como e possıvel optimizar recorrendo a estes conceitos convem
exemplificar. Considere-se um sistema operativo multitarefa, onde existe a possi-
bilidade de executar varias tarefas concorrentemente. Sempre que acontece uma
mudanca de contexto, isto e, sempre que e alterada a tarefa em execucao e necessario
gravar estado da tarefa em execucao, e restaurar o estado da tarefa a executar. Con-
tudo, nas tarefas existentes num sistema operativo e possıvel encontrar tarefas do
sistema e tarefas de aplicacoes. As do sistema como estao associados ao sistema nao
precisam de gravar tanta informacao numa mudanca de contexto em comparacao
com as das aplicacoes, em que e necessario gravar informacao adicional da aplicacao.
Uma forma tradicional de implementar esta situacao consiste em definir uma flag
para especificar que tipo de tarefa esta representado. Conforme o estado da flag e
entao decidido o volume de informacao a ser guardada ou restaurada. A programacao
orientada a objetos possibilita uma solucao mais simples e mais otimizada. A classe
Task pode ser criada como abstrata e as classes SysTask e AppTask como subclasses
concretas. Os metodos da subclasse SysTask podem guardar e restaurar o estado de
uma tarefa do sistema, enquanto que os metodos da subclasse AppTask guardam a
informacao adicional das tarefas das aplicacoes.
Exemplos de sistemas operativos orientados a objetos
Para a realizacao desta dissertacao e essencial selecionar um sistema operativo
orientado a objetos para efetuar o porting e upgrade do mesmo para a famılia de
microcontroladores MCS-51. Assim sendo, torna-se essencial averiguar o trabalho
desenvolvido nesta area e avaliar as solucoes existentes, para que com base nas suas
caracterısticas e propriedades perceber qual o que mais se adequa aos recursos da
arquitetura a utilizar. De seguida sao apresentados e caracterizados os sistemas ope-
rativos orientados a objetos que o autor considera mais relevantes.
Choices
O Choices [28, 29] e um sistema operativo orientado a objetos desenvolvido pela
17
2.2. Sistemas Operativos
Universidade de Illinois em Urbana-Champaign no Estados Unidos da America. Este
foi o primeiro sistema operativo a utilizar o paradigma da orientacao a objetos, isto e,
os componentes do sistema estao encapsulados em classes e apresentam flexibilidade
para a gestao e extensibilidade. Desenvolvido em C++, o Choices foi desenhado
como uma framework que suporta a maioria das caracterısticas dos sistemas opera-
tivos de proposito geral: gestao de processos, memoria virtual, sistema de ficheiros,
dispositivos entrada/saıda (input/output - I/O) e suporte de rede.
Ao nıvel da gestao de processos, o Choices e um sistema multithread com suporte
de varios escalonadores (FIFO1, LIFO2, Round Robin3, etc.). Alem disso, oferece
mudanca de contexto otimizada, isto e, utilizando a heranca e subclasse implementa
mudanca de contexto entre processos de aplicacoes, processos do sistema ou entao
processos com interrupcao. Quanto aos mecanismos de sincronizacao de processos, o
Choices disponibiliza spin-locks 4 (lock) e busy-wait loops 5 (busy wait) para exclusao
mutua, e semaforos (semaphore) para exclusao mutua e sincronizacao. Relativamente
ao sistema de memoria virtual, este utiliza page tables independentes da maquina
alvo. No que diz respeito ao sistema de ficheiros, o Choices inclui suporte para
discos, particoes, ficheiros e diretorias conforme os sistemas standard V UNIX, BSD
4.2 UNIX ou MS-DOS. Quanto aos dispositivos I/O, o sistema tem suporte para
discos, RAM, dispositivos serie, buffers tty, entre outros. A nıvel de rede suporta
ethernet6, UDP/IP7 e TCP/IP8. Alem disso, para aqueles que queiram executar
aplicacoes UNIX, o Choices possui uma biblioteca de compatibilidade.
Resumindo, o sistema operativo Choices foi o primeiro sistema operativo orientado
a objetos desenvolvido para plataformas de proposito geral. Devido a sua arquitetura
monolıtica e extensa lista de propriedades e caracterısticas, aliado ao elevado con-
sumo de memoria (memory footprint), o autor considera que este sistema operativo
1FIFO (First-In-First-Out): algoritmo de escalonamento que determina a ordem de execucao dastarefas pela ordem de entrada no sistema
2LIFO (Last-In-First-Out): algoritmo de escalonamento que determina a ordem de execucao dastarefas pela ordem inversa de entrada no sistema
3Round Robin: algoritmo de escalonamento que atribui um tempo fixo a cada tarefa paraexecucao
4Spin-locks: mecanismo de sincronizacao de tarefas em que o lock da thread e feito em ciclo ateque o recurso esteja disponıvel
5Busy-wait loops: tecnica de programacao em que um processo verifica repetidamente se umadeterminada condicao e verdadeira
6Ethernet: padrao (IEEE 802.3) de transmissao de dados para redes locais (LAN)7UDP (User Datagram Protocol): protocolo da camada de transporte8TCP (Transmission Control Protocol): protocolo da camada de transporte
18
Capıtulo 2. Estado da Arte
nao se enquadra no domınio embebido, sobretudo na arquitetura MCS-51 onde os
recursos de memoria sao muito reduzidos.
Trion OS
O Trion Operating System [30] e um projeto de codigo aberto cuja intencao passa
por criar um sistema operativo moderno de 32/64-bits utilizando os conceitos e ideais
da orientacao a objetos.
Apesar do sistema operativo ainda estar em desenvolvimento, ja se encontra dis-
ponıvel para download a versao 0.2. Nesta versao, apesar de previa, e possıvel en-
contrar ja a implementacao em C++ de uma serie de funcionalidades dos sistemas
operativos: nucleo, gestao de dispositivos, gestao de memoria e gestao de tarefas.
Relativamente ao nucleo o sistema e baseado numa estrutura em microkernel com
suporte para threads, IPC (Inter Process Comunication), sincronizacao de tarefas
(mutex ), interrupcoes e excecoes. Por sua vez, o gestor de dispositivos e responsavel
por gerir os recursos do kernel, isto e, garante ao sistema o acesso a todos os recursos
de hardware, atraves da deteccao de todos os dispositivos usando tecnicas de plug
and play, informacao da BIOS e ficheiros de configuracao. Relativamente ao gestor
de memoria, este pode ser divido em tres partes: gestor de memoria fısica, gestor
de memoria virtual e gestor de memoria paginada. O gestor de memoria fısica e
responsavel por controlar o acesso a toda memoria fısica do sistema, assim como a
gestao da pilha. O gestor de memoria virtual mantem o controlo da memoria virtual
usada ou nao usada de cada espaco de endereco. O gestor de memoria paginada
sobretudo grava e carrega paginas de memoria em disco. Por fim, o gestor de tarefas
e responsavel pelo escalonamento das tarefas, isto e, e responsavel por carregar novas
tarefas e agendar as threads ja em execucao.
Resumidamente, apesar do Trion ser um sistema operativo baseado em microker-
nel, o autor considera que este tambem nao e uma solucao valida porque implementa
algumas funcionalidades (memoria paginada, memoria virtual, tecnicas plug and play,
) demasiado complexas para a plataforma alvo. Alem disso, este ainda nao atingiu
sequer uma versao estavel e final (apenas esta disponıvel a versao 0.2).
BOSS
O sistema operativo BOSS [31, 32] e um sistema operativo orientado a objetos de
tempo real desenvolvido pela FHG-FIRST, utilizado no satelite BIRD (Bi-Spectral
19
2.2. Sistemas Operativos
Infrared Detection) [33], e outras aplicacoes roboticas no espaco.
O BOSS foi desenhado com a finalidade de reduzir a complexidade do software de
forma a garantir a confiabilidade. O nucleo do sistema operativo foi desenvolvido em
C++, e foi efetuado o porting para varias plataformas, nomeadamente para PowerPC,
x86 e Atmel AVR. Como o objectivo deste sistema operativo passa pela aplicacao em
sistemas embebidos, este foi desenvolvido seguindo um modelo arquitetural baseado
em microkernel : tem escalonador, gestor de tarefas, mecanismo de sincronizacao de
tarefas (semaphore), gestor de temporizacao e mailbox. A figura 2.2 [32] apresenta o
diagrama de classes do nucleo do BOSS.
Figura 2.2: Diagrama de classes do core do sistema operativo BOSS
Para alem do nucleo principal, o grupo de Sistemas Embebidos (ESRG) da Univer-
sidade do Minho [34] foi responsavel por introduzir suporte para tolerancia a falhas.
Assim sendo, foi desenvolvida uma framework de middleware que torna possıvel a
implementacao de um conjunto de estrategias de tolerancia a falhas, e integrada no
sistema operativo BOSS utilizando programacao orientada a aspetos (AOP - seccao
2.3.5).
Em suma, a simplicidade do BOSS assim como a utilizacao do mesmo em siste-
mas embebidos de alta fiabilidade, fazem deste sistema operativo orientado a objetos
um potencial candidato para o objectivo da presente tese. Todavia, uma vez que o
codigo do sistema operativo e fechado e proprietario, nao foi possıvel utilizar o BOSS
no trabalho de dissertacao.
20
Capıtulo 2. Estado da Arte
ADEOS
ADEOS [35], acronimo de A Decent Embedded Operating System, e um sistema
operativo orientado a objetos baseado em microkernel, desenvolvido por Michael Barr.
Desenvolvido em C++, o sistema operativo com cerca de 1000 linhas de codigo
fonte foi desenhado para aplicacoes em sistemas embebidos com escassez de recursos.
Apesar de compacto, este tem as funcionalidades essenciais de um sistema operativo:
gestor de tarefas, escalonador, sincronizacao de tarefas e mudanca de contexto. Re-
lativamente ao gestor de tarefas, este encarrega-se de criar novas tarefas, adicionar
e remover tarefas da lista de tarefas, e ainda colocar as tarefas em execucao. O
sistema e multitask pois permite correr varias tarefas ”simultaneamente”. No que
diz respeito ao escalonador, este e responsavel por decidir que tarefa deve executar
em cada instante de tempo e gerir as interrupcoes. A estrategia de escalonamento e
preemptiva e baseada em prioridades, isto e, a cada tarefa e associada uma priori-
dade e a tarefa que deve ser executada e a de mais alta prioridade da lista de tarefas
prontas para execucao. Quanto a sincronizacao de tarefas, e implementado o meca-
nismo de mutex, disponibilizando os metodos take e release para garantir que num
determinado instante de tempo apenas uma tarefa acede a um mesmo recurso parti-
lhado. Finalmente, a mudanca de contexto permite guardar e restaurar o estado de
uma determinada tarefa sempre que e alterada a tarefa em execucao. A mudanca de
contexto neste sistema operativo e implementada em linguagem assembly especıfica
para a plataforma 80188.
Concluindo, devido ao seu modelo arquitetural e propensao para o domınio embe-
bido, assim como o facto do codigo fonte ser livre e com possibilidade de ser facilmente
melhorado e expandido, fazem deste sistema operativo a escolha do autor para o tra-
balho a desenvolver.
Este sistema operativo sera analisado e explicado ao detalhe na seccao 3.2.
2.3 Configurabilidade e Variabilidade no Software:
tecnicas de programacao
Devido a complexidade dos sistemas atuais, o desenvolvimento de software re-
quer cada vez mais um pensamento estruturado, assim como o uso de mecanismos
que permitam desenvolver bem, de modo a minimizar os recursos de hardware ne-
cessarios, e, simultaneamente, maximizar o desempenho do sistema. Alem disso, se
21
2.3. Configurabilidade e Variabilidade no Software: tecnicas de programacao
pensarmos no desenvolvimento de software para diferentes aplicacoes ou produtos,
para diferentes plataformas, com diferentes necessidades, e normal que a complexi-
dade de desenvolvimento cresca exponencialmente, e portanto seja necessario arranjar
mecanismos, tecnicas ou metodologias que permitam gerir toda essa variabilidade e
configurabilidade.
E neste sentido que o autor apresenta de seguida as principais tecnicas e me-
todologias de programacao utilizadas na gestao da variabilidade e configurabilidade
do software - (i) compilacao condicional, (ii) orientacao a objetos, (iii) orientacao a
componentes, (iv) orientacao a funcionalidades, (v) orientacao a aspetos, (vi) pro-
gramacao generativa -, procurando apontar os pontos fortes e os inconvenientes de
cada uma, de modo a perceber qual a mais adequada para aplicar no refactoring do
sistema operativo.
2.3.1 Compilacao Condicional
A compilacao condicional e uma estrategia de refactoring usada no ambiente de
programacao C/C++ para o desenvolvimento de software para diferentes platafor-
mas e com diferentes funcionalidades [36]. Do ponto de vista do programador, a
compilacao condicional, em conjunto com o pre-processador C/C++, e bastante facil
de aprender e aplicar no software configuravel. Diretivas de pre-processador C como
]define, ]ifdef, ]ifndef, ]if, ]else, etc., sao usadas para controlar e gerir zonas
ou trechos de codigo que devem ser incluıdos ou excluıdos conforme a condicao espe-
cificada. O exemplo da listagem 2.6 apresenta a utilizacao de compilacao condicional
num simples programa escrito em C. Caso o codigo seja compilado (por exemplo,
com o compilador da GNU - GCC [37]) com a macro DEBUG o programa mostra
no ecra a frase ” DEBUG DEFINED”, caso contrario sera mostrado ” DEBUG
not defined”.
#include <stdio.h>
int main()
{#ifdef DEBUG
printf(” DEBUG DEFINED\n”);
#else
printf(” DEBUG notdefined\n”);
#endif
return 0;
22
Capıtulo 2. Estado da Arte
}
Listagem 2.6: Exemplo da utilizacao de compilacao condicional
O sistema operativo Linux e o exemplo tıpico e magno da aplicacao da compilacao
condicional para gerir a variabilidade e configurabilidade do mesmo. Contudo, como
todo o codigo precisa de ser anotado com diretivas de pre-processamento, este acaba
por ficar confuso e ofuscado, tornando extremamente difıcil a manutencao e o upgrade.
Alem disso, uma vez que as anotacoes nao sao seguras, isto e, podem ser alteradas
com recurso a um simples editor de texto, faz com que esta tecnica seja propensa
a erros. A simples troca de uma letra numa diretiva de pre-processamento ou de
uma macro associada a esta, pode levar a uma inconsistencia de funcionamento. Isto
atinge uma proporcao colossal se pensarmos na quantidade de ficheiros e linhas de
codigo da maioria dos sistemas operativos UNIX atuais. Daı que esta tecnica seja
muitas vezes criticada na literatura, e tenha sido designada de ”]ifdef considered
harmful”[38] ou ”]ifdef-hell”[39].
2.3.2 Orientacao a Objetos
Outra tecnica ou metodologia que pode ser usada para gerir a variabilidade e
configurabilidade de um sistema, e a propria orientacao a objetos. Suportada pela
linguagem C++, este paradigma pode ser usado para implementar as diversas funci-
onalidades utilizando o polimorfismo dinamico. Basicamente, consiste em implemen-
tar o sistema utilizando o conceito de heranca e funcoes virtuais, gerindo as possıveis
configuracoes em runtime, o que torna o sistema dinamico e parametrizavel. No en-
tanto, este tipo de abordagem resulta numa sobrecarga excessiva de recursos, e numa
degradacao do desempenho do sistema.
2.3.3 Orientacao a Componentes
O conceito da orientacao a componentes surgiu com a visao do desenvolvimento
de software generalizado. Assim, esta metodologia pretende substituir os sistemas
de software monolıticos tradicionais, por componentes de software reutilizaveis e fra-
meworks de componentes em camadas. Desta forma, os componentes aumentam
as capacidades das frameworks, enquanto as frameworks fornecem um ambiente de
execucao para os componentes. No entanto, este termo ainda nao e totalmente aceite
na comunidade cientıfica, daı que nao exista nenhuma linguagem de programacao
23
2.3. Configurabilidade e Variabilidade no Software: tecnicas de programacao
desenvolvida segundo este paradigma, e apenas exista suporte de orientacao a com-
ponentes em linguagens orientadas a objetos como o C++, o Java, e, mais concreta-
mente, o Lagoona. [40]
Contudo, apesar das vantagens inerentes a esta metodologia, como por exemplo,
a reutilizacao e a especializacao, o desenvolvimento de um sistema com a abordagem
orientada a componentes proporciona um overhead de recursos, da mesma magni-
tude da abordagem dinamica e parametrizavel da orientacao a objetos. Alem disso,
ainda tem a desvantagem do compilador nao possibilitar a otimizacao ao nıvel do
componente (devido ao conceito de black-box 9), o que acrescenta funcionalidades
desnecessarias as aplicacoes.
2.3.4 Orientacao a Funcionalidades
A orientacao a funcionalidades, introduzida por Prehofer em 1997 [41], e uma es-
trategia de software utilizada para combater o problema do crescimento exponencial
de classes da orientacao a objetos. Em vez de uma estrutura de classes rıgida, esta
metodologia propoe o desenvolvimento de funcionalidades que descrevem a relacao
da classe base com as suas extensoes, sem utilizar a heranca. Isto e, as funcionalida-
des sao semelhantes a subclasses abstratas, contudo com a grande diferenca de que
as funcionalidades do nucleo da subclasse sao separados dos metodos de overwriting
da classe base. Desta forma, atraves da implementacao separada dos metodos de
overwriting e possıvel resolver as dependencias e interacoes entre as diversas funcio-
nalidades, isto e, algumas funcionalidades apresentam comportamentos diferentes na
presenca de outras.
Neste sentido, com a programacao orientada a funcionalidades e construıdo um
repositorio de funcionalidades, que substitui a estrutura rıgida e convencional de uma
hierarquia de classes tradicional (figura 2.3a [41]). Conforme se pode ver pela figura
2.3b [41], para construir um objeto, em vez do tradicional metodo de heranca, as
funcionalidades sao adicionadas umas apos as outras, com uma determinada ordem.
Para adicionar interacao e construir uma especie de hierarquia de classes persona-
lizada, sao utilizados os chamados lifters. No trabalho realizado por Prehofer [41]
e possıvel encontrar pormenores de implementacao, nomeadamente um exemplo em
Java da utilizacao desta tecnica para modelacao de pilhas (stacks) com diversas fun-
9Black-box: dispositivo, sistema, ou objeto que pode ser visto apenas em termos de entradas,saıdas e caracterısticas, sem nenhum conhecimento da implementacao e funcionamento interno
24
Capıtulo 2. Estado da Arte
cionalidades.
(a) Hierarquia de classes tıpica
(b) Composicao de objetos no modelo defuncionalidades
Figura 2.3: Orientacao a funcionalidades: hierarquia de classes e modelo defuncionalidades
Resumindo, comparando com a programacao orientada a objetos classica, a pro-
gramacao orientada a funcionalidades fornece maior modularidade e flexibilidade. A
reutilizacao e simplificada, uma vez que para cada funcionalidade, as caracterısticas
do nucleo sao separadas das interacoes. Daı que esta tecnica tenha sido utilizada em
diferentes aplicacoes de diferentes domınios [42], nomeadamente em simuladores de
incendio do exercito Norte Americano, em protocolos de rede de alta performance, e
ferramentas de verificacao de programas.
2.3.5 Orientacao a Aspetos
Em 1997, Kiczales et al. [43] foi o responsavel por criar o conceito de orientacao
a aspetos para lidar com um problema de programacao designado por cross-cutting.
25
2.3. Configurabilidade e Variabilidade no Software: tecnicas de programacao
De forma sucinta, sempre que duas propriedades a programar componham multiplos
elementos, e, simultaneamente, necessitem de coordenacao, entao diz-se que estas sao
transversais ou que se ”cross-cut”uma a outra. Foi na tentativa de simplificar este
problema, e melhorar o desenvolvimento e manutencao dos sistemas de software, que
Kiczales apresentou o conceito de aspetos. Um exemplo tıpico onde este problema de
cross-cutting acontece, e onde e possıvel aplicar a programacao orientada a aspetos,
e nos sistemas de autenticacao. Como a estrategia de loggin afeta necessariamente
inumeras partes do sistema, entao diz-se que o logging e transversal a todas as classes
e metodos de autenticacao.
Por outras palavras, a programacao orientada a aspetos (aspect oriented program-
ming - AOP) procura resolver o problema que geralmente uma unica dimensao da
decomposicao funcional nao e suficiente para implementar todos os aspetos de um
programa de forma modular [44]. Isto significa que o codigo que resulta de uma unica
decisao de design e amplamente disseminado por todo o sistema, ou seja, este nao
pode ser encapsulado numa unica funcao, classe ou metodo. Este tipo de codigo e
designado por aspect code. Um exemplo muito referenciado para ilustrar este efeito
consiste no codigo para efetuar sincronizacao em programas nao-sequenciais. Ape-
sar de no design ser possıvel especificar onde tem que ser introduzido o codigo de
sincronizacao, e o que este deve fazer, nao e possıvel encapsular de forma transpa-
rente. Portanto, a AOP ajuda neste dilema, pois o ambiente de desenvolvimento
desta abordagem permite implementar o cross-cutting em unidades modulares (aspe-
tos) e uma ferramenta - aspect weaver - insere os fragmentos de codigo, derivados do
codigo aspeto, onde estes sao precisos. Estes pontos de insercao sao designados por
joint points. A figura 2.4 [44] ilustra, simplificadamente, como e que o codigo aspeto
e embutido pelo aspect weaver no codigo dos componentes.
Para possibilitar o desenvolvimento e implementacao de codigo aspeto nas ferra-
mentas de desenvolvimento C++, e necessario utilizar uma extensao para a lingua-
gem, como AspectC++ [44], que atua como um pre-processador source-to-source.
2.3.6 Programacao Generativa
Todas as tecnicas e metodologias de customizacao mencionadas ate ao momento
apresentam algumas limitacoes ou inconvenientes, nomeadamente, propensao a er-
ros, consumo excessivo de recursos, debilidades no desempenho, e necessidade de
ferramentas adicionais demasiado precoces no estagio de desenvolvimento.
26
Capıtulo 2. Estado da Arte
Figura 2.4: Juncao do codigo aspeto no codigo dos componentes
Neste sentido, e numa tentativa de resolver a maioria dos problemas apontados an-
teriormente, uma outra forma de gerir a configurabilidade e variabilidade do software
consiste em usar tecnicas da programacao generativa, nomeadamente C++ template
metaprogramming (C++ TMP) [6, 7, 45, 46]. Esta metodologia acaba por ser classi-
ficada, em parte, como uma linguagem funcional, pois e processada pelo compilador
durante a fase de instanciacao dos templates, ou seja, e processada em tempo de
compilacao e nao em tempo de execucao. Desta forma, e possıvel efetuar geracao de
codigo, calculo de constantes, selecao de tipos, etc., e ao mesmo tempo gerar ape-
nas as funcionalidades pretendidas, garantindo assim codigo otimizado e ajustado as
necessidades da aplicacao. Por outras palavras, como o compilador atua momenta-
neamente como um interpretador, todo o processamento e realizado em tempo de
compilacao, resultando em codigo otimizado e especıfico para a configuracao reque-
rida, garantindo assim uma melhor gestao dos recursos e desempenho do sistema.
Contudo, apesar da potencialidade desta metodologia para a gestao de variabilidade
e customizacao de sistemas, esta apresenta um inconveniente. Caso algo de errado
aconteca durante a fase de compilacao, o compilador gera mensagens que podem ser
demasiado difıceis de interpretar, o que pode tornar crıtico e moroso o processo de
desenvolvimento. No entanto, existem ja mecanismos para minimizar o problema:
por um lado, (i) usar tecnicas que permitam a geracao de mensagens de erros custo-
27
2.4. Conclusoes
mizadas; por outro lado, (ii) utilizar compiladores com melhor suporte ao template
metaprogramming.
Esta tecnica sera revista e explicada com mais detalhe na seccao 3.3.
2.4 Conclusoes
Este capıtulo, para alem de fundamentar e familiarizar a leitura do documento
com os termos tecnicos e conceitos das diferentes tematicas adjacentes ao trabalho -
programacao orientada a objetos, sistemas operativos, e variabilidade e configurabi-
lidade no software -, permitiu tomar duas decisoes fundamentais para o sucesso da
dissertacao.
A primeira esta centrada na escolha do sistema operativo orientado a objetos.
Segundo a avaliacao do autor, o sistema operativo ADEOS (codigo aberto) e a escolha
mais acertada para os recursos da plataforma alvo. Por sua vez, a decisao de utilizar
TMP para gerir a variabilidade do sistema operativo de forma estatica, promete a
geracao de codigo otimizado, sem overhead e deterioracao do desempenho do sistema.
28
Capıtulo 3
Especificacao do Sistema
O capıtulo anterior permitiu expor os conceitos essenciais ao enquadramento da
tematica da dissertacao. Alem disso, foi discutido e definido o sistema operativo a
adotar, assim como a metodologia e abordagem para a gestao da variabilidade do
sistema. Neste capıtulo, por sua vez, serao explicados, numa aproximacao bottom-up,
cada uma das camadas e componentes que compoe a base do sistema a implementar.
Assim, primeiro sera explicada a arquitetura do microcontrolador 8051. Memoria,
perifericos, conjunto de instrucoes, sao alguns dos conceitos essenciais para a com-
preender a sua arquitetura. Depois disso, o sistema operativo ADEOS sera revisto,
com o objetivo de perceber detalhadamente o codigo implementado por Michael Barr
ao nıvel de escalonamento, tarefas e sincronizacao das mesmas. A tecnica de C++
template metaprogramming tambem sera novamente abordada, de modo a explicar
detalhes de implementacao, bem como exemplos de aplicacao. No fim do capıtulo
serao apresentadas algumas particularidades do compilador C++ da IAR para o 8051,
que o autor considera importantes para o sucesso do trabalho da presente dissertacao.
3.1 Microcontrolador 8051
Em 1981, a Intel Corporation [47] apresentou um microcontrolador designado por
8051. Seguindo uma arquitetura Harvard, isto e, memoria de codigo separada fisica-
mente da memoria de dados, este microcontrolador, na sua versao classica, possuıa
128-byte de RAM (memoria de dados), 4-kbyte de ROM (memoria de codigo), dois
temporizadores, uma porta serie, e quatro portas (8-bit) entrada/saıda de proposito
geral. O 8051 e um microcontrolador de 8-bit, o que significa que a unidade de proces-
29
3.1. Microcontrolador 8051
samento apenas consegue processar 8-bit de dados a cada instante de tempo. Dados
com tamanho superior a 8-bit tem que ser divididos e processados ao byte. A figura
3.1 apresenta o diagrama de blocos dessa versao do microcontrolador. [48]
Figura 3.1: Diagrama de blocos do microcontrolador 8051 classico
Este microcontrolador tornou-se ainda mais popular quando a Intel Corporation
permitiu que outros fabricantes o reproduzissem, na condicao de que estes garantissem
compatibilidade do codigo e instrucoes do 8051 original. Isto levou ao aparecimento
de muitas versoes do microcontrolador, com diferentes configuracoes de velocidades,
tipo e capacidade da memoria de codigo e dados, e perifericos. Os microcontrolado-
res atuais baseados no nucleo do 8051 tem varias caracterısticas importantes, como
interfaces de comunicacoes I2C (seccao 4.2.2), SPI (seccao 4.2.2), CAN1, converso-
res analogico-digital (ADC), conversores digital-analogico (DAC), geradores PWM
(seccao 4.2.2), e memoria de programa Flash auto-programavel. Inclusive, recente-
mente a Texas Instruments [49] lancou uma famılia de microcontroladores baseado
no nucleo do 8051, que possui on-chip um transceiver de radio frequencia para co-
municacoes sem fios sub-1GHz (p.e. CC1111 [50]) e 2.4GHz (p.e. CC2530 [51]).
1CAN (Controller Area Network): protocolo de comunicacao desenhado especialmente para aindustria automovel
30
Capıtulo 3. Especificacao do Sistema
3.1.1 Arquitetura de Memoria
O microcontrolador 8051 tem quatro memorias distintas: (i) memoria de dados
interna (RAM interna); (ii) registos de funcoes especiais (SFR - special function
registers, RAM interna); (iii) memoria de programa ou de codigo (Flash interna ou
ROM externa); (iv) e memoria de dados externa (RAM externa).
A versao original do 8051 possui 128-byte de memoria de dados interna que podem
ser enderecados direta ou indiretamente (seccao 3.1.5). Nos enderecos de 00h a 1Fh
desta memoria, estao localizados os bancos de registos. Este possui quatro bancos,
sendo, por defeito, selecionado o banco 0. No banco de registo selecionado estao
sempre mapeados oito registos de trabalho (R0 a R7) disponıveis ao programador.
Por sua vez, do endereco 20h a 2Fh estao disponıveis 128 localizacoes enderecaveis ao
bit. Isto significa que com uma unica instrucao pode-se executar operacoes booleanas
sobre os bits individuas desta area. As restantes posicoes de memoria, 30h a 7Fh,
estao livres, o que significa que estao disponıveis para armazenar dados e variaveis
definidas pelo programador. Existem outras versoes deste microcontrolador que dis-
ponibilizam mais 128-byte de dados de proposito geral. Como estes 128-byte estao
nos enderecos 80h a FFh, ou seja, os mesmos enderecos da area do SFR, o microcon-
trolador faz essa distincao atraves do enderecamento utilizado. Se a instrucao utilizar
enderecamento direto acede ao SFR. Caso a instrucao utilize enderecamento indireto
acede aos 128-byte de dados extra.
Todos os registos internos do 8051 estao mapeados nos 128-byte superiores da
memoria de dados interna. Assim sendo, nos enderecos 80h a FFh esta localizada
a area do SFR, que contem todos os registos do 8051, com excecao dos bancos de
registos de proposito geral R0 a R7. No 8051 original estao apenas definidos 21
enderecos, no entanto nos derivados mais recentes desta famılia a grande maioria dos
enderecos do SFR esta ja ocupada. Estes registos permitem o acesso e o controlo de
todos os perifericos internos do 8051.
A memoria de programa e destinada a armazenar o codigo e constantes da aplicacao.
Assim sendo, a memoria e apenas de leitura e tipicamente e implementada em
memoria ROM. Esta pode estar dentro ou fora do chip, com capacidade ate 64k-
byte, dependendo do modelo usado. Tal como ja foi referido, algumas variantes do
8051 possuem memoria flash em substituicao da memoria ROM.
A memoria externa de dados pode ser utilizada para armazenar dados e variaveis
do programador, ou simplesmente para implementar uma segunda area do SFR. Esta
31
3.1. Microcontrolador 8051
memoria pode ser acedida atraves de acesso indireto, utilizando uma instrucao espe-
cial, de mnemonica MOVX. Atualmente, algumas versoes do microcontrolador colocam
parte desta memoria externa dentro do chip. O espaco de memoria permitido pela
arquitetura e de 64k-byte, tendo tres barramentos disponibilizados para o efeito: bar-
ramento de enderecos de 16-bit ; barramento de dados de 8-bit ; barramento de controlo
de 3-bit.
A figura 3.2 resume e ilustra o mapa de memoria generico das diferentes variantes
do microcontrolador 8051.
Figura 3.2: Mapa de memoria do 8051 generico
3.1.2 Registos Basicos
Para alem dos quatro bancos de registos de uso geral ja mencionados anterior-
mente (R0 a R7), o microcontrolador dispoe de outros registos basicos de significativa
relevancia. O registo A (Accumulator) e o registo B, ambos de 8-bits, sao utilizados
para operacoes aritmeticas. O registo PSW (Program Status Word) contem os bits
de estado que refletem o estado atual do CPU, nomeadamente as flags de carry (C),
carry auxiliar (CA), selecao do banco de registos (RS0 e RS1), overflow (OV) e pari-
dade (P). O registo IE (Interrupt Enable) permite configurar e gerir as interrupcoes.
32
Capıtulo 3. Especificacao do Sistema
O registo SP (Stack Pointer) e utilizado como apontador para a pilha. O registo
DPTR (Data Pointer), de 16-bit, e muito util para enderecar memoria de dados ex-
terna e memoria de codigo. Finalmente o registo PC (Program Counter), tambem
de 16-bit, contem o endereco de memoria de programa da proxima instrucao a ser
executada.
3.1.3 Perifericos
O microcontrolador 8051, na sua versao classica, inclui essencialmente tres grupos
de perifericos: (i) portas entrada/saıda digital; (ii) contadores/temporizadores de 16-
bit ; e (iii) porta serie.
As quatro portas de entrada/saıda digital possuem quatro registos de 8-bit, mape-
ados no SFR, que permitem controla-las: P0, P1, P2 e P3. Cada um destes registos
possui oito latches2 e hardware de interface as saıdas (output drivers) e de leitura das
entradas (input buffers) que permitem implementar as funcionalidades necessarias a
uma porta de entrada/saıda digital. As oito linhas de cada uma destas portas I/O
podem ser tratadas individualmente, de modo a realizar a interface a dispositivos de
1-bit (LEDs3, ataque de MOSFETs4, etc.), ou entao como unidades para realizar a
interface paralela de 8-bit a outros dispositivos (display LCD, teclado, etc.).
Relativamente as unidades de contagem, contador/temporizador 0 e contador/-
temporizador 1, estes podem ser configurados para funcionar como temporizador
ou contador de eventos. Quando configurados como temporizadores, os registos de
contagem THx e TLx (onde x corresponde a 0 ou 1 dependendo do numero do tempo-
rizador), sao incrementados a cada ciclo maquina atraves de um sinal cuja frequencia
e 1/12 da frequencia do oscilador interno do CPU. Quando configurados como conta-
dores, os registos de contagem sao incrementados na transicao descendente do sinal
a entrada do pino P3.4 e P3.5.
A porta serie existente na famılia MCS-51 permite a transferencia no modo full-
duplex 5 e pode funcionar em varios modos e frequencias. A sua principal funcao
consiste na conversao paralelo-serie dos dados a serem transmitidos, e na conversao
serie-paralelo dos dados. O hardware da porta serie pode ser acedido atraves dos
2Latches: circuito sequencial biestavel assıncrono capaz de armazenar um bit de informacao3LED (Light-Emitting Diode): semicondutor (dıodo) emissor de luz4MOSFET (Metal Oxide Semiconductor Field Effect Transistor): transistor de efeito de campo5Full-duplex: permite comunicacao (transmissao e recepcao) em ambos os sentidos simultanea-
mente
33
3.1. Microcontrolador 8051
Tabela 3.1: Vetores de interrupcao na famılia MCS-51
Interrupcao Flag de Interrupcao Bit SFR Endereco SFRRESET RST 00hExterna 0 IE0 TCON.1 03hTimer 0 TF0 TCON.5 0BhExterna 1 IE1 TCON.3 13hTimer 1 TF1 TCON.7 1BhPorta serie RI ou TI SCON.0 ou SCON.1 23h
pinos TxD e RxD e apresenta um buffer que permite a rececao de um segundo byte,
antes da leitura do primeiro. Pode-se configurar a porta serie para transmissao com
frequencia fixa, derivado do oscilador interno, ou variavel, atraves da programacao
do temporizador 1 (nas novas variantes do 8051 o temporizador 2 tambem pode ser
utilizado para gerar a frequencia de transmissao). [52]
3.1.4 Interrupcoes
O 8051 original apresenta duas fontes de interrupcoes externa, duas interrupcoes
das unidades contadoras/temporizadoras, e uma interrupcao da porta serie. Existem
tres registos que fornecem o controlo total sobre todas as interrupcoes do 8051: (i)
registo IE, que controla a ativacao das interrupcoes; (ii) registo IP (Interrupt Pri-
ority), que permite configurar a prioridade individual das fontes de interrupcoes; e
(iii) o registo TCON (Timer Control), que permite configurar a forma de aciona-
mento das duas interrupcoes externas. A tabela 3.1 apresenta algumas informacoes
sobre as varias fontes de interrupcao, entre os quais os enderecos das ISR, as flags de
interrupcao associadas e as SFR onde se encontram as flags.
Na ocorrencia de uma interrupcao e da aceitacao da mesma pelo processador, o
programa principal e interrompido, desencadeando o seguinte conjunto de acoes: (i) e
concluıda a execucao da instrucao atualmente em execucao; (ii) o endereco de retorno
do PC e guardado na pilha; (iii) o estado atual da interrupcao e guardado interna-
mente; (iv) as interrupcoes sao desativadas; (v) o PC e carregado com o endereco do
vetor da ISR; e, finalmente, (vi) a ISR e executada, sendo posteriormente terminada
com a instrucao de RETI.
34
Capıtulo 3. Especificacao do Sistema
Tabela 3.2: Modos de enderecamento do 8051
Modo de enderecamento Codigo exemploEnderecamento imediato MOV A,#55H
Enderecamento direto MOV A,50H
Enderecamento direto por registo MOV A,R7
Enderecamento indireto por registo MOV A,@R0
Enderecamento implıcito PUSH ACC
Enderecamento indexado MOVC @A+DPTR
Enderecamento relativo SJMP loop0
Enderecamento absoluto ACALL loop1
Enderecamento longo LJMP loop2
3.1.5 Arquitetura do Conjunto de Instrucoes
A arquitetura do conjunto de instrucoes (ISA - Instruction Set Architecture) define
a interface entre o programador e o processador, isto e, fornece ao programador
toda a informacao necessaria para a interacao e comunicacao com o processador.
Por outras palavras, o ISA descreve o conjunto de instrucoes assembly suportadas
pelo processador, juntamente com as informacoes relativas aos registos acessıveis ao
programador, interacao com a memoria e gestao das interrupcoes.
Modos de Enderecamento
Independentemente do tipo de ISA, o processador, quando acede a um operando
para efetuar uma operacao de leitura ou escrita, deve especificar como e que os
enderecos de memoria e registos devem ser representados e interpretados. Uma ins-
trucao em linguagem assembly pode usar um de varios modos de enderecamento, a
partir do qual o CPU gera o endereco especificado para, posteriormente, aceder ao
subsistema de memoria. A tabela 3.2 apresenta os nove modos de enderecamento
disponıveis no 8051, assim como algumas instrucoes onde estes se aplicam.
O modo de enderecamento imediato utiliza constantes de 8 ou 16 bits como ope-
rando fonte. Esta constante e especificada diretamente na instrucao, ao inves de ser
especificada por registo ou por endereco de memoria.
No modo de enderecamento direto, a instrucao define o endereco do operando
como uma constante e o processador acede a localizacao de memoria. Este modo e
tipicamente utilizado para aceder a area de memoria do SFR.
O modo de enderecamento direto por registo e identico ao modo de enderecamento
35
3.1. Microcontrolador 8051
direto, exceptuando o facto de ser especificado um registo (isto e, um meio endereco)
e nunca um endereco de memoria, ou seja, e o registo que contem o operando.
No modo de enderecamento indireto, a instrucao especifica o endereco de uma
localizacao de memoria que contem o endereco do operando. Isto significa que re-
quer duas referencias a memoria para ler o operando. Apenas os registos R0, R1 e
DPTR podem ser utilizados. Este modo de enderecamento e muito utilizado para
implementar o conceito de apontadores, visto o 8051 nao implementar o modo de
enderecamento indirecto por memoria (apenas suporta enderecamento indirecto por
registo).
O modo de enderecamento implıcito nao especifica explicitamente um operando
pois tem-se sempre associado um determinado registo ou a pilha. Apesar de este
modo de enderecamento nao se aplicar diretamente no 8051, as instrucoes PUSH e POP
especificam implicitamente o topo da pilha como sendo o outro operando.
O modo de enderecamento por deslocamento, nomeadamente base por registo, e
especialmente util quando se necessita aceder a dados em memoria de codigo. Neste
modo especificam-se dois operandos, onde um deles contem um endereco de memoria
e o outro o deslocamento relativo ao endereco de memoria.
O modo de enderecamento relativo e utilizado por algumas instrucoes de salto
(por exemplo, SJMP) e salto condicional (por exemplo, JNZ). O operando fornecido
pela instrucao contem um offset que sera adicionado ao endereco da instrucao atual
por forma a gerar o endereco efetivo. Este destino efetivo deve-se encontrar entre
-128 e +127 bytes da instrucao atual dado o comprimento de 8-bit do offset.
O modo de enderecamento absoluto esta associado as instrucoes ACALL e AJMP.
Estas sao instrucoes de 2-byte, que especificam um endereco absoluto de 11-bit. Aten-
dendo ao fato dos 5-bit mais significativos do PC (16-bit) nao serem modificados, estas
instrucoes permitem apenas saltos dentro de paginas de 2k-byte, onde a memoria de
codigo se encontra logicamente dividida em 32 paginas.
O modo de enderecamento longo e utilizado atraves das instrucoes LCALL e LJMP.
Estas sao instrucoes de 3-byte em que os ultimos 2-byte especificam um endereco de
destino de 16-bit. Desta forma e possıvel percorrer os 64k-byte de memoria de codigo.
Tipos de Instrucoes
O microcontrolador 8051 disponibiliza 255 instrucoes assembly [53], agrupadas
em tres grupos funcionais: (i) instrucoes logicas e aritmeticas; (ii) instrucoes de
36
Capıtulo 3. Especificacao do Sistema
transferencia de dados; e (iii) instrucoes de controlo.
As instrucoes logicas e aritmeticas caracterizam-se por modificarem o valor do
operando destino. Instrucoes que efetuam a soma, subtracao, multiplicacao, divisao
ou deslocamento, sao classificadas como instrucoes aritmeticas, enquanto as que efe-
tuam o e-logico, ou-logico, xor-logico e complemento, sao designadas por instrucoes
logicas. As instrucoes do tipo set ou clear, podem ser classificadas como logicas ou
aritmeticas. As operacoes aritmeticas tem a particularidade de afectarem as flags do
processador, nomeadamente, carry, carry auxiliar, overflow, e paridade. [52]
Por sua vez, as instrucoes de transferencia de dados nao modificam os dados
originais, pois estes nao sao removidos da sua localizacao, apenas sao copiados para
uma nova localizacao. As instrucoes que efetuam a transferencia de dados podem ser
divididas em tres grandes tipos: (1) MOV destino, fonte; (2) PUSH fonte ou POP
fonte; e (3) XCH destino, fonte. [52]
Finalmente, as instrucoes de controlo alteram o fluxo de execucao do programa
e efetuam o fetch da proxima instrucao de uma localizacao de memoria diferente do
endereco consecutivo. Normalmente, alteram o valor do registo PC com um endereco
de uma instrucao diferente da instrucao consecutiva, e o proximo ciclo de fetch usa
este novo endereco colocado no registo PC para obter a proxima instrucao. Tal como
as instrucoes de transferencia de dados, tambem as instrucoes de controlo podem ser
divididas em tres tipos: (1) salto condicional; (2) salto incondicional; e (3) gestao de
subrotinas e interrupcoes. [52]
3.2 ADEOS: A Decent Embedded Operating Sys-
tem
Acronimo de ADEOS, A Decent Embedded Operating System e um sistema ope-
rativo orientado a objetos desenvolvido em C++ por Michael Barr. Foi desenvolvido
para aplicacoes embebidas, daı que o numero de linhas do codigo fonte seja infe-
rior a 1000. A maioria do codigo foi implementado independente da arquitetura e
seguindo o paradigma de abstracao da programacao orientada a objetos. Por isso,
a maioria das funcionalidades estao estruturadas em classes, sendo apenas escritas
em linguagem assembly tres rotinas especıficas ao processador 80188 [54]. Portanto,
para fazer o porting do sistema operativo para a plataforma 8051, apenas devem ser
re-implementadas estas tres rotinas.
37
3.2. ADEOS: A Decent Embedded Operating System
As funcionalidades implementadas pelo sistema operativo sao mınimas, mas as
essenciais para o correto funcionamento do mesmo: gestor de tarefas (Task), escalo-
nador (Sched) e sincronizacao de tarefas (Mutex ). A figura 3.3 apresenta o diagrama
de classes do ADEOS.
Figura 3.3: Diagrama de classes do ADEOS
3.2.1 Tarefas
Quando se fala de um sistema operativo multitarefa (multitasking) significa que o
sistema operativo possibilita a execucao de varias tarefas ”ao mesmo tempo”. No en-
tanto, em arquiteturas com um unico processador (single-processor) e nucleo (single-
core), como e o caso da famılia MCS-51, as tarefas nao sao executadas paralelamente,
mas sim de forma pseudo-paralela.
38
Capıtulo 3. Especificacao do Sistema
Desta forma, o sistema operativo e responsavel por decidir que tarefa executara
em instante de tempo. Portanto, durante a comutacao da tarefa este deve guar-
dar a informacao sobre o estado de cada tarefa, designado como contexto da tarefa
(context). O mecanismo de comutacao de contexto guarda o estado do processador
antes de outra tarefa assumir o controlo do mesmo, e de seguida restaura o estado
da tarefa selecionada para execucao. Esse estado consiste basicamente no apontador
para a proxima instrucao a ser executada, no endereco do topo da pilha da tarefa, e
o conteudo dos registos e flags do processador.
Neste sentido, para manter as tarefas e respetivos contextos organizados, o sistema
operativo retem a informacao de cada tarefa. Essa informacao e guardada sob a forma
de estruturas de dados designados por task control block (TCB). No ADEOS a classe
(Task) (listagem 3.1) e uma implementacao C++ do TCB.
class Task
{public:
Task (void (∗function)(), Priority, int stackSize);
TaskId id;
Priority priority;
TaskState state;
Context context;
int ∗ pStack;
Task ∗ pNext;
void (∗entryPoint)();
private:
static TaskId nextId;
};
Listagem 3.1: Declaracao da classe Task
Nesta classe importa explicar os atributos id, priority, state, context, pStack,
pNext. O id contem um numero inteiro (entre 0 e 255) que identifica a tarefa. O
priority identifica a prioridade da tarefa. O state informa sobre o estado da tarefa,
isto e, se a tarefa esta em execucao, se esta pronta a executar ou se esta em espera.
O context e a estrutura de dados que contem o estado do processador da ultima vez
que a tarefa teve acesso a execucao. O pStack e um apontador para o topo da pilha
da tarefa (isto e, stack frame da tarefa). Finalmente, o pNext e um apontador para a
proxima entrada TCB de uma das possıveis tarefas, estando a lista ligada ordenada
por prioridade.
39
3.2. ADEOS: A Decent Embedded Operating System
Estado das Tarefas
Conforme foi referido anteriormente apenas uma tarefa pode usar o processador
em cada instante de tempo. Portanto, essa tarefa e designada como a tarefa em
execucao (running), e e a unica que pode ter associado esse estado em cada instante
de tempo. Por sua vez, tarefas que estao prontas a executar mas que nao estao a
usar o processador encontram-se no estado ready, enquanto que tarefas que estao
bloqueados a espera de um evento externo sao tarefas no estado waiting. A figura
3.4 ilustra a relacao entre os tres estados que podem ser associados a uma tarefa.
Figura 3.4: Relacao dos estados das tarefas no ADEOS
Uma transicao entre o estado ready e running ocorre sempre que o escalonador
do sistema operativo seleciona uma nova tarefa para executar. Por outras palavras, a
tarefa que estava em execucao passa para o estado ready, e a nova tarefa passa para
execucao (running). Desde que uma tarefa esteja em execucao, esta apenas transita
desse estado para outro se for forcada pelo escalonador do sistema operativo ou entao
se tiver de esperar que um determinado evento externo ocorra. Nesse caso a tarefa
e colocada em estado waiting e uma nova tarefa e colocada em execucao. Logo que
esse evento externo ocorra a tarefa e entao colocada no estado ready. Resumindo,
embora possa haver varias tarefas no estado ready e waiting, apenas uma e so uma
tarefa pode estar no estado running em cada instante de tempo.
Mecanismos das Tarefas
Qualquer classe definida numa linguagem de programacao tem sempre associada
um conjunto de rotinas. Neste sentido, tambem a class Task tem o seu proprio
grupo de rotinas que permitem fazer a gestao das tarefas. No entanto, a interface
das tarefas no ADEOS e mais simples que na maioria dos sistemas operativos, pois
40
Capıtulo 3. Especificacao do Sistema
a unica funcionalidade disponıvel consiste em criar objetos dessa classe. Isto porque
o ADEOS distingue-se dos demais RTOS no mecanismo de controlo de execucao
das tarefas. Este e baseado numa maquina de estados com apenas tres estados,
ao contrario da maioria dos RTOS que apresentam um quarto estado (por exemplo
Dead) indicando a conclusao de execucao da tarefa. Este estado e que indica que
a tarefa deve ficar fora do processo de escalonamento. No entanto, no ADEOS nao
e obrigatorio que a rotina da tarefa seja implementada em corpo infinito. Como a
primeira execucao da tarefa e efetuada utilizando a funcao Run (listagem 3.2), se o
corpo da tarefa nao for implementada com um ciclo infinito, assim que a esta termine
o sistema operativo retorna a funcao run, que e responsavel por excluir a tarefa da
lista de tarefas prontas a executar, e colocar uma nova tarefa em execucao (ponto de
escalonamento).
void run(Task ∗ pTask)
{// Start the task, by executing the associated function.
pTask−>entryPoint();
enterCS();////// Critical Section Begin
// Remove this task from the scheduler’s data structures.
os.readyList.remove(pTask);
os.pRunningTask = NULL;
// Free the task’s stack space.
delete pTask−>pStack;
os.schedule(); // Scheduling Point
// This line will never be reached.
}
Listagem 3.2: Funcao de iniciacao das tarefas - run
Voltando de novo ao construtor da classe Task (listagem 3.3) este recebe tres
parametros de entrada. O primeiro parametro, function, e um apontador para
a funcao a ser executada pela nova tarefa. O segundo parametro, p, e um numero
unico entre 1 e 255 que representa a prioridade da nova tarefa relativamente as outras
tarefas no sistema. Estes numeros sao usados pelo escalonador quando seleciona uma
nova tarefa para execucao (255 representa a prioridade maxima). Por fim, o terceiro
parametro, stackSize, consiste no numero de bytes que devem ser reservados para
a pilha da tarefa.
Task::Task(void (∗function)(), Priority p, int stackSize)
{stackSize /= sizeof(int);// Convert bytes to words.
enterCS();////// Critical Section Begin
41
3.2. ADEOS: A Decent Embedded Operating System
// Initialize the task−specific data.
...
// Initialize the processor context.
contextInit(&context, run, this, pStack + stackSize);
// Insert the task into the ready list.
os.readyList.insert(this);
os.schedule();// Scheduling Point
exitCS();////// Critical Section End
};
Listagem 3.3: Construtor da classe Task
Relativamente ao corpo do construtor, pode-se verificar que a rotina e envolvida
por duas macros: enterCS e exitCS. O bloco de codigo entre estas duas macros e
designado por seccao critica. Uma seccao crıtica e um pedaco de programa que deve
ser executado de forma atomica, ou seja, o codigo deve ser executado sequencial-
mente e sem interrupcoes. Assim sendo, basicamente o que essas macros fazem e
habilitar e desabilitar as interrupcoes de forma a garantir a atomicidade do codigo a
ser executado.
Nesse bloco de codigo atomico importa referenciar especialmente a chamada de
tres funcoes: contextInit, os.readyList.insert e os.schedule. A rotina de
contextInit estabelece o contexto inicial de uma tarefa. A segunda rotina adi-
ciona a tarefa a lista de tarefas prontas a executar do sistema operativo. Esta lista e
um objeto do tipo TaskList, que consiste numa lista ligada de tarefas ordenada por
prioridade. Finalmente, a rotina os.schedule invoca o escalonador do ADEOS de
forma a decidir que tarefa deve ser colocada em execucao.
3.2.2 Escalonador
O escalonador e a fracao do sistema operativo que decide que tarefa sera escolhida
para execucao em cada instante de tempo. No entanto, o metodo de decisao ou, por
outras palavras, o algoritmo de escalonamento pode ser diferente. Nos sistemas ope-
rativos de tempo-real e necessario que a estrategia de escalonamento permita que as
tarefas mais importantes entrem em execucao com a menor latencia possıvel. Daı que
a maioria dos RTOS utilizem algoritmos de escalonamento baseado em prioridades
com preempcao.
Quando um algoritmo baseado em prioridades e implementado, e necessario im-
plementar tambem uma estrategia de ”desempate”. Por outras palavras, e necessario
estabelecer uma regra que permita definir que tarefa deve ser executada no caso de
42
Capıtulo 3. Especificacao do Sistema
existirem varias tarefas com a mesma prioridade. A estrategia mais usada nesses
casos e o algoritmo round robin. No caso do ADEOS, tal como ja foi referido, o esca-
lonador tambem e baseado em prioridades. No entanto, por questoes de simplicidade
a estrategia de ”desempate”implementada consiste no algoritmo FIFO.
Pontos de Escalonamento
Os pontos de escalonamento (scheduling points) podem ser designados como even-
tos do sistema operativo que desencadeiam a invocacao do escalonador.
Neste sentido, podemos desde ja estabelecer dois pontos de escalonamento: na
criacao de tarefas e na eliminacao das mesmas. Na ocorrencia destes eventos, o
metodo os.schedule e invocado de forma a selecionar a proxima tarefa a ser exe-
cutada. Se a tarefa atualmente em execucao ainda for a de maior prioridade, entao
esta continuara a usar o processador. Caso contrario, a tarefa de maior prioridade
da lista de tarefas (readyList) sera executada. A eliminacao da tarefa e feita pelo
sistema operativo utilizando o metodo run explicado anteriormente. Isto significa
que o ADEOS nao fornece nenhum servico para de forma explicita matar uma tarefa.
Um terceiro ponto de escalonamento acontece aquando do ”clock-tick”. O clock-
tick e um evento periodico desencadeado pelo trigger da interrupcao do temporizador.
No ADEOS, este e responsavel por acordar as tarefas que estao a espera que um
determinado temporizador por software termine a contagem. Na verdade, a utilizacao
de temporizadores por software e uma funcionalidade comum em sistemas operativos
embebidos. Com a ocorrencia do clock-tick o sistema operativo decrementa e verifica
os temporizadores por software ativos, e caso algum finalize a contagem todas as
tarefas colocadas em estado waiting a espera da temporizacao sao comutadas para o
estado de ready. De seguida, o escalonador e invocado e e verificada se alguma das
novas tarefas ”acordadas”tem associada uma prioridade mais elevada que a tarefa em
execucao antes da interrupcao temporal.
Ready list
O escalonador, para gerir as tarefas que estao prontas a ser executadas, usa uma
estrutura de dados chamada readyList, implementada com uma lista ligada ordenada
pela prioridade da tarefa. Portanto, na cabeca da lista esta sempre a tarefa pronta a
executar com a prioridade mais elevada, e na cauda da lista a tarefa com prioridade
mais baixa. A figura 3.5 ilustra a lista ligada explicada. A principal vantagem da lista
43
3.2. ADEOS: A Decent Embedded Operating System
ligada ordenada e a facilidade e rapidez com que o escalonador seleciona a proxima
tarefa a executar, pois e sempre a tarefa no topo da lista.
Figura 3.5: Ilustracao da lista de tarefas prontas a executar (readyList)
Tarefa Idle
Na eventualidade de nao haver tarefas prontas a executar (no estado ready)
quando o escalonador e chamado, e entao necessario garantir a existencia de uma
tarefa para ser executada. Essa tarefa e designada por idle task e e semelhante em
muitos dos RTOS. Consiste simplesmente num ciclo vazio infinito que mantem ocu-
pado o processador a saltar sempre para a mesma instrucao. No entanto, em sistemas
operativos mais avancados, esta tarefa e explorada na gestao do consumo, para evi-
tar desperdıcios de energia desnecessarios. Inclusive, isso acontece no trabalho da
presente dissertacao (seccao 4.2.3).
No ADEOS, a idle task tem associado um identificador e uma prioridade validos,
sendo zero em ambos os casos. Assim sendo, essa tarefa esta sempre presente na
readyList, e devido a sua baixa prioridade, e a tarefa da cauda da lista. Desta
forma, o escalonador executara esta tarefa apenas quando nao existirem mais tarefas
prontas para execucao.
Algoritmo Escalonamento
Uma vez que e usada uma lista ligada ordenada para gerir as tarefas prontas a
executar, o algoritmo de escalonamento torna-se bastante simples de implementar.
Em poucas palavras, este simplesmente verifica se a tarefa em execucao e a tarefa do
topo da lista sao a mesma. Se sao, entao nao e preciso escalonar. Caso contrario, e
necessario comutar de contexto e colocar em execucao a tarefa do topo da readyList.
A implementacao C++ do algoritmo de escalonamento do ADEOS pode ser visto na
listagem 3.4.
void Sched::schedule(void)
44
Capıtulo 3. Especificacao do Sistema
{...
// If there is a higher−priority ready task, switch to it.
if (pRunningTask != readyList.pTop)
{pOldTask = pRunningTask;
pNewTask = readyList.pTop;
pNewTask−>state = Running;
pRunningTask = pNewTask;
if (pOldTask == NULL)
{contextSwitch(NULL, &pNewTask−>context);
}else
{pOldTask−>state = Ready;
contextSwitch(&pOldTask−>context, &pNewTask−>context);
}}
}
Listagem 3.4: Metodo schedule da classe Sched
3.2.3 Sincronizacao de Tarefas
Num sistema operativo multitarefa, a maioria das tarefas executadas concorrente-
mente nao funcionam como entidades completamente independentes. Muitas vezes,
as varias tarefas trabalham cooperativamente no sentido de resolver problemas de
maior complexidade, daı que necessitem de comunicar entre elas para sincronizar as
suas atividades. Por exemplo, num sistema de controlo em que se faz amostragem
de dados e se aplica controlo PID (Proportional-Integral-Derivative), a tarefa res-
ponsavel pela aplicacao do algoritmo de controlo nao pode ser executada ate que a
amostra seja fornecida pelo ADC. Uma forma de resolver esse problema e usar um
mecanismo designado por mutex.
Assim sendo, os mutexes sao disponibilizados pelo sistema operativo para auxiliar
na sincronizacao de tarefas. No entanto, nao sao a unica forma de o fazer. Existem
outros mecanismos de sincronismo e comunicacao, como os semaphores, message
queues6 e shared memory7. Na verdade, o mutex e um tipo especial de semaphore
6Message queue: mecanismo de comunicacao entre tarefas que utiliza queues para enviar men-sagens entre os processos/threads
7Shared memory: mecanismo que utiliza porcoes reservadas de memoria para a troca de dadosentre tarefas
45
3.2. ADEOS: A Decent Embedded Operating System
designado binario ou mesmo mutuamente exclusivo. Em poucas palavras, um mu-
tex pode ser definido como um sinalizador multitarefa, isto e, havendo um recurso
partilhado por mais que uma tarefa, logo que uma das tarefas associe e sinalize esse
recurso com o mutex, entao mais nenhuma das tarefas pode aceder a esse recurso ate
que a tarefa desative o sinalizador.
No caso do ADEOS, para sincronizacao de tarefas o mecanismo disponıvel sao
os mutexes. Utilizando a classe Mutex e possıvel criar e destruı-los, e ainda ativar
ou desativa-los. Estas duas ultimas operacoes sao fornecidas pelos metodos take e
release. O processo de criacao de um novo mutex (listagem 3.5) e bastante simples:
todos os mutexes sao criados com estado available, e associados a uma lista ligada de
tarefas em estado waiting inicialmente vazia. No entanto, claro que uma vez criado
um mutex e necessario arranjar alguma forma de mudar o seu estado. Neste sentido,
foram implementadas no ADEOS os metodos take e realese.
Mutex::Mutex()
{enterCS();////// Critical Section Begin
state = Available;
waitingList.pTop = NULL;
exitCS();////// Critical Section End
}
Listagem 3.5: Construtor da classe Mutex
No que diz respeito ao metodo take este deve ser chamado por uma tarefa antes
de aceder a um recurso partilhado. Por outras palavras, este metodo garante a
tarefa exclusividade sobre o recurso. Se o mutex ja estiver associado a uma tarefa
(sinalizador binario ativado), a outra tarefa que o invocou sera suspensa ate que o
mutex seja libertado. E possıvel que varias tarefas estejam em espera do mesmo
mutex, todavia uma vez que a lista de espera e ordenada pela prioridade das tarefas,
assim que o mutex e libertado apenas a tarefa de maior prioridade e ”acordada”.
Relativamente ao metodo release, embora este possa ser invocado por qualquer
tarefa, e expectavel que apenas o invoque a tarefa que anteriormente tenha chamado
o metodo take. Isto significa que apenas faz sentido que a tarefa que sinalizou o
acesso a um recurso seja a mesma a libertar esse recurso. Um possıvel resultado de
libertar o mutex pode ser o de ”acordar”uma tarefa de maior prioridade. Nesse caso,
a tarefa que libertou o recurso deve ser forcada a ceder a execucao a tarefa de maior
prioridade que estava a espera desse mesmo recurso.
46
Capıtulo 3. Especificacao do Sistema
3.3 Template MetaProgramming
Descoberta a possibilidade de aplicacao em 1994 por Erwin Unruh, e aplicada
em 1998 por Krzysztof Czarnecki [6], o template metaprograming e uma tecnica que
utiliza templates para gerar e manipular o codigo de uma aplicacao em tempo de
compilacao (compile time) [45]. Assim, com a utilizacao desta tecnica e possıvel
expandir as capacidades do compilador, permitindo que atue momentaneamente como
um interpretador, de forma a produzir configuracoes estaticas e otimizadas.
A sintaxe e idiomas do TMP sao isotericos quando comparados com a pro-
gramacao convencional em C++. Por outras palavras, o codigo TMP (codigo estatico
C++) e consideravelmente diferente, e mais difıcil de perceber, que o codigo C++
standard (codigo dinamico C++). O codigo C++ dinamico e imperativo e orientado
a objetos, enquanto o codigo C++ estatico pode mesmo ser considerado funcional.
Como o TMP pode ser considerado uma linguagem de programacao funcional, este
nao possui variaveis, atribuicoes, e iteracoes. O codigo e baseado no conceito de
funcoes matematicas, onde cada passo do processo e separado em multiplos casos, e,
normalmente, utiliza as funcoes recursivamente.
3.3.1 Blocos Basicos do Template Metaprogramming
O codigo C++ TMP e composto essencialmente por quatro blocos basicos: (i)
valores; (ii) funcoes; (iii) saltos condicionais; e (iv) recursividade. [55]
Em TMP as ”variaveis”nao podem ser modificadas, uma vez que sao nomes pre-
definidos (typedefed) e constantes. Caso seja requerido um novo tipo ou valor, este
deve ser implementado dessa forma. O codigo da listagem 3.6 mostra como se faz
essa definicao.
// named value definition
struct NamedValue
{typedef int value;
} ;
// integer value definition
struct IntegerValue
{enum { value = 2 } ;
} ;
...
// using named and integer values
47
3.3. Template MetaProgramming
NamedValue::value var = 19;
int x = IntegerValue::value;
Listagem 3.6: Valores em template metaprogramming
As funcoes, ou mais precisamente metafuncoes, sao definidas em TMP utilizando
estruturas ou classes. Para passar meta-argumentos as metafuncoes sao utilizados
argumentos template. Para definir o valor ou tipo de retorno sao utilizados nomes
pre-definidos ou valores inteiros. A listagem 3.7 apresenta um exemplo de uma me-
tafuncao para a adicao de dois inteiros.
// function definition
template<int X, int Y>
struct Add
{// define the result type
typedef int result type;
// store the result value
enum { result = X + Y } ;
} ;
...
// call Add function
Add::result type var = Add<2,3>::result;
Listagem 3.7: Funcoes em template metaprogramming
Sempre que sejam necessarios utilizar construtores condicionais, sao usadas as
templates especializadas. Em compile time o compilador instancia a template que
melhor se identifica com os meta-argumentos especificados. O codigo da listagem 3.8
implementa a especializacao de templates para verificar se dois tipos sao identicos
(is same).
// generic implementation
template<typename T, typename U>
struct is same
{enum { result = 0 } ;
} ;
// partial specialized implementation
template<typename T>
struct is same<T, T>
{enum { result = 1 } ;
} ;
...
48
Capıtulo 3. Especificacao do Sistema
// check if the provided types are the same
bool value = is same<int, char>::result;
Listagem 3.8: Saltos condicionais em template metaprogramming
Tal como nas linguagens funcionais, tambem o codigo TMP utiliza recursividade
em vez da iteracao (ciclos). Para parar a recursao, e definida uma template especi-
alizada. A listagem 3.9 implementa uma metafuncao para o calculo da soma dos n
primeiros numeros inteiros.
// generic implementation
template <unsigned n>
struct sum
{enum { value = n + sum<n − 1>::value } ;
};// stop condition
template <>
struct sum<0>
{enum { value = 0 } ;
};
...
// call sum metafunction
int result = sum<4>::value;
Listagem 3.9: Recursividade em template metaprogramming
3.3.2 O Fatorial
Um exemplo basico para demonstrar as potencialidades do C++ template me-
taprogramming consiste no calculo do fatorial de um numero. A implementacao
standard (dinamica) para o calculo do fatorial, consiste na implementacao de uma
funcao iterativa ou recursiva, que e invocada durante a execucao da aplicacao. O
codigo da listagem 3.10 apresenta a implementacao recursiva em linguagem C++.
// dinamic factorial function
int factorial(int n)
{if(n == 0)
{return 1;
}return n ∗ factorial(n − 1);
}
49
3.3. Template MetaProgramming
...
// call factorial function
int value = factorial(3);
Listagem 3.10: Implementacao C++ recursiva do calculo do fatorial
Com esta implementacao, o resultado do fatorial do numero tres e conhecido em
tempo de execucao. No entanto, em tempo de compilacao, o numero para o qual se
pretende calcular o fatorial ja e conhecido. Assim sendo, utilizando C++ TMP, e
possıvel calcular em compile time o resultado da constante correspondente ao fatorial
de tres. O codigo apresentado na listagem 3.11 traduz a implementacao estatica em
TMP do calculo do fatorial desse numero.
// generic implementation
template<int n>
struct Factorial
{enum {value = Factorial<n−1>::value ∗ n};
};// specific implementation/stop condition
template<>
struct Factorial<0>
{enum {value = 1};
};
...
// call factorial metafunction
int value = Factorial<3>::value;
Listagem 3.11: Implementacao C++ TMP recursiva do calculo do fatorial
De forma sucinta, o primeiro trecho de codigo implementa a template generica do
calculo do fatorial, enquanto o segundo implementa a template especializada para a
condicao de paragem da recursao. A figura 3.6 ilustra o processo que o compilador
utiliza para resolver os templates no calculo do fatorial.
Para ter uma ideia do nıvel de optimizacao do codigo gerado com a utilizacao
do TMP, o autor decidiu avaliar, nesta fase preliminar, o desempenho e os recursos
de memoria de cada uma das aplicacoes (estatica e dinamica), implementadas no
microcontrolador 8051. O desempenho da aplicacao foi obtido utilizando o debugger
do ambiente de desenvolvimento, enquanto a memoria de codigo (sem otmizacoes do
compilador) foi conseguida com a utilizacao do FLIP da Atmel [56]. A tabela 3.3
apresenta os resultados obtidos.
50
Capıtulo 3. Especificacao do Sistema
Figura 3.6: Resolucao dos templates no calculo do fatorial
Tabela 3.3: Resultados de desempenho e memoria das aplicacoes Fatorial (C++dinamico) e Fatorial (TMP)
Aplicacao Tempo execucao (ciclos relogio) Memoria de codigo (bytes)Fatorial (C++ dinamico) 2578 300
Fatorial (TMP) 8 53
3.3.3 Lista Ligada Estatica
Um exemplo mais avancado que ilustra a aplicabilidade do TMP consiste na
implementacao estatica de uma lista ligada (linked list). Uma lista ligada e uma
estrutura de dados que consiste num grupo de nos, que globalmente representam uma
sequencia. De forma simplificada, cada no e composto por dados e uma referencia
(link) para o proximo nodo da sequencia.
A implementacao estatica da lista ligada e semelhante a lista ligada dinamica, no
entanto tudo e resolvido em compile time, reduzindo o tempo de execucao de uma
determinada tarefa, e aumentando portanto o desempenho do sistema. Por exemplo,
supondo que se pretende determinar o numero de ocorrencias da letra ’a’ num ficheiro
de texto, a ideia passa por implementar uma lista ligada estatica em que cada nodo da
lista e preenchida com um caracter do ficheiro de texto. Depois disso, basta percorrer
a lista ligada e incrementar um contador a cada ocorrencia do caracter ’a’. O codigo
da listagem 3.12 apresenta a implementacao de uma lista ligada estatica de inteiros.
const int endValue = ˜(˜0u >> 1); //lowest integer value
//Linked List Implementation
struct End
51
3.4. Ambiente de Desenvolvimento
{enum { head = endValue};typedef End Tail;
};template<int head , typename Tail = End>
struct Cons
{enum { head = head };typedef Tail Tail;
};
...
//Create a Linked List
Cons<1, Cons<2, Cons<3, End> > >;
Listagem 3.12: Implementacao C++ TMP de uma lista ligada estatica de inteiros
Com esta lista e possıvel implementar metafuncoes para determinar, por exemplo,
o tamanho (length), ou entao se esta vazia (is empty). A listagem 3.13 apresenta a
metafuncao Lenght. A metafuncao utiliza recursividade, implementando portanto a
template generica e a template especıfica para a condicao de paragem.
// LL Length Implementation
template<typename List>
struct Lenght
{enum { value = Lenght<typename List::Tail>::value + 1 };
};template<>
struct Lenght<End>
{enum { value = 0 };
};
Listagem 3.13: Metafuncao Length da lista ligada estatica
Resumindo, em tempo de compilacao e possıvel definir a lista ligada, assim como
utilizar as metafuncoes para determinar algumas das suas caracterısticas. Mais uma
vez, so para mostrar o poder de otimizacao das implementacoes com TMP, e apresen-
tado na tabela 3.4 uma pequena aplicacao em C++ com TMP e o respectivo codigo
assembly gerado pelo compilador para a arquitetura 8051.
3.4 Ambiente de Desenvolvimento
Nos sistemas informaticos de proposito geral, assim como nos sistemas embe-
bidos, para converter o codigo fonte de uma aplicacao, escrito numa linguagem de
52
Capıtulo 3. Especificacao do Sistema
Tabela 3.4: Codigo C++ TMP e codigo assembly da aplicacao estatica do fatorial
Codigo C++ com TMP Codigo assembly
void main (){
typedef Cons<1,Cons<2,Cons<3,End>>> list1;P0 = Lenght<list1>::value;P0 = IsEmpty<list1>::value;
}
main:CODE; Auto size: 0; P0 = list1.lenght (3)MOV 0x80,#0x3; P0 = list1.isEmpty (1)MOV 0x80,#0x1RET
programacao de alto nıvel, para codigo objeto ou mesmo codigo maquina, e necessario
recorrer sobretudo a tres ferramentas: (i) compilador, (ii) assembler e (iii) linker.
Os compiladores podem ser definidos como programas para computador que tra-
duzem uma linguagem para outra [57]. Por outras palavras, um compilador recebe
como entrada o codigo fonte de uma determinada aplicacao, e produz como saıda um
programa semanticamente equivalente, porem escrito noutra linguagem. Geralmente,
o codigo fonte e escrito numa linguagem de alto nıvel, como C ou C++, e e conver-
tido para codigo objeto especıfico ao processador. Por sua vez, um assembler traduz
o codigo em linguagem assembly para codigo objeto ou codigo maquina proprio do
processador [57] (3.7a). A linguagem assembly e uma forma simbolica da linguagem
maquina dos processadores e e particularmente facil de traduzir. As vezes, alguns
compiladores geram mesmo codigo assembly como saıda, e de seguida chamam o as-
sembler para concluir a traducao em codigo objeto (3.7b). Tanto os compiladores
como os assemblers muitas vezes dependem de um programa chamado linker. Esta
ferramenta e entao responsavel pela fusao de todo o codigo relocatable (codigo que
tem sımbolos por resolver, que o compilador nao reconhece porque compila os fi-
cheiros separadamente) presente nos ficheiros objetos, num unico ficheiro executavel
[57].
Tal como foi referido na seccao 3.2, o sistema operativo ADEOS foi desenhado se-
gundo o paradigma da orientacao a objetos, sendo portanto implementado com uma
linguagem de programacao orientada a objetos, concretamente C++. Alem disso,
determinadas rotinas crıticas do sistema operativo estao implementadas em lingua-
gem assembly. Neste sentido, para traduzir esse codigo fonte escrito em C++ para
codigo assembly ou codigo objecto, e necessario um compilador C++ para o proces-
sador alvo, ou seja, um compilador C++ para o 8051. Mais, e tambem necessario
um assembler e um linker para o 8051, de modo a converter o codigo assembly das
rotinas crıticas em codigo objeto, e fundir todo o codigo objecto e traduzir em codigo
53
3.4. Ambiente de Desenvolvimento
(a) (b)
Figura 3.7: Processo de compilacao de codigo fonte em codigo executavel/maquina
maquina especıfico ao processador, respectivamente.
Com efeito, o autor investigou quais os ambientes de desenvolvimento disponıveis
no mercado que integrassem as ferramentas especificadas anteriormente. As solucoes
encontradas foram unicamente duas: (i) Ceibo 8051 C++ Compiler + Keil uVision
IDE [58] e (ii) IAR Embedded Workbench for 8051 [59]. Relativamente a primeira,
consiste na integracao do compilador C++ da Ceibo com o software Keil, permitindo
assim a compilacao de codigo C++, C e assembly em codigo objeto. Esse codigo
objeto e depois traduzido em codigo maquina com o linker do Keil. O editor e o
debugger tambem fazem parte do IDE Keil. Portanto, esta solucao consiste numa
dualidade de esforcos por parte da Ceibo e da Keil Software. Por outro lado, a segunda
54
Capıtulo 3. Especificacao do Sistema
solucao consiste na utilizacao da Embedded Workbench para o microcontrolador 8051
desenvolvida pela IAR. Este ambiente de desenvolvimento integra conjuntamente nao
so compilador C/C++, assemblador e linker, assim como editor e debugger. Portanto,
todas as ferramentas sao desenvolvidas por uma unica entidade, a IAR SYSTEMS.
Analisando e comparando as solucoes, o autor decidiu optar pela IAR Embedded
Workbench pelas seguintes razoes:
• O compilador da Ceibo nao e actualizado desde 2002, e requer a versao do
Keil uVision2 (atualmente o software Keil encontra-se na versao uVision4 ). O
software da IAR foi atualizado em Fevereiro do presente ano;
• O Compilador C++ da Ceibo nao suporta templates, o que impossibilita a
aplicacao de C++ TMP para a gestao da variabilidade do SO, essencial para o
sucesso deste trabalho. O compilador da IAR na versao IAR Extended Embed-
ded C++ (EEC++) suporta;
3.4.1 Compilador IAR C/C++ para o 8051
O IAR C/C++ Compiler for 8051 e uma das ferramentas integradas na IAR Em-
bedded Workbench for 8051. Este programa permite a compilacao de duas linguagens
de programacao de alto-nıvel:
• C, a linguagem de programacao mais usada na industria dos sistemas embebi-
dos. E possıvel desenvolver aplicacoes que sigam os standards:
– Standard C : tambem conhecido como C99;
– C89: tambem conhecido como C94, C90, C89 e ANSI C.
• C++, a linguagem de programacao orientada a objetos, com bibliotecas com
recursos para a programacao modular. Qualquer um dos seguintes standards
pode ser usado:
– Embedded C++ (EC++): um subconjunto de funcionalidades da pro-
gramacao standard C++, definidas pelo consorcio Embedded C++ Te-
chnical committee;
– IAR Extended Embedded C++ (EEC++): corresponde ao EC++ com
funcionalidades adicionais, como suporte completo a templates, namespace
e Standard Template Library (STL).
55
3.4. Ambiente de Desenvolvimento
Memoria de Codigo
Conforme foi explicado da seccao 3.1, no 8051 classico o tamanho da memoria de
codigo e de 4k-byte com possibilidade de extensao ate 64k-byte. Por sua vez, existem
alguns 8051/8052 em que a memoria de codigo e expandida atraves do conceito de
bancos. E possıvel estender a memoria ate 16M-byte utilizando 256 bancos de 64k-
byte. O C8051F12X da Silabs [60] e o CC2430 da Texas Instruments [61] sao alguns
exemplos onde isso e feito por hardware. Mas, alem disso, existem ainda dispositivos
com memoria de codigo estendida, o que significa que podem ter ate 16M-byte de
memoria de codigo linear. Os dispositivos da Maxim DS80C390/DS80C400[62, 63]
sao exemplo disso.
O compilador da IAR suporta todas as configuracoes da memoria de codigo apre-
sentadas acima. Para especificar o nucleo e o modelo de memoria de codigo pretendido
este pode ser feito de duas formas:
• No IAR Embedded Workbench IDE, escolhendo Project->Options->General
Options->Target->CPUcore e Project->Options->General Options-
>Target->Codemodel;
• Atraves da linha de comandos com a opcao de compilacao –core = { plain
| p1 | extended1 | e1 | extended2 | e2 } e –code model = { near | n
| banked | b | banked ext2 | b2 | far | f } ;
Memoria de Dados
Relativamente ao modelo de dados, ou seja, ao modelo que especifica o tipo de
memoria usada por defeito para armazenar os dados, o compilador da IAR suporta
seis, dos quais importa destacar os seguintes:
• Tiny - O modelo de dados Tiny usa a memoria tiny por defeito, que esta
localizada nos primeiros 128-byte do espaco de memoria de dados interna. Esta
memoria pode ser acedida usando enderecamento directo. A vantagem e que
sao apenas necessarios 8-bit para o apontador.
• Small - O modelo de dados Small usa, por defeito, os primeiros 256-byte do
espaco de memoria de dados interna. Esta memoria pode ser acedida com
apontadores de 8-bits, tendo entao como vantagem ser apenas necessarios 8-bit
para o apontador.
56
Capıtulo 3. Especificacao do Sistema
• Large - O modelo de dados Large usa, por defeito, os primeiros 64k-kbyte do
espaco de memoria de dados externa. Esta memoria pode ser acedida apenas
com apontadores de 16-bit.
Para especificar o modelo de dados no compilador, e possıvel faze-lo de duas
formas:
• No IAR Embedded Workbench IDE, escolhendo Project -> Options -> Ge-
neralOptions -> Target -> Data model;
• Atraves da linha de comandos com a opcao de compilacao –data model = {tiny | t | small | s | large | l | far | f | far generic | fg | generic | g } ;
Funcoes
Para alem do tradicional suporte a funcoes standard C, este compilador fornece
um conjunto de extensoes - mecanismos que controlam as funcoes - que permitem
acrescentar e personalizar determinados aspetos inerentes as mesmas.
Desta forma, seja atraves das opcoes de compilacao, da utilizacao de keywords ou
diretivas pragma, ou mesmo com o uso de funcoes intrınsecas, e possıvel controlar onde
e que as funcoes sao armazenadas em memoria, usar primitivas para programar inter-
rupcoes e concorrencia, configurar e utilizar o sistema de bancos do microcontrolador
8051, otimizar funcoes, e aceder a recursos de hardware. Por exemplo, configurando
o modelo de codigo (near ou banked) e possıvel controlar o espaco de memoria para
o armazenamento das funcoes, nomeadamente o tamanho maximo e o conjunto de
enderecos dedicados.
Para definir uma funcao interrupcao, tem que ser usada a keyword interrupt
e a directiva ]pragma vector. Com a directiva especifica-se qual a interrupcao pre-
tendida do vector de interrupcoes existente no microcontrolador , e com a keyword
define-se que a funcao e uma rotina de servico a interrupcao. O codigo da listagem
3.14 mostra como definir uma funcao interrupcao para o overflow do temporizador 0
do 8051. Uma funcao do tipo interrupcao, obrigatoriamente, nao pode retornar nada
(tipo de retorno void), e nao pode especificar nenhum parametro.
#pragma vector = TF0 int /∗Symbol defined in I/O header file∗/interrupt void MyISR(void)
{/∗ISR code∗/
57
3.4. Ambiente de Desenvolvimento
}
Listagem 3.14: Funcao de interrupcao de overflow do timer 0
Interface Assembly
Quando se desenvolvem aplicacoes, sobretudo para sistemas embebidos, e nor-
mal existirem situacoes onde e necessario escrever partes de codigo em linguagem
assembly. Seja para obter timings precisos, seja para escrever sequencias especiais de
instrucoes, para obter melhorias a nıvel de performance, ou entao simplesmente por-
que os compiladores mesmo com recurso aos varios pragmas nao conseguem aceder a
todos os recursos de hardware. Conforme foi visto na seccao 3.2, o sistema operativo
ADEOS nao e excecao, e tanto a rotina de inicializacao de contexto (contextInit)
como de mudanca de contexto (contextSwitch) estao escritas em assembly. Desta
forma, para se poder fazer o porting do sistema operativo para a plataforma MCS-
51 e preciso perceber de que forma e que o compilador IAR para o 8051 suporta
o interface com o assembly. Assim sendo, o compilador IAR C/C++ para o 8051
disponibiliza tres formas de aceder aos recursos de baixo nıvel: (i) assembly inline;
(ii) modulos escritos inteiramente em assembly ; e (iii) funcoes intrınsecas.
Relativamente a primeira, e possıvel inserir codigo assembly diretamente em
funcoes escritas em C e C++, atraves da utilizacao da keyword asm. O codigo
apresentado na listagem 3.15 e um pequeno exemplo da utilizacao do inline assem-
bler para introduzir instrucoes assembly num pequeno programa em C. E possıvel
introduzir apenas uma instrucao, ou entao um bloco de instrucoes. E importante
nao esquecer que as instrucoes inline sao inseridas naquela localizacao no programa.
Portanto, e preciso ter presente as possıveis consequencias da indevida utilizacao da
mesma.
int main()
{int a = 2;
asm(”MOV SP,#0x80”); //change stack adress
int b = 0;
asm(
”PUSH 0 \n\t”
”MOV A,#10 \n\t”
”MOV 0,A \n\t”
”POP 0 \n\t”
);
return 0;
58
Capıtulo 3. Especificacao do Sistema
}
Listagem 3.15: Exemplo de utilizacao de inline assembler no compilador IAR
No que diz respeito a segunda possibilidade, o compilador permite chamar rotinas
escritas totalmente em assembly (em ficheiros assembler) a partir do C ou C++.
Como o trabalho do autor esta enquadrado na programacao orientada a objetos, sera
somente explicado o metodo para C++, podendo o leitor consultar mais detalhes
para linguagem C no Manual do compilador IAR C/C++ para o 8051 [64]. Desta
forma, em primeiro lugar e preciso declarar o nome, parametros e retorno da funcao
no ficheiro de codigo C++, conforme e apresentado na listagem 3.16.
extern ”C”
{int assembler routine(int val);
}
Listagem 3.16: Definicao de uma funcao implementada num ficheiro assembly externo
Depois, no ficheiro assembler, as rotinas devem ser declaradas como publicas e
deve ser especificado o codigo de cada uma delas. O ficheiro assembly deve ser
estruturado conforme apresentado na listagem 3.17. Os parametros das funcoes sao
passados atraves dos registos R0-R5 ou pela pilha, dependendo no numero e tipo
de parametros em questao. O retorno e somente feito atraves dos registos R0-R5.
Na subseccao seguinte sera analisado e explicado com mais detalhe a convencao de
chamada suportada pelo compilador.
NAME assembler example
RSEG DOVERLAY:DATA:NOROOT(0)
RSEG IOVERLAY:IDATA:NOROOT(0)
RSEG ISTACK:IDATA:NOROOT(0)
RSEG PSTACK:XDATA:NOROOT(0)
RSEG XSTACK:XDATA:NOROOT(0)
;Name of Assembler functions here
PUBLIC assembler routine
RSEG NEAR CODE:CODE:NOROOT(0)
;Declaration of functions here
assembler routine:
;Assembly Code
END
Listagem 3.17: Estrutura de um ficheiro assembly gerado pelo compilador IAR
Finalmente, a terceira e ultima forma de interface assembly consiste na utilizacao
de funcoes intrınsecas, isto e, sao funcoes pre-definidas disponibilizadas pelo compi-
lador que permitem aceder aos recursos de baixo nıvel sem ter de usar a linguagem
59
3.4. Ambiente de Desenvolvimento
Tabela 3.5: Convencoes de chamada de funcoes no compilador C/C++ 8051 da IAR
Convencaode cha-mada
Atributo da funcao Stackpointer
Descricao
Data over-lay
data overlay – Uma porcao da memoriainterna com acesso di-reto e usada para dadose parametros
Idata over-lay
idata overlay – Uma porcao da memoriainterna com acesso indi-reto e usada para dadose parametros
Idata reen-trant
idata reentrant SP A pilha da memoria in-terna com acesso indireto(idata) e usada para da-dos e parametros
Pdatareentrant
pdata reentrant PSP Uma pilha emulada na(pdata) e usada para da-dos e paramteros
Xdatareentrant
xdata reentrant XSP Uma pilha emulada na(xdata) e usada para da-dos e parametros
Extendedstackreentrant
ext stack reentrant ESP:SP Uma pilha estendidae usada para dados eparametros
assembly. A vantagem das funcoes intrınsecas relativamente ao uso de inline assem-
bler, e que o compilador tem toda a informacao necessaria para garantir uma correta
sequencia de interface, isto e, garante que tanto os registos como as variaveis sao
corretamente salvaguardados e restaurados.
Convencao de Chamada de Funcoes
Normalmente, as funcoes podem ser invocadas dentro de um programa por nome
ou por endereco. A convencao de chamada e o processo subjacente a essa invocacao
gerida automatica e transparentemente pelo compilador, delegando responsabilidades
a funcao chamada e ao chamante. Contudo, se uma funcao for escrita em linguagem
assembly, e necessario saber onde e como os parametros podem ser encontrados, bem
como quando retornar ao chamante e como retornar o resultado. O compilador IAR
60
Capıtulo 3. Especificacao do Sistema
Tabela 3.6: Registos utilizados nos parametros das funcoes
Parametro Passado nos registos1-bit B.0, B.1, B.2, B.3, B.4, B.5, B.6, B.7, VB.0, VB.1,
VB.2, VB.3, VB.4, VB.5, VB.6 ou VB.7
8-bit R1, R2, R3, R4 ou R5
16-bit R3:R2 ou R5:R4
32-bit R5:R4:R3:R2
C/C++ para o 8051 suporta seis diferentes convencoes de chamada, responsaveis por
controlar como e que a memoria e usada para os parametros e as variaveis locais. A
tabela 3.5 lista as diversas convencoes de chamada disponıveis.
Para especificar a convencao de chamada utilizado por defeito pelo compilador, e
possıvel faze-lo de duas formas:
• No IAR Embedded Workbench IDE, escolhendo Project -> Options ->
GeneralOptions -> Target -> Calling model ;
• Atraves da linha de comandos com a opcao de compilacao –calling convention
= { data overlay | do | idata overlay | io | idata reentrant | ir |pdata reentrant | pr | xdata reentrant | xr | ext stack reentrant | er
} ;
Apesar de apenas ser possıvel definir uma convencao de chamada para cada pro-
jeto em cada instante de tempo, o compilador possibilita definir a convencao de
chamada para funcoes individuais atraves da utilizacao dos atributos apresentados
na tabela 3.5.
Prologo da funcao
Os parametros podem ser passados para uma funcao usando tres metodos distin-
tos: em registos, na pilha, em janelas de memoria (overlay frame). E muito mais
eficiente usar os registos do que utilizar a pilha, daı que todas as convencoes de cha-
mada tenham sido desenhadas para maximizar o uso de registos. Apenas um numero
limitado de registos pode ser usado para a passagem de parametros. A tabela 3.6
apresenta os registos que podem ser utilizados para a passagem de parametros.
Quando nao estejam disponıveis mais registos, os restantes parametros sao pas-
sados pela pilha. Em alguns casos, nomeadamente em estruturas, unioes, classes ou
parametros de funcoes com tamanho variavel (ellipsis), estes sao sempre passados
61
3.4. Ambiente de Desenvolvimento
Tabela 3.7: Registos utilizados no retorno das funcoes
Valores de Retorno Passado nos registos1-bit Carry (C)
8-bit R1
16-bit R3:R2
32-bit R5:R4:R3:R2
pela pilha. Os parametros passados por pilha sao guardados na memoria na loca-
lizacao apontada pelo apontador da pilha especificada pela convencao da chamada. O
primeiro parametro e colocado diretamente na localizacao seguinte ao endereco apon-
tado pelo apontador da pilha. A pilha da convencao idata e extended stack cresce
para enderecos de memoria superiores, enquanto a pilha da convencao xdata e pdata
cresce para enderecos de memoria inferiores.
Epılogo da funcao
Uma funcao pode ou nao retornar um valor para o chamante. O retorno de
uma funcao, se existir, pode ser escalar (inteiro ou apontador), ponto-flutuante, ou
estrutura. Em todas as convencoes de chamada, o valor de retorno e passado em
registos ou no bit de carry. A tabela 3.7 apresenta os registos que podem ser utilizados
para o valor de retorno das funcoes.
Ambiente de Execucao - DLIB
O ambiente de execucao corresponde ao ambiente na qual a aplicacao e executada.
Este depende do hardware alvo, do ambiente de software, e do codigo da aplicacao, e
disponibiliza:
• Suporte as caracterısticas do hardware, nomeadamente acesso direto a camada
de baixo nıvel do processador (funcoes intrınsecas), registos dos perifericos e
interrupcoes (ficheiros cabecalho);
• Suporte a ambiente de execucao, isto e, codigo para a inicializacao e termino
do sistema;
• Suporte a operacoes de ponto-flutuante (fenv);
O compilador IAR C/C++ para o 8051 possibilita a execucao de aplicacoes em
dois ambientes de execucao: (i) CLIB; e (ii) DLIB. Enquanto o primeiro apenas
62
Capıtulo 3. Especificacao do Sistema
pode ser utilizado com linguagem C, o segundo suporta tanto C como C++. Assim
sendo, no contexto do trabalho a desenvolver, interessa apenas ao autor perceber o
ambiente de execucao DLIB. Portanto, este consiste numa biblioteca de execucao, que
contem funcoes definidas em C e C++, e ficheiros cabecalho que definem a interface
da biblioteca (headers). Essa biblioteca de execucao e disponibilizada tanto sob a
forma de bibliotecas pre-compiladas ($IAR directory/8051/lib/dlib) como ficheiros
de codigo fonte ($IAR directory/8051/src/lib/dlib). As bibliotecas pre-compiladas
sao configuradas para diferentes combinacoes das seguintes caracterısticas: ambiente
de execucao DLIB; variante do core; localizacao da pilha; modelo de codigo; modelo
de dados; convencao de chamada; localizacao das constantes; e numero, visibilidade,
tamanho e metodo de selecao do(s) data pointer(s).
O nome da biblioteca pre-compilada e gerado com a seguinte configuracao:
{lib} - {core} {stack} - {code mod} {data mod} {cc} {const loc} -
{]dptrs} {dptr vis} {dptr size} {dptr select}.r51.
Caso o compilador nao disponibilize uma biblioteca DLIB pre-compilada para as
combinacoes pretendidas, ou entao caso seja necessario alterar as rotinas de startup
ou exit, ou caso seja mesmo necessario adicionar suporte a alguma funcionalidade,
e possıvel criar uma biblioteca customizada. O processo e complexo, e toda a in-
formacao pode ser consultada no manual do compilador. Finalmente, para terminar,
importa referir que a biblioteca DLIB nao pode ser construıda para os modelos de
dados Tiny e Small, devido a necessidade de certos recursos inerentes a linguagem
C++.
63
Capıtulo 4
Implementacao do Sistema
Este capıtulo descreve o desenvolvimento dos componentes do sistema especifica-
dos no capıtulo anterior. Basicamente, o capıtulo anterior permitiu a familiarizacao
com a arquitetura do microcontrolador alvo, o sistema operativo orientado a objetos,
a tecnica de programacao para a gestao da variabilidade, bem como o compilador
C++ a utilizar. Este capıtulo descreve entao o trabalho concretamente desenvolvido.
Numa primeira fase e explicado o processo de porting do ADEOS, ou seja, e anali-
sado o codigo dependente do microcontrolador 80188, e apresentada a implementacao
para o 8051. De seguida, na fase de upgrade, sao explicadas as melhorias introduzidas
no ADEOS. Clock-tick intrınseco ao escalonador, device-drivers para os diversos pe-
rifericos, e escalonador power-aware. Finalmente, no final do capıtulo e apresentado
e explicado o refactoring do sistema operativo com template metaprogramming, de
modo a permitir e possibilitar a sua customizacao de acordo com as necessidades do
utilizador.
4.1 Porting do ADEOS para a Plataforma MCS-
51
Todo o codigo de software pode ser classificado, segundo o conceito de porta-
bilidade, de duas formas distintas: (i) codigo dependente do processador (CDP); e
(ii) codigo independente do processador (CIP). Portanto, ou estamos perante codigo
universal que corre em qualquer plataforma, como bytecode compilado em Java para
maquinas virtuais, ou entao codigo binario que corre apenas numa arquitetura dedi-
65
4.1. Porting do ADEOS para a Plataforma MCS-51
cada. Regra geral, quanto mais proxima a linguagem de programacao for do hardware,
menos portavel esta e. Assim sendo, o porting de software consiste basicamente em
reescrever o CDP de uma arquitetura original, para outra arquitetura alvo.
Neste sentido, para efetuar o porting do sistema operativo ADEOS da arquite-
tura 80188 para a arquitetura 8051, basta alterar, reescrever e adaptar o codigo BSP
(escrito em assembly especıfico ao 80188). Analisando a figura 4.1, que ilustra a ar-
quitetura de software do ADEOS e a sua relacao com o hardware, e possıvel constatar
que para efetuar o porting deste sistema operativo, basta portanto alterar e reescrever
o codigo dos ficheiros bsp.h e bsp.asm. Basicamente, esses ficheiros contem o codigo
responsavel por inicializar o contexto das tarefas, assim como realizar a mudanca de
contexto entre as mesmas.
Figura 4.1: Arquitetura de software do ADEOS
O autor decidiu, de modo a tornar a tarefa mais organizada e simplificada, dividir
a actividade de porting do SO em duas fases subsequentes: (i) analisar e compreender
o codigo assembly especıfico ao 80188; (ii) substituir o codigo assembly 80188 pelo
codigo assembly 8051, procurando manter a estrutura e estrategia (o mais fidedigno
quanto possıvel) de inicializacao e mudanca de contexto utilizada no processador
original;
66
Capıtulo 4. Implementacao do Sistema
4.1.1 Analise do Codigo Dependente do Processador
A primeira tarefa de porting do ADEOS para a arquitetura 8051 passa entao
por analisar e perceber o codigo, especıfico ao 80188, responsavel pela inicializacao
e mudanca de contexto das tarefas do SO. Esta tarefa torna-se essencial para o
autor, nao so para interiorizar e assimilar conceitos inerentes ao porting de software,
assim como perceber a estrategia e abordagem utilizada pelo projetista do sistema
operativo, para relacionar e perceber os contornos da mudanca para a arquitetura
8051. Alem disso, vai permitir que o mesmo adquira competencia e conhecimentos
relativamente a arquitetura e conjunto de instrucoes do 80188.
Ficheiro Cabecalho (bsp.h)
O ficheiro cabecalho bsp.h (listagem 4.1) e utilizado para definir a estrutura de
dados responsavel por guardar o estado da maquina de cada tarefa (contexto), es-
pecificar as macros que delimitam seccoes de codigo crıtico, e declarar o prototipo
das funcoes implementadas em assembly responsaveis pela inicializacao e mudanca
de contexto. Alem disso, e ainda gerido o problema de name mangling subjacente ao
interface entre as linguagens C e C++.
struct Context
{int IP;
int CS;
int Flags;
int SP;
int SS;
int SI;
int DS;
};#include ”task.h”
#define enterCS() asm { pushf; cli }#define exitCS() asm { popf }extern ”C”
{void contextInit(Context ∗, void (∗run)(Task ∗), Task ∗, int ∗ pStackTop);
void contextSwitch(Context ∗ pOldContext, Context ∗ pNewContext);
void idle();
};
Listagem 4.1: Ficheiro bsp.h para a arquitetura 80188
A estrutura Context permite guardar o estado atual do processador, isto e, o
valor dos registos essenciais do 80188 utilizados por uma determinada tarefa. Neste
67
4.1. Porting do ADEOS para a Plataforma MCS-51
caso, os registos necessarios a salvaguardar sao: o Instruction Pointer (IP); o Code
Segment (CS); as flags (Flags); o Stack Pointer (SP); o Stack Segment (SS); o Source
Index (SI); e o Data Segment (DS). As macros enterCS e exitCS permitem delimitar
seccoes de codigo consideradas crıticas. Por outras palavras, sempre que uma porcao
de codigo nao possa ser interrompido, entao este e considerado uma seccao de codigo
crıtica, nao podendo as interrupcoes estarem habilitadas. Daı que as macros sejam
implementadas em inline assembler, com recurso as instrucoes pushf, cli, e popf.
Segundo o conjunto de instrucoes do 80x86 [65] (compatıvel com o 80188), a instrucao
pushf guarda as flags na pilha, a instrucao cli desabilita a flag de interrupcao, e
a instrucao popf restaura as flags da pilha. Finalmente, a utilizacao da diretiva
extern "C", serve para informar o compilador que as funcoes foram escritas em
assembly seguem a convencao de nomes do C, que e diferente da convencao de nomes
do C++.
Ficheiro Assembly (bsp.asm)
O ficheiro assembly bsp.asm contem a implementacao das tres funcoes declaradas
no ficheiro cabecalho bsp.h, ou seja, implementa a funcao de inicializacao do contexto,
a funcao de mudanca de contexto, e a funcao idle.
Inicializacao de Contexto
Relativamente a funcao de inicializacao do contexto - contextInit -, esta apre-
senta uma estrategia de implementacao baseada em cinco etapas. O algoritmo 1
apresenta a estrategia utilizada.
Antes de explicar propriamente a implementacao da funcao, convem perceber
o prototipo da mesma (listagem 4.2). Assim, a funcao contextInit tem quatro
parametros de entrada. O primeiro e um apontador para a estrutura do contexto
da tarefa, o segundo um apontador para a rotina de startup da tarefa, o terceiro um
apontador para o objeto da tarefa, e o quarto e ultimo parametro um apontador para
o endereco do topo da pilha dedicada a tarefa. A funcao nao retorna nenhum valor
(void).
void contextInit(Context ∗, void (∗run)(Task ∗), Task ∗, int ∗ pStackTop);
Listagem 4.2: Prototipo da funcao contextInit
A primeira etapa da inicializacao do contexto representa a primeira parte do
prologo da funcao. Resume-se em gravar o base pointer na pilha do sistema, actualizar
68
Capıtulo 4. Implementacao do Sistema
Algoritmo 1 Inicializacao do contexto no 80188 - contextInit
contextInit(...):
aceder ao apontador da estrutura context da tarefa;
inicializar o endereco de retorno;
inicializar as flags do processador;
inicializar o segmento da stack ;
inicializar o segmento de dados;
esse base pointer depois de o ter gravado, e posteriormente, atraves da instrucao les,
obter o apontador para a estrutura do contexto passado como parametro pela pilha,
colocando 16-bit do endereco no destination index e os outros 16-bit no extra segment.
O codigo apresentado abaixo representa a implementacao, e a imagem 4.2 ilustra a
organizacao da pilha do sistema logo apos a chamada da funcao e execucao destas
instrucoes.
push bp
mov bp, sp
les di, dword ptr ss:[bp+6]; Get pContext.
Figura 4.2: Pilha do sistema apos entrada na funcao contextInit
69
4.1. Porting do ADEOS para a Plataforma MCS-51
Na segunda etapa (continuacao do prologo e inıcio do corpo da funcao) inicia-
se o preenchimento da estrutura de dados do contexto da tarefa, concretamente e
inicializado o endereco de retorno de startup da tarefa.
push ds
lds bx, dword ptr ss:[bp+10]; Get pFunc from the caller.
mov dx, ds
mov es:[di], bx
mov es:[di+2], dx
Basicamente, com a instrucao lds obtem-se o apontador da rotina de startup
passado como parametro (16-bit do endereco no registo base e os outros 16-bit no
data segment), e com as duas ultimas duas instrucoes mov preenche-se o primeiro (IP)
e segundo elemento (CS) da estrutura do contexto da tarefa (es:[di]) com o endereco
do apontador pFunc.
A terceira etapa e responsavel por inicializar as flags do processador na estrutura
do contexto da tarefa.
pushf
pop ax
or ax, 0000001000000000b; Enable interrupts by default.
mov es:[di+4], ax
Para isso, comeca por guardar as flags na pilha (pushf), restaura as flags para ao
acumulador (pop ax) e activa as interrupcoes por defeito. Por fim, com a instrucao
mov preenche o terceiro elemento(Flags - es:[di+4]) da estrutura com esse valor.
A quarta etapa e a etapa mais complexa da rotina de inicializacao de contexto,
pois inicializa-se a area de memoria reservada a pilha da tarefa.
les di, dword ptr ss:[bp+18]; Point to the task’s stack.
lds bx, dword ptr ss:[bp+14]; Get pTask from the caller.
mov dx, ds
mov es:[di−4], bx ; Place pTask onto the stack.
mov es:[di−2], dx
les di, dword ptr ss:[bp+6] ; Point to the task’s context.
lds bx, dword ptr ss:[bp+18]; Get pStack from the caller.
mov dx, ds
sub bx, 8 ; Save stack space for pTask.
mov es:[di+6], bx
mov es:[di+8], dx
Assim sendo, as duas primeiras instrucoes assembly permitem obter, respecti-
vamente, o apontador para o topo da pilha da tarefa (enderecos em es e di) e o
apontador para o objeto da tarefa (enderecos em ds e bx ). Depois disso, guarda-
se o endereco do objeto da tarefa (enderecos em bx e dx ) nos primeiros enderecos
da propria pilha reservada para a tarefa (es:[di-4] e es:[di-2]). As duas instrucoes
70
Capıtulo 4. Implementacao do Sistema
seguintes permitem aceder, respectivamente, ao apontador para a estrutura do con-
texto da tarefa (enderecos em es e di) e novamente o apontador para o topo da pilha
da tarefa (enderecos em ds e bx ). A instrucao sub subtrai 8 unidades ao endereco
do topo da pilha da tarefa, e as duas instrucoes seguintes preenchem o quarto (SP -
es:[di+6]) e quinto elemento (SP - es:[di+8]) da estrutura com os enderecos da pilha
da tarefa atualizada. A imagem 4.3 representa a organizacao da pilha da tarefa apos
a execucao desse bloco de codigo.
Figura 4.3: Pilha da tarefa apos inicializacao
A quinta e ultima etapa inicializa o segmento de dados, isto e, preenche na estru-
tura do contexto da tarefa os valores dos registos de segmentos si e ds. Alem disso,
e tambem responsavel por implementar o codigo epılogo da funcao (instrucoes pop e
ret).
pop ds
mov dx, ds
mov es:[di+10], si
mov es:[di+12], dx
pop bp
ret
Neste sentido, na sequencia da instrucao push ds da segunda etapa, que continha
o valor inicial desse registo, a instrucao pop ds restaura entao novamente o registo.
Desta forma, as instrucoes seguintes preenchem o sexto (SI - es:[di+10]) e setimo
elemento (DS - es:[di+12]) da estrutura com o valor original desses registos. A ins-
trucao pop bp restaura o base pointer com o valor que este tinha antes da chamada
da funcao. A etapa termina com a instrucao ret, responsavel por retornar a execucao
de codigo para a instrucao seguinte a chamada da rotina.
71
4.1. Porting do ADEOS para a Plataforma MCS-51
Mudanca de Contexto
Por sua vez, a rotina de mudanca de contexto - contextSwitch - basicamente
salvaguarda o estado da tarefa atual em execucao (a excecao da tarefa idle), e restaura
o estado da que se pretende executar a posteriori. Com efeito, esta rotina apresenta
uma estrategia de implementacao baseada em seis ou dez etapas, dependendo da
condicao da tarefa que se encontra atualmente em execucao. Caso seja a idle nao e
necessario guardar o estado da tarefa actual, resumindo-se portanto a rotina a seis
etapas. O algoritmo 2 ilustra a estrategia utilizada.
Algoritmo 2 Mudanca de contexto no 80188 - contextSwitch
contextSwitch(...):
aceder ao apontador do parametro old Context ;
if tarefa idle then;
guardar o endereco do final da rotina;
guardar as flags do processador;
guardar o segmento da stack ;
guardar o segmento de dados;
endif ;
aceder ao apontador do parametro new Context ;
restaurar o segmento de dados;
restaurar o segmento da stack ;
restaurar as flags do processador;
restaurar o endereco de retorno;
O prototipo da funcao contextSwitch (listagem 4.3) tem dois parametros de
entrada, sendo estes os apontadores para a estrutura do contexto da tarefa atualmente
em execucao (pOldContext), e para a tarefa que se pretende que entre em execucao
72
Capıtulo 4. Implementacao do Sistema
(pNewContext). A funcao tem retorno vazio (void).
void contextSwitch(Context ∗ pOldContext, Context ∗ pNewContext);
Listagem 4.3: Prototipo da funcao contextSwitch
A primeira etapa da inicializacao do contexto representa o prologo da funcao.
Este consiste em gravar o base pointer na pilha do sistema, atualizar esse base poin-
ter depois de o ter gravado, e posteriormente, atraves da instrucao les, aceder ao
apontador para a estrutura do contexto da tarefa atualmente em execucao (16-bit
no destination index e 16-bit no extra segment). Alem disso, com a utilizacao das
instrucoes mov copia-se esses enderecos para o registo data (dx ) e para o acumulador
(ax ), para avaliar a a condicao da tarefa idle.
push bp
mov bp, sp
les di, dword ptr ss:[bp+6]
mov dx, es
mov ax, di
O codigo assembly apresentado abaixo inicia o corpo da funcao. Consiste na
verificacao da tarefa atualmente em execucao. Com o or-logico verifica-se se ambos
os enderecos da estrutura da tarefa em execucao sao nulos, pois caso isso aconteca
significa que a tarefa atualmente em execucao e a idle, nao sendo portanto necessario
guardar o estado da mesma.
or ax, dx
jz fromIdle
Na segunda etapa inicia-se o processo de backup do estado da tarefa, isto e,
preenche-se a estrutura de dados do contexto da tarefa atualmente em execucao
com o estado atual da tarefa.
mov dx, cs
lea ax, switchComplete
mov es:[di], ax
mov es:[di+2], dx
Assim, com a primeira instrucao guarda-se o code segment, com a instrucao lea
obtem-se o endereco (offset) da label switchComplete (16-bit apenas), e com as duas
ultimas duas instrucoes mov preenche-se o primeiro (IP) e segundo elemento (CS) da
estrutura do contexto da tarefa (es:[di]) atual com o endereco do final da rotina.
A terceira etapa guarda as flags do processador na estrutura do contexto da
tarefa. Para isso, guarda as flags na pilha (pushf), e posteriormente preenche o
terceiro elemento (Flags - es:[di+4]) da estrutura com esse valor.
73
4.1. Porting do ADEOS para a Plataforma MCS-51
pushf
pop es:[di+4]
A quarta etapa consiste no backup do segmento da pilha da tarefa.
mov dx, ss
mov es:[di+6], sp
mov es:[di+8], dx
Com a execucao das duas ultimas instrucoes mov preenche-se o quarto (SP -
es:[di+6]) e quinto elemento (SS - es:[di+8]) da estrutura do contexto da tarefa com
o stack pointer e stack segment.
A quinta etapa e a ultima etapa destinada ao backup do estado da tarefa, nome-
adamente o segmento de dados da mesma.
mov dx, ds
mov es:[di+10], si
mov es:[di+12], dx
Com a execucao das duas ultimas instrucoes mov preenche-se o sexto (SI - es:[di+10])
e setimo elemento (DS - es:[di+12]) da estrutura do contexto da tarefa com os registos
source index e data segment.
A sexta etapa e a primeira destinada ao restauro do processador com a informacao
da tarefa que se pretende colocar em execucao.
fromIdle:
les di, dword ptr ss:[bp+10]
mov dx, es
mov ax, di
Com a instrucao les acede-se ao apontador para a estrutura do contexto da tarefa
que ira entrar em execucao, mais concretamente ao ultimo elemento (SI - es:[di+10])
da mesma. As duas instrucoes mov efetuam o backup do apontador para o registo
data (dx ) e para o acumulador (ax ).
A setima etapa restaura entao o registo source ındex do segmento de dados. Para
isso, utiliza a instrucao lds, colocando em si o sexto elemento (SI) da estrutura do
contexto da nova tarefa.
lds si, dword ptr [di+10]; si = pNewContext−>SI
A oitava etapa consiste no restauro do segmento da pilha.
mov dx, es:[di+8]
mov ax, es:[di+6]
pushf ; Save the current interrupt state.
pop cx
cli ; Disable interrupts.
74
Capıtulo 4. Implementacao do Sistema
mov ss, dx
mov sp, ax
push cx
popf ; Restore the saved interrupt state.
Com efeito, as duas primeiras instrucoes colocam no registo de dados e no acumu-
lador o quinto (SS) e quarto (SP) elementos da estrutura do contexto da nova tarefa,
respetivamente. As tres instrucoes seguintes permitem guardar o estado das flags e
desabilitar as interrupcoes. Depois disso, sao restaurados os registos sack segment e
stack pointer, com as instrucoes mov. A etapa termina com o restauro das flags.
Finalmente, o ultimo segmento de codigo representa o epılogo da funcao, res-
ponsavel por restaurar, de forma indireta, as flags do processador e do endereco de
retorno. Por outras palavras, com a instrucoes push coloca na pilha o primeiro (IP),
segundo (CS) e terceiro (Flags) elementos da estrutura do contexto da nova tarefa, e
com a instrucao iret retorna da rotina restaurando as flags simultaneamente.
push es:[di+4]
push es:[di+2]
push es:[di]
iret
4.1.2 Porting do Codigo Dependente do Processador
A segunda tarefa do processo de porting do ADEOS consiste entao na substituicao
do codigo dependente da arquitetura 80188 por codigo assembly 8051, procurando
manter, tanto quanto possıvel, a estrategia utilizada na versao original do sistema
operativo. Obviamente que uma vez que os processadores tem arquiteturas dispares,
sera necessario efetuar algumas modificacoes. Neste sentido, de seguida serao apre-
sentadas as alteracoes efetuadas pelo autor, assim como as estrategias utilizadas para
a inicializacao e mudanca de contexto.
Ficheiro Cabecalho (bsp.h)
No ficheiro cabecalho bsp.h a primeira alteracao surge desde logo com a alteracao
da estrutura do contexto (listagem 4.4). Como os microprocessadores tem arquite-
turas diferentes, e compreensıvel que tenham registos e estados diferentes. Assim
sendo, a estrutura apresentada abaixo implementa o contexto de uma tarefa do 8051.
De toda a estrutura importa referenciar as variaveis PC H e PC L, XSP H e XSP L,
que correspondem, respetivamente, ao endereco da memoria da proxima instrucao
75
4.1. Porting do ADEOS para a Plataforma MCS-51
de execucao da tarefa e ao endereco da pilha da tarefa (em memoria externa). Os
restantes sao registos intrınsecos ao estado do microprocessador.
struct context
{unsigned char PC H, PC L;
unsigned char A, B;
unsigned char IE;
unsigned char DPL, DPH;
unsigned char R0, R1, R2, R3, R4, R5, R6, R7;
unsigned char PSW;
unsigned char SP;
unsigned char XSP H, XSP L;
};
Listagem 4.4: Definicao da estrutura do estado da maquina (8051) de cada tarefa
Tambem as macros para delimitacao de seccoes crıticas foram ligeiramente alte-
radas (listagem 4.5). Apesar da logica ser a mesma, nao existe instrucoes dedicadas
para gravar as flags e desabilitar as interrupcoes, pelo que isso tem que ser feito com
os respetivos registos. Portanto, sempre que se entra numa secacao crıtica o registo
IE (0xA8) e colocado na pilha e e desabilitado o bit geral das interrupcoes. Nao e
utilizada a instrucao CLR EA, pois o compilador nao reconhece a flag. Por sua vez,
quando sai da seccao crıtica, e feito o restauro atraves da pilha
#define enterCS()\{\
asm(\”PUSH 0xA8 \n” \”ANL 0xA8, #0x7F \n” \
);\}#define exitCS()\{\
asm(\”POP 0xA8 \n” \
);\}
Listagem 4.5: Macros para delimitacao de uma seccao crıtica
No prototipo das funcoes nao existe nenhuma alteracao na declaracao, apenas
e utilizada uma macro para redefinir a funcao de mudanca de contexto (listagem
4.6). Isto e necessario devido a convencao da chamada de funcoes do compilador
da IAR. Como na chamada de uma funcao os parametros sao colocados nos registos
do microprocessador (por questoes de otimizacao), e entao necessario guardar esses
registos na pilha antes de invocar a funcao.
76
Capıtulo 4. Implementacao do Sistema
#define ContextSwitch(old context, new context)\{ \
asm( \”PUSH A \n” \”PUSH 1 \n” \”PUSH 2 \n” \”PUSH 3 \n” \”PUSH 4 \n” \”PUSH 5 \n” \”PUSH DPL \n” \”PUSH DPH \n” \); \
contextSwitch(&old context, &new context); \} \
Listagem 4.6: Macro para comutacao de contexto (ContextSwitch)
Ficheiro Assembly (bsp.asm)
No ficheiro assembly bsp.asm e onde se verificam as principais alteracoes. Ape-
sar deste continuar a ter a implementacao das tres funcoes declaradas no ficheiro
cabecalho, existem modificacoes consideraveis em duas delas. De seguida, serao apre-
sentadas e explicadas as novas metodologias para inicializacao e mudanca de contexto,
assim como as alteracoes na implementacao das mesmas.
Inicializacao do contexto
Relativamente a funcao de inicializacao do contexto, esta apresenta agora uma
estrategia de implementacao baseada em oito etapas. Apesar de seguir a mesma
abordagem que a anterior, e mais longa pois esta mais detalhada a nıvel dos registos
do estado do processador. O algoritmo 3 ilustra a estrategia utilizada.
De forma a simplificar a explicacao da implementacao da funcao, convem clari-
ficar, desde ja, onde e que os parametros de entrada sao colocados na chamada da
funcao. Assim sendo, conforme foi apresentado na subseccao 3.4.1, o primeiro ar-
gumento, endereco para uma estrutura localizada em memoria externa (64k-byte), e
um endereco de 16-bit, pelo que e colocado nos registos R2 e R3 do banco 0. Por
sua vez, o segundo argumento e um apontador para uma localizacao da memoria de
codigo (216 = 64k-byte), daı que seja um endereco de 16-bit colocado nos registos R4
e R5. O terceiro argumento e um apontador para o objeto tarefa, colocado na pilha
externa (XSP), devido a inexistencia de mais registos para variaveis de 16-bit.
O codigo apresentado abaixo implementa a primeira e segunda etapa do processo
77
4.1. Porting do ADEOS para a Plataforma MCS-51
Algoritmo 3 Inicializacao do contexto no 8051 - contextInit
contextInit(...):
aceder ao apontador da estrutura context da tarefa;
inicializar o apontador para a rotina de startup;
inicializar o registo A e B;
inicializar o registo de interrupoes;
inicializar o registo DPTR;
inicializar os registo R0-R7;
inicializar as flags do processador;
inicializar o segmento da stack ;
de inicializacao do contexto da tarefa.
;Get the pointer to context
MOV DPH, 3; Load pContext H into DPH
MOV DPL, 2; Load pContext L into DPL
;Initialize the pointer to startup routine
MOV A, 5; A = pFunc H
MOVX @DPTR, A; pContext−>PC H = pFunc H
INC DPTR; point to pContext−>PC L
MOV A, 4; A = pFunc L
MOVX @DPTR, A; pContext−>PC L = pFunc L
INC DPTR; point to pContext−>A
As duas primeiras instrucoes (prologo da funcao) permitem aceder ao apontador
do contexto da tarefa. O restante codigo (inıcio do corpo da funcao) inicializa o
apontador para a rotina de startup da tarefa. Com as instrucoes MOVX inicializa-se
o primeiro (PC H) e segundo (PC L) elemento da estrutura do contexto da tarefa,
atraves de enderecamento indirecto para memoria externa.
A terceira etapa consiste na inicializacao dos registos A e B. Seguindo a mesma
linha da etapa anterior, com a utilizacao das instrucoes MOVX inicializa-se o terceiro
(A) e quarto (B) elemento da estrutura do contexto da tarefa.
;Initialize A and B
MOV A, #0; A = 0;
78
Capıtulo 4. Implementacao do Sistema
MOVX @DPTR, A; pContext−>A = A (0)
INC DPTR; point to pContext−>B
...
A inicializacao do estado das interrupcoes acontece na quarta etapa. O estado
actual das interrupcoes e salvaguardado na pilha (PUSH), as interrupcoes gerais e a do
temporizador 0 sao ativadas por defeito (valor 0x82), o quinto elemento da estrutura
(IE) e inicializado com esse valor, e o estado anterior das interrupcoes e restaurado
(POP). A activacao da interrupcao do timer 0 esta ligada a metodologia utilizada na
versao original para tornar as tarefas periodicas.
; Initialize interrupts
PUSH 0xA8; Save IE in stack
ORL 0xA8,#0x82; Enable Interrupts (Timer0 for clock tick) by default
MOV A, 0xA8; A = IE;
MOVX @DPTR, A; pContext−>IE = 0x82
POP 0xA8; Restore IE
As proximas tres etapas permitem inicializar o data pointer, os registos R0 a R7 e
as flags do processador. A metodologia e exactamente a mesma das etapas anteriores,
que consiste em aceder aos elementos seis a dezasseis (DPL a PSW) da estrutura da
tarefa, e inicializar a nulo. No registo PSW isso significa limpar todas as flags, como
por exemplo, a flag de carry (C) e paridade (P).
; Initialize DPTR
INC DPTR; point to pContext−>DPL
MOVX @DPTR, A; pContext−>DPL = 0
INC DPTR; point to pContext−>DPH
MOVX @DPTR, A; pContext−>DPH = 0
;Initialize Registers.
INC DPTR; point to pContext−>R0
MOV A, #0;
MOVX @DPTR, A; pContext−>R0 = 0
...
;Initialize Processor Flags
PUSH PSW; Save PSW in stack
ANL PSW,#0x00; CLEAN ALL FLAGS
MOV A, PSW; A = PSW;
MOVX @DPTR, A; pContext−>PSW = 0x00
POP PSW; Restore PSW
A oitava e ultima etapa da inicializacao do contexto da tarefa permite inicializar
as variaveis da estrutura do contexto da tarefa que armazenam a informacao relativa
ao segmento da stack. Por outras palavras, inicializam a variavel SP com o endereco
da pilha interna, bem como as variaveis XSP L e XSP H com o endereco da pilha
externa.
79
4.1. Porting do ADEOS para a Plataforma MCS-51
Mudanca de contexto
Tal como na inicializacao, tambem a rotina de mudanca de contexto apresenta
uma estrategia de implementacao mais longa, quando comparada com a estrategia
descrita na seccao 4.1.1. Mais uma vez, o processo e condicionado pela tarefa atual
em execucao. O algoritmo 4 apresenta essa estrategia.
O codigo apresentado abaixo (em parte, prologo da funcao) implementa a condicao
da tarefa em dois passos.
MOV A, 2; put pOldContext L in A
JNZ fromTask; if pOldContext L != 0, no NULL pointer
MOV A,3; put pOldContext H in A
JNZ fromTask; if pOldContext H != 0, no NULL pointer , goto fromTask
CALL fromIdle; NULL pointer , goto fromIdle
Caso a tarefa em execucao seja a idle, entao o apontador para o contexto dessa
tarefa e nulo. Portanto, o codigo acima testa o LSB e MSB desse endereco, e so na
eventualidade de ambos serem nulos e que salta para a etapa oito. Isso e conseguido
com a instrucao JNZ, que verifica se o valor do acumulador e nulo e salta para o
endereco de codigo da label caso isso nao aconteca. Se acontecer continua o fluxo
normal de execucao, sem efetuar nenhum salto.
A segunda etapa implementa o primeiro estagio do backup da informacao da tarefa
em execucao, isto e, salvaguarda o endereco da proxima instrucao a executar assim
que a tarefa volte a obter o controlo do processador.
As etapas tres a oito permitem gravar os registos, flags e interrupcoes do proces-
sador, bem como a pilha da tarefa. A metodologia de implementacao e semelhante
em todos os casos. Como esses registos sao guardados na pilha (interna) antes da
chamada da funcao contextSwitch, consistem basicamente em aceder ao endereco da
pilha que tem o estado do registo, e copiar essa informacao para a respetiva estrutura.
O codigo abaixo exemplifica para o caso do registo A do processador.
;Save A
MOV A, SP; Save into ACC SP adress
CLR C; Clear Carry to subtract
SUBB A,#9; Point to the adress of ACC saved in stack
MOV R1,A; R1 = adress ACC (saved)
MOV A,@R1; A = A(saved into stack)
INC DPTR; point to pOldContext−>A
MOVX @DPTR, A; pOldContext−>A = A
...
Depois de efetuado o backup da informacao da tarefa em execucao, e entao ne-
cessario restaurar o estado da nova tarefa. Como a execucao de instrucoes afeta
80
Capıtulo 4. Implementacao do Sistema
Algoritmo 4 Mudanca de contexto no 8051 - contextSwitch
contextSwitch(...):
aceder ao apontador do parametro old Context ;
if tarefa idle then;
guardar o endereco de retorno;
guardar o registo A e B;
guardar o estado das interrupcoes;
guardar o registo DPTR;
guardar os registo R0-R7;
guardar as flags do processador;
guardar o segmento da stack ;
endif ;
aceder ao apontador do parametro new Context ;
restaurar o segmento da stack ;
restaurar o endereco de retorno
restaurar o registo A e B;
restaurar o estado das interrupcoes;
restaurar o registo DPTR;
restaurar os registo R0-R7;
restaurar as flags do processador;
81
4.2. Upgrade do ADEOS
registos no processador, a estrategia passa por reter a informacao do novo estado na
pilha (interna), e apenas restaura-lo no processador no momento anterior ao retorno
da funcao.
A nona etapa e a primeira destinada ao restauro do estado da nova tarefa. Com as
instrucoes MOV acede-se ao endereco na estrutura do contexto da nova tarefa, passado
como argumento atraves dos registos R4 e R5.
;Get pNewContext
MOV DPL, 4; get pNewContext
MOV DPH, 5; get pNewContext
As etapas dez a quinze permitem restaurar o endereco de retorno, registos, flags
e interrupcoes do processador. Conforme foi previamente explicado, esse restauro e
feito em dois momentos, pelo que o codigo apresentado acima reflete esse primeiro
estagio. Acede-se os elementos da estrutura da nova tarefa, e copia-se a informacao
para a pilha (interna). So mais tarde e que essa informacao e restaurada ao proces-
sador.
;Save the return address into stack
INC DPTR; point to pNewContext−>PC L
MOVX A,@DPTR; A = pNewContext−>PC L
PUSH A; Save ACC (pNewContext−>PC L) into stack
MOV DPL, 4; get pNewContext
MOV DPH, 5; get pNewContext
MOVX A,@DPTR; A = pNewContext−>PC H
PUSH A; Save ACC (pNewContext−>PC H) into stack
;Save A and B into stack
INC DPTR; point to pNewContext−>PC L
INC DPTR; point to pNewContext−>A
MOVX A,@DPTR; A = pNewContext−>A
PUSH A
...
;Save PSW into stack
INC DPTR; point to pNewContext−>PSW
MOVX A,@DPTR; A = pNewContext−>PSW
PUSH A
A etapa dezasseis reflete o restauro da pilha da tarefa (interna e externa). Depois
disso, o ultimo bloco de codigo (epılogo da funcao) faz o restauro sequencial da
informacao da nova tarefa.
4.2 Upgrade do ADEOS
O upgrade de software e um processo gradual e progressivo, que requer tempo
pois existe sempre alguma funcionalidade a implementar. O ADEOS nao e excepcao.
82
Capıtulo 4. Implementacao do Sistema
Assim sendo, o upgrade de um sistema operativo podia, por si so, dar origem a uma
dissertacao. Como tal, o autor decidiu expandir e melhorar o sistema operativo em
tres aspetos: (1) clock-tick intrınseco ao escalonador; (2) device-drivers para os pe-
rifericos do 8051; (3) escalonador power-aware. O primeiro porque possibilita ao sis-
tema operativo implementar estrategias de escalonamento com time-slice. O segundo
porque os device drivers simplificam a interface com os perifericos do microcontrola-
dor. Finalmente, o escalonador power-aware porque implementa uma estrategia de
escalonamento tendo em vista a minimizacao do consumo, caracterıstica fundamen-
tal nos sistemas embebidos atuais. Outras funcionalidades como metodos de comu-
nicacao entre processos (message queue, shared memory, etc.), outras estrategias de
escalonamento, ou mesmo uma pilha TCP/IP, podem ser implementadas de forma
gradual, pois nao sao o foco central nem desempenham um papel crucial na presente
dissertacao.
4.2.1 Upgrade: clock-tick no escalonador
Conforme mencionado na seccao 3.2.2, um dos pontos de escalonamento acontece
com o clock-tick dos temporizadores por software. A versao original do sistema opera-
tivo implementa temporizadores por software para gerir a periocidade e o estado das
tarefas. Por outras palavras, sempre que esse clock-tick ocorre, o sistema operativo
decrementa e verifica os temporizadores por software ativos, e caso algum termine as
tarefas colocadas em estado waiting a espera dessa temporizacao sao comutadas para
o estado ready. Esta metodologia e bastante eficaz para o tipo de escalonador im-
plementado, no entanto em escalonadores com time-slice esta abordagem e ineficaz.
Neste sentido, como a tarefa do autor passa por criar a base do sistema operativo
para o melhorar e aumentar gradualmente, este decidiu implementar um clock-tick
intrınseco ao proprio escalonador, responsavel por invocar o escalonador a cada time-
slice. Desta forma e possıvel escalonar utilizando a abordagem dos temporizadores
por software, ou entao seguindo a estrategia de time-slice.
Para implementar essa nova estrategia, convem primeiro definir uma nova inter-
rupcao desencadeada pelo trigger da interrupcao do temporizador. O 8051 classico
dispoe de dois temporizadores. O temporizador 0 e utilizado para gerar a interrupcao
responsavel pela gestao dos temporizadores por software. O temporizador 1 tem que
ser entao utilizado para desencadear o trigger responsavel pelo time-slice. A tabela
4.1 apresenta a implementacao da rotina de ISR invocada aquando da ocorrencia do
83
4.2. Upgrade do ADEOS
Tabela 4.1: Rotina de interrupcao do temporizador 1
Metodo C++ CDP (8051)#pragma vector = TF1 int
interrupt void Sched::tick(void){
enterCS();
recharge sched tick(˜CYCLES PER TICK);os.schedule();
exitCS();}
recharge sched tick:CODEMOV TL1,R2MOV TH1,R3RET
overflow do temporizador 1, assim como a implementacao assembly de recarrega-
mento dos registos de contagem do temporizador.
A definicao da rotina de interrupcao e feita com a macro ]pragma vector. A
interrupcao e embutida na classe definindo-a como estatica na sua declaracao. Na
ocorrencia da interrupcao, o temporizador e novamente carregado com o valor da
temporizacao pretendida, e o escalonador e invocado. Como e uma rotina de servico
a interrupcao, e considerada uma zona crıtica, daı que o codigo esteja delimitado pelas
macros enterCS() e exitCS(). De forma a tornar o codigo o mais portavel possıvel, a
funcao responsavel pela reconfiguracao da temporizacao e implementada diretamente
em assembly, juntamente com o restante codigo dependente do processador (ficheiros
bsp.asm). Basicamente, os registos do temporizador 1 sao carregados com o valor do
parametro de temporizacao passado na funcao atraves dos registos R2 e R3 (inteiro).
Para alem da especificacao da rotina de servico a interrupcao, e necessario configu-
rar o temporizador 1. Por exemplo, e preciso especificar a cadencia (temporizacao) a
que ocorre a interrupcao, assim como a habilitacao da mesma. A tabela 4.2 apresenta
o metodo responsavel pela configuracao do temporizador responsavel pelo clock-tick,
assim como o respetivo CDP implementado em assembly. O codigo assembly confi-
gura o timer 1 para funcionar como temporizador de 16-bit, habilita a interrupcao de
overflow do respetivo temporizador, e carrega os registos de contagem com o valor
para gerar o trigger da interrupcao com a cadencia temporal pretendida.
Depois de configurado o clock-tick e definida a rotina de ISR, apenas e necessario
colocar o correr a temporizacao. Para isso, e especificado o metodo run tick, res-
ponsavel por iniciar a contagem no temporizador. O codigo C++ e assembly apre-
sentados na tabela 4.3 especificam o metodo explicado anteriormente e a respetiva
implementacao. A implementacao de baixo nıvel e bastante simples, e corresponde
apenas a activacao da flag TR1 (Timer 1 Run) no registo TCON.
84
Capıtulo 4. Implementacao do Sistema
Tabela 4.2: Configuracao do temporizador 1
Metodo C++ CDP (8051)
void Sched::config tick(){
config sched tick(˜CYCLES PER TICK);}
config sched tick:CODEORL TMOD,#0x10ORL IEN0,#0x88MOV TL1,R2MOV TH1,R3RET
Tabela 4.3: Inicializacao da contagem do temporizador 1
Metodo C++ CDP (8051)void Sched::run tick(){
run sched tick();}
run sched tick:CODEORL TCON,#0x40RET
Desta forma, a possibilidade de inclusao do clock-tick para o time-slice fica apenas
restringindo a duas linhas de codigo. Antes da execucao do metodo de inicializacao do
sistema operativo, sao executados os metodos config tick e run tick (listagem 4.7).
Omitindo a chamada desses metodos o sistema operativo nao invoca a ISR responsavel
por esse clock-tick. Assim sendo, o sistema operativo fica preparado para algoritmos
de escalonamento com time-slice, possibilitando, no futuro, a implementacao, por
exemplo, do escalonador round-robin com time-slice.
void main(void)
{os.config tick();
os.run tick();
os.start();
}
Listagem 4.7: Configuracao do clock-tick do escalonador
4.2.2 Upgrade: device drivers
Um device driver e um componente de software que permite que aplicacoes de
alto nıvel comuniquem e interajam com dispositivos de hardware. Por outras pala-
vras, podem ser definidos como black boxes que permitem que um componente de
hardware responda a uma determinada interface de programacao. Estes escondem
completamente os detalhes de como o dispositivo funciona, e disponibilizam apenas
operacoes e chamadas padronizadas que atuam no hardware real [66].
Na versao original do ADEOS a interface ao hardware nao utiliza a pura abstracao
85
4.2. Upgrade do ADEOS
associada ao conceito de device driver (que tem associado um modelo comum), ape-
nas implementa controladores de hardware sob a forma de classes. Isto porque o
projetista apenas pretendeu demonstrar como e que os dois dispositivos (porta serie,
temporizador) podiam ser implementados usando classes. Assim sendo, a ideia do
autor passa entao, numa primeira fase, por desenvolver os varios controladores de
hardware como objetos representativos dos diversos perifericos do 8051. Contudo,
futuramente definir-se-a um modelo para uma framework I/O, em que essa abstracao
e implementada com template metaprogramming. Desta forma, implementar-se-a a
verdadeira abstracao caracterıstica do modelo dos device drivers. Isto tudo para expli-
car o porque da designacao de device drivers atribuıda aos controladores de hardware
desenvolvidos para os varios perifericos - (i) PWM, (ii) UART, (iii) GPIO, (iv) I2C
e (v) SPI - do 8051.
Device Driver : PWM
Pulse with modulation, ou em portugues, modulacao por largura de pulso, e uma
tecnica que permite gerar sinais analogicos, recorrendo a hardware externo (filtro
passa-baixo), a partir de sinais digitais. O controlo digital e usado para gerar uma
onda quadrada, que alterna constantemente entre o estado ligado (on) e desligado
(off ). A porcao de tempo que o sinal esta em estado on, relativamente ao seu perıodo,
e designado de largura de impulso (duty-cycle). Assim, controlando o tempo que o
sinal esta a on e off num determinado perıodo de tempo, e possıvel obter diferentes
valores analogicos. Por exemplo, num sinal com um perıodo de 10ms e com uma
tensao maxima de 5V, se a onda estiver 6ms em estado on (5V) e 4ms em estado off
(0V), o valor medio analogico conseguido e de 3V. Este tipo de tecnica e muito utili-
zada para controlar o brilho de LEDs e a velocidade de motores de corrente contınua
(DC).
PWM no 8051
O microcontrolador 8051, na versao AT89C51ID2 da Atmel, dispoe de quatro
modulos de PWM configurados atraves do periferico PCA (Programmable Counter
Array). O PCA consiste num temporizador/contador dedicado que serve de base
para um vetor de cinco modulos de comparacao/captura.
Todos os modulos do PCA podem ser usados com saıdas de PWM. A frequencia
da saıda e comum a todos os modulos, pois depende da fonte de relogio do periferico:
86
Capıtulo 4. Implementacao do Sistema
(i) frequencia de relogio do microcontrolador com divisao por seis; (ii) frequencia de
relogio do microcontrolador com divisao por dois; (iii) overflow do timer 0; (iv) fonte
externa atraves do pino P1.2. O valor do duty-cycle de cada modulo e independente
e variavel (registo CCAPLn). Quando o valor do contador do PCA e inferior ao valor
carregado no registo do modulo, a saıda permanece em baixo, no entanto quando
esse valor e igual ou superior entao a saıda e ativada. Quando o registo de tempo-
rizacao do PCA atinge o overflow, o valor do registo CCAPLn e carregado com o
valor do registo CCAPHn. Isto permite fazer atualizacao do valor do PWM sem a
ocorrencia de falhas no sinal. Os bits de PWMn e ECOMn devem ser ativados no
registo CCAPMn para selecionar o modo pretendido.
PWM DD: Design
O diagrama de classes da figura 4.4 representa a estrutura de classes do driver de
PWM. Este e composto por uma classe principal, uma estrutura de configuracao e
varias enumeracoes.
Figura 4.4: Diagrama de classes do driver PWM
A classe tem quatro atributos e dez metodos. Relativamente aos atributos, dois de-
les sao atributos da instancia e dois atributos da classe (atributos estaticos). Os atri-
butos da instancia permitem caracterizar cada objeto com a especificacao do modulo
87
4.2. Upgrade do ADEOS
(module) e o valor do duty-cycle (dutycycle). Os atributos da classe permitem gerir
e garantir a unicidade dos modulos de PWM. No que diz respeito aos metodos, estes
tambem podem ser caracterizados como metodos da instancia e metodos da classe.
Assim, a classe Pwm8051 possui sete metodos da instancia e tres metodos da classe.
Os primeiros permitem instanciar e configurar um objeto, enquanto os outros permi-
tem gerir os modulos, de modo a nao permitir tanto a instanciacao de modulos de
PWM ja criados, como ultrapassar o limite maximo de modulos de PWM existentes
no periferico.
A estrutura pwm8051 config e utilizada para configurar cada um dos modulos da
sua instanciacao ou configuracao. Com esta metodologia, apenas e passado o aponta-
dor da estrutura no chamada do metodo, garantindo assim melhor desempenho. Isto
porque o compilador em vez de colocar todos os argumentos na pilha ou em regis-
tos, coloca apenas o apontador da estrutura. Quando a configuracao requer poucos
parametros a diferenca de performance nao e assim tao acentuada, no entanto em
drivers com muitos parametros de configuracao a diferenca e consideravel.
A utilizacao das diversas enumeracoes permite adicionar alguma portabilidade ao
codigo a desenvolver. Exemplificando, caso outra versao do microcontrolador utilize
o valor 0x60 em vez de 0x40 para ativar o modulo de PWM, entao basta modificar
esse valor na enumeracao sem ter de modificar todos os segmentos de codigo que o
usam.
PWM DD: Implementacao
A utilizacao da orientacao a objetos no desenvolvimento de software tem crescido
exponencialmente, muito por causa da simplicidade e transparencia na passagem do
design para a implementacao. Se o software for bem desenhado utilizando as tecnicas
de UML, a implementacao torna-se muito clara e fidedigna. Portanto, o codigo da
listagem 4.8 representa a declaracao da classe especificada no diagrama da figura 4.4.
class Pwm8051
{public:
Pwm8051();
Pwm8051(pwm8051 Config ∗ Config);
˜Pwm8051();
void config(pwm8051 Config ∗ Config);
void config DC module(unsigned char dc);
void config Freq module(pwm freq freq);
void enable module(pwm en enable);
static bool check module(unsigned char module);
88
Capıtulo 4. Implementacao do Sistema
static void enable ALL(pwm en enable);
static void config Freq ALL(pwm freq freq);
private:
unsigned char module;
unsigned char dutycycle;
enum {max pwm modules = 4};static unsigned char modules[];
static unsigned char num modules;
};
Listagem 4.8: Declaracao da classe Pwm8051
Na classe apresentada acima, utiliza-se a tecnica de encapsulamento. Portanto,
os metodos sao declarados como publicos, enquanto os atributos como privados. Os
primeiros tres metodos representam o construtor e destrutor da classe. Os quatro
metodos seguintes permitem fazer a configuracao total ou parcial do device driver, isto
e, permitem configurar o duty-cycle, frequencia e activacao de cada um dos modulos.
Os ultimos tres metodos sao os metodos da classe, pois permitem a configuracao de
todos os modulos simultaneamente, e nao de cada instancia ou modulo em particular.
Os primeiros dois atributos permitem configurar cada modulo. A enumeracao limita
o numero maximo de modulos do periferico. Os ultimos dois atributos da classe
permitem fazer essa gestao dos modulos.
A estrutura que permite fazer a configuracao do driver e apresentada na listagem
4.11. Esta e composta por dois elementos, que permitem configurar qual o modulo
que se pretende instanciar (0 a 4) e o respectivo valor do duty-cycle (0 a 255, em que
255 corresponde a estar sempre activo).
typedef struct pwm8051 config
{unsigned char dc;
unsigned char module;
}pwm8051 Config;
Listagem 4.9: Estrutura de configuracao da classe Pwm8051
A definicao das enumeracoes que contem a informacao dos parametros especıficos
deste hardware e apresentada na listagem 4.10.
enum pwm freq {f osc2 = 0x02, f osc6= 0x00, f timer = 0x04, f ext = 0x06};enum pwm en {enable = 0x40, disable = 0x00};enum pca pwm {pwm 8bit = 0x42, none = 0x00};
Listagem 4.10: Enumeracoes da classe Pwm8051
Sempre que um novo objeto e instanciado, o periferico PCA e configurado para
funcionar no modo PWM. Depois, com o metodo de configuracao total (config) e
89
4.2. Upgrade do ADEOS
possıvel configurar o modulo criado (listagem 4.11). Esta recebe como parametro o
apontador da estrutura de configuracao, que permite inicializar os atributos module
e dutycycle intrınsecos ao objecto. As duas linhas de codigo seguintes adicionam
esse modulo aos atributos da classe. Por fim, e inicializado o registo de configuracao
do duty-cycle com o valor pretendido.
void Pwm8051::config(pwm8051 Config ∗ Config)
{module = Config−>module;
dutycycle = (255−Config−>dc);
modules[num modules] = module;
num modules++;
switch(module)
{case 0:
CCAP0H=DutyCycle; //Reload value to Duty−Cycle
break;
case 1:
CCAP1H=DutyCycle; //Reload value to Duty−Cycle
break;
case 2:
CCAP2H=DutyCycle; //Reload value to Duty−Cycle
break;
case 3:
CCAP3H=DutyCycle; //Reload value to Duty−Cycle
break;
}}
Listagem 4.11: Metodo config da classe Pwm8051
A implementacao dos restantes metodos pode ser consultada no codigo fonte do
driver PWM desenvolvido.
Device Driver : UART
Universal Asynchronous Receiver/Transmitter (UART) e um transmissor/recep-
tor full-duplex que fornece toda a logica para a transferencia assıncrono, isto e, e
um componente de hardware que converte e formata os dados entre as formas serie
e paralela. Estes geralmente sao usados em conjunto com normas de comunicacao,
nomeadamente RS-232, RS-422 ou RS-485. A designacao universal indica que o for-
mato de dados e as velocidades de transmissao sao configuraveis, e que os nıveis de
tensao dos sinais eletricos sao convertidos por um circuito externo, como por exemplo
o MAX232 [67].
90
Capıtulo 4. Implementacao do Sistema
UART no 8051
A porta serie integrada no AT89C51ID2 e compatıvel com a porta serie da famılia
MCS-51. Assim, esta permite a transmissao no modo full-duplex, e pode funcionar
em varios modos e frequencias. A sua principal funcao assenta, portanto, na con-
versao paralelo-serie dos dados a serem transmitidos e conversao serie-paralelo dos
dados recebidos. O hardware da porta serie pode ser acedido atraves dos pinos TxD
(transmissao) e RxD (rececao), e apresenta um buffer que permite a rececao de um
segundo caracter antes da leitura do primeiro. Assim, a rececao dos caracteres e efe-
tuada atraves da leitura do registo SBUF, enquanto o envio de um caracter e realizado
pela escrita no mesmo registo. A porta serie fornece quatro modos de funcionamento,
programados atraves da escrita nos bits SM0 e SM1 do registo SCON. Este registo
contem tambem os bits de estado e controlo da mesma. Os modos 1, 2 e 3 permitem
a comunicacao assıncrona, com os bits de dados encapsulados entre o start e stop
bit. O modo 0 e sıncrono e a porta serie funciona como um registo de deslocamento.
Nos modos 1 e 3 o baud rate e variavel e pode ser gerado pelo temporizador 1 ou 2.
Os modos 2 e 3 permitem a comunicacao entre varios processadores 8051, usando o
modelo de multiprocessamento mestre-escravo. Para isso basta ativar o bit SM2 do
registo SCON.
UART DD: Design
O diagrama de classes da figura 4.5 representa a estrutura de classes do driver
UART. Este e composto por duas classes, uma estrutura de configuracao e varias
enumeracoes.
A classe Uart8051 tem dois atributos e oito metodos. Os atributos, ambos da
instancia, permitem definir um apontador para um buffer de rececao e transmissao,
responsaveis por reter os dados da comunicacao. No que diz respeito aos metodos,
os primeiros quatro permitem instanciar e configurar um objeto UART, enquanto os
outros permitem iniciar o processo de transmissao e rececao, e obter o tamanho de
cada um dos buffers.
A classe Buffer tem cinco atributos e oito metodos. Relativamente aos atributos,
o primeiro (array) e um apontador para o primeiro elemento do buffer, o segundo
(size) define o tamanho do buffer, o terceiro (head) e quarto (tail) permitem gerir
os elementos do mesmo, e, finalmente, o quinto atributo (count) permite saber o
numero de itens presentes no buffer. Os metodos, os primeiros correspondem ao
91
4.2. Upgrade do ADEOS
Figura 4.5: Diagrama de classes do driver UART
construtor e destrutor do objeto, enquanto os restantes fazem a gestao do buffer,
como por exemplo adicionar e remover itens do mesmo.
A estrutura uart8051 config e utilizada para configurar a porta serie no mo-
mento da sua instanciacao ou configuracao. Desta forma, e possıvel configurar por
exemplo o baud rate, modo de operacao (sıncrona ou assıncrona), rececao, e buf-
fers. Com a utilizacao desta metodologia, o ganho de desempenho e relativamente
maior que no caso anterior (driver PWM), pois, tal como foi explicado, o numero
de parametros que seriam passados como argumentos da funcao e consideravelmente
superior.
Mais uma vez, a utilizacao das enumeracoes para especificar os valores dos regis-
tos na configuracao da porta serie, permite adicionar alguma portabilidade e clareza
ao codigo a desenvolver.
UART DD: Implementacao
Na classe Uart8051 (listagem 4.12) e utilizado o encapsulamento para garantir
a integridade dos dados contidos no objeto. Portanto, os metodos sao declarados
92
Capıtulo 4. Implementacao do Sistema
como publicos enquanto os atributos como privados. Os primeiros tres metodos
representam o construtor e destrutor da classe. Os sete metodos seguintes permitem
fazer a configuracao total ou parcial do device driver, isto e, permitem configurar,
por exemplo, o baud rate, o modo de funcionamento (sıncrono ou assıncrono), e a
multicomunicacao. Os ultimos quatro metodos permitem desencadear a transmissao
e rececao dos dados, assim como saber o numero de elementos de cada um dos buffers
(rececao e transmissao)
class Uart8051
{public:
Uart8051();
Uart8051(uart8051 Config ∗ Config);
˜Uart8051();
void config(uart8051 Config ∗ Config);
void config baudrate(uart baud baudrate);
void config mode(uart mode mode);
void config reception(uart reception reception);
void config multiCom(uart multiCom multiCom);
void config TX b8(uart tx b8 TX b8);
void config RX b8(uart tx b8 RX b8);
void txStart(void);
void rxStart(void);
int get tx buf size() {return pTx buf−>getSize();}int get rx buf size() {return pRx buf−>getSize();}
private:
Buffer ∗ pTx buf;
Buffer ∗ pRx buf;
};
Listagem 4.12: Declaracao da classe Uart8051
A estrutura que permite fazer a configuracao do driver e composta por oito mem-
bros, que permitem configurar o baud rate (4800, 9600, 19200, 28800, ...), o modo
de operacao (modo 0,1,2 ou 3), a ativacao da rececao, a multicomunicacao, e o 8-
bit de dados da transmissao e rececao. Os elementos ptx buf e prx buf definem
apontadores para os buffers de transmissao e rececao.
A implementacao do construtor da classe que permite a configuracao da porta
serie e apresentado na listagem 4.13. Este recebe como parametro o apontador da
estrutura de configuracao. As primeiras oito linhas de codigo permitem configurar
o valor do baud rate, no entanto apenas nos modos onde isso e possıvel (modos 1 e
3). O temporizador 2 e definido como o gerador de baud rate pois os temporizadores
0 e 1 ja sao utilizados para outras funcoes do sistema operativo. O registo SCON e
93
4.2. Upgrade do ADEOS
configurado com as funcionalidades pretendidas. Os elementos pTx buf e pRx buf,
intrınsecos a classe, sao inicializados com os apontadores pretendidos.
Uart8051::Uart8051(uart8051 Config ∗ Config)
{if(Config−>mode == mode1 || Config−>mode == mode3)
{int baud value;
T2CON = 0x34;//timer 2 baud rate generator
baud value = (int)(65535)−(F OSC/(32∗Config−>baudrate));
RCAP2H = (baud value&0xff00)>>8;
RCAP2L = (baud value&0x00ff);
}SCON |= Config−>mode | Config−>reception | Config−>multiCom
| Config−>tx b8 | Config−>rx b8;
pTx buf = Config−>ptx buf;
pRx buf = Config−>prx buf;
}
Listagem 4.13: Construtor da classe Uart8051 com configuracao
Os metodos apresentados na listagem 4.14 implementam a transmissao e rececao
de dados na porta serie. A transmissao consiste em colocar no registo SBUF um
elementos do buffer de transmissao, e esperar que a flag de conclusao de transmissao
(TI) seja ativa. Para enviar n elementos, repete-se o processo n vezes. A recepcao e
o processo inverso. Aguarda-se que a flag de conclusao de recepcao (RI) seja ativada,
e coloca-se o elemento recebido no buffer de rececao. Para receber n elementos,
repete-se o processo n vezes.
void Uart8051::txStart(void)
{SBUF = tx buf−>remove();
while(!(SCON&TI)); //while TI=0
SCON &=˜TI;//Clean TI
}void Uart8051::rxStart(void)
{while(!(SCON&RI));//while RI=0
SCON &=˜RI;//Clean RI
rx buf−>add(SBUF);
}
Listagem 4.14: Metodos txStart e rxStart da classe Uart8051
A implementacao dos restantes metodos, assim como a implementacao da classe
Buffer, pode ser consultada no codigo fonte do driver UART desenvolvido.
94
Capıtulo 4. Implementacao do Sistema
Device Driver: GPIO
General Purpose Input/Output (GPIO), ou em portugues, entradas/saıdas de
proposito geral, podem ser designadas como pinos genericos presentes em chips cujo
comportamento (incluindo a definicao de entrada ou saıda) pode ser controlador por
software. Este tipo de hardware e muito utilizado em integrados multifuncoes (por
exemplo, codecs de audio, placas de vıdeo) ou em aplicacoes embebidas (por exemplo,
Arduino) para leitura de sensores (temperatura, aceleracao, orientacao) ou controlo
de motores de corrente continua e brilho de LEDs. As capacidades de um pino de
GPIO incluem a configuracao da direcao (entrada ou saıda), mascara (ativos ou ina-
tivos), valores de entrada e saıda, e configuracao de interrupcoes. Um grupo de pinos
GPIO, tipicamente 8 pinos, e designado como um porto GPIO.
GPIO no 8051
Todos os registos de controlo de perifericos do 8051 estao mapeados na memoria
de dados interna, concretamente na area do SFR. Assim sendo, as quatro portas de
entrada/saıda possuem quatro registos de 8-bit que permitem controla-los: P0, P1,
P2 e P3. Cada um destes registos possui latches e hardware de interface as saıdas
(output drivers) e de leitura das entradas (input buffers) que permitem implementar
as funcionalidades necessarias a uma porta de entrada/saıda digital. As oito linhas de
cada uma destas portas I/O podem ser tratadas individualmente, de modo a realizar
a interface a dispositivos de 1-bit, ou entao como unidades para realizar a interface
paralela de 8-bit a outros dispositivos. Por defeito todos os pinos estao definidos como
entradas digitais. Sempre que se pretende definir um pino como saıda, e necessario
ativar a respetiva latch, ou seja, escrever o valor logico ’1’. So depois de definido
como saıda e que o pino pode ser especificado como saıda a nıvel logico alto ou baixo.
GPIO DD: Design
O diagrama de classes da figura 4.6 representa a estrutura de classes do driver
GPIO. Este e composto por uma classe principal, uma estrutura de configuracao e
varias enumeracoes.
A classe tem cinco atributos e sete metodos. Relativamente aos atributos, tres sao
atributos da instancia enquanto os outros sao atributos da classe (atributos estaticos).
Os atributos da instancia permitem caracterizar cada objeto com a definicao da porta
(port), pino (pin) e direcao (direction). Os atributos da classe permitem gerir e
95
4.2. Upgrade do ADEOS
Figura 4.6: Diagrama de classes do driver GPIO
garantir a unicidade dos pinos. No que diz respeito aos metodos, existem tambem
metodos da instancia e metodos da classe. Assim, a classe Gpio8051 possui seis
metodos da instancia e um metodo da classe. Os primeiros permitem instanciar e
configurar um objeto GPIO, enquanto o metodo da classe permite fazer a gestao dos
mesmos, isto e, garantir nao so que nao e instanciado nenhum pino ja utilizado, assim
como um pino que nao exista.
A estrutura gpio8051 Config e utilizada para configurar o pino de GPIO no mo-
mento da sua instanciacao ou configuracao. Desta forma, e possıvel configurar por
exemplo o porto, o pino e a direcao.
GPIO DD: Implementacao
Na classe Gpio8051 (listagem 4.15) os primeiros tres metodos representam o cons-
trutor e destrutor da classe. Os tres metodos seguintes permitem fazer a configuracao
total ou parcial do device driver, isto e, permitem configurar, por exemplo, a porta,
pino e direcao. O ultimo, metodo da classe, permite verificar, antes da configuracao,
se um determinado pino de GPIO e valido. Os primeiros tres atributos permitem a
sua configuracao. Os ultimos dois atributos da classe permitem fazer essa gestao dos
96
Capıtulo 4. Implementacao do Sistema
pinos.
class Gpio8051
{public:
Gpio8051();
Gpio8051(gpio8051 Config ∗ Config);
˜Gpio8051();
void config(gpio8051 Config ∗ Config);
void config direction(gpio direction direction);
bool config output(gpio out value);
static bool check gpio(gpio8051 Config ∗ Config);
private:
unsigned char port;
unsigned char pin;
unsigned char direction;
static unsigned char gpios[max gpio];
static unsigned char num gpios;
};
Listagem 4.15: Declaracao da classe Gpio8051
A estrutura que permite fazer a configuracao do driver e composta por tres ele-
mentos, que permitem configurar qual a porta (p0 a p3), o pino (0 a 7), e a direcao
(input ou output) do pino de GPIO.
Sempre que um objeto do tipo pino e instanciado este deve ser devidamente con-
figurado. A listagem 4.16 apresenta a implementacao do metodo de configuracao.
Esta recebe como parametro o apontador da estrutura de configuracao, que permite
inicializar os atributos port, pin e direction intrınsecos ao objecto. As duas li-
nhas de codigo seguintes adicionam esse modulo aos atributos da classe. Por fim, e
especificado no hardware o valor do registo para configuracao da direcao do pino.
void Gpio8051::config(gpio8051 Config ∗ Config)
{port = Config−>port;
pin = Config−>pin;
direction = Config−>direction;
gpios[num gpios] = (port<<4)|(pin);
num gpios++;
switch(port)
{case p0:
if(direction == input) P0|=(1<<pin);
else P0&=˜(1<<pin);
break;
case p1:
if(direction == input) P1|=(1<<pin);
else P1&=˜(1<<pin);
97
4.2. Upgrade do ADEOS
break;
case p2:
if(direction == input) P2|=(1<<pin);
else P2&=˜(1<<pin);
break;
case p3:
if(direction == input) P3|=(1<<pin);
else P3&=˜(1<<pin);
break;
}}
Listagem 4.16: Metodo config da classe Gpio8051
A implementacao dos restantes metodos pode ser consultada no codigo fonte do
driver GPIO desenvolvido.
Device Driver: I2C
Inter-Integrated Circuit (I2C) e um protocolo de comunicacao bidirecional de-
senvolvido e patenteado pela Philips (atual NXP), de forma a reduzir os custos de
fabrico dos dispositivos eletronicos. Isto porque os dispositivos utilizam apenas duas
linhas para a comunicacao (interface serie), permitindo a comunicacao utilizando um
numero reduzido de pinos. As duas linhas utilizadas pelo barramento I2C sao a SCL
(Serial Clock) e SDA (Serial Data). A linha SDA e responsavel por transportar os
dados, enquanto a linha SCL sincroniza a transferencia dos mesmos. Os dispositivos
I2C podem ser classificados como mestre (master) ou escravos (slave). Um disposi-
tivo que inicia a comunicacao e designado por master, enquanto um dispositivo que
responde as mensagens e denominado por slave. Um dispositivo pode ser unicamente
master, unicamente slave, ou entao comutar entre master e slave, dependendo da
finalidade da aplicacao. Normalmente, a velocidade de comunicacao corresponde a
100k-bit/s para modo standard, 400k-bit/s para o modo fast e 3.4M-bit/s para o
modo high-speed. [68]
A figura 4.7 ilustra o formato da trama I2C. A comunicacao inicia-se com o en-
vio da condicao de start pelo dispositivo master : enquanto a linha SCL esta a nıvel
logico alto (’1’), a linha de SDA e colocada a nıvel logico baixo (’0’). Depois disso, sao
enviados 7-bit com o endereco do dispositivo slave, mais 1-bit para definir se e uma
operacao de leitura (’1’) ou escrita (’0’). A transmissao e confirmada com o envio de
um acknowledge (linha SDA a ’0’) pelo dispositivo slave. A etapa seguinte consiste
no envio do byte de dados. Caso seja bem sucedido o slave envia novo acknowledge.
98
Capıtulo 4. Implementacao do Sistema
Posto isso, ou sao enviados dados continuamente, ou entao e sinalizada a condicao
de paragem por parte do master. Essa condicao consiste em colocar ambas as linhas
de comunicacao a nıvel logico alto.
Figura 4.7: Formato da trama I2C
I2C no 8051
No 8051 classico, nao existe uma implementacao por hardware do protocolo de co-
municacao I2C. No entanto, com o aumento exponencial da utilizacao do mesmo, os
fabricantes decidiram implementa-lo em algumas das versoes mais modernas. Assim,
o AT89C51ID2 da Atmel e um exemplo onde este esta presente.
Neste microcontrolador, o protocolo esta implementado com a designacao TWI
(2-wire interface). Isto porque a NXP patenteou o nome I2C, pelo que os outros fabri-
cantes implementam um protocolo analogo com uma designacao diferente. Tal como
o I2C, o TWI utiliza duas linhas para comunicacao, SCL e SDA, que sao responsaveis
pela transferencia e sincronizacao da informacao entre os dispositivos. O CPU con-
trola a logica do protocolo atraves de quatro registos especiais: SSCON (Synchronous
Serial Control); SSDAT (Synchronous Serial Data); SSCS (Synchronous Serial Con-
trol and Status); e SSADR (Synchronous Serial Address). Estes registos permitem
definir quatro modos de operacao: (i) master transmitter ; (ii) master receive; (iii)
slave transmitter ; e (iv) slave receive.
O registo SSCON e usado para ativar a interface TWI, programar a taxa de trans-
ferencia, ativar o modo slave, assinalar ou nao a rececao de dados, e enviar a condicao
de start ou stop. O registo SSCS especifica o estado da logica e barramento do pro-
tocolo. Existem 26 possibilidades diferentes. Estes codigos podem ser consultados
com mais detalhe do datasheet do microcontrolador [69]. O registo SSDAT contem
o byte de dados serie a ser transmitido ou recebido. Por outras palavras, antes de
99
4.2. Upgrade do ADEOS
desencadear e iniciar uma transmissao e necessario carregar o byte para o registo.
Por outro lado, sempre que uma rececao e concluıda, e necessario ler o byte deste
registo. Finalmente, o registo SSADR e responsavel por definir o endereco (7-bit) do
dispositivo sempre que este e definido como slave.
I2C DD: Design
O diagrama de classes da figura 4.8 representa a estrutura de classes do driver
I2C. Este e composto por uma classe principal, uma estrutura de configuracao e
varias enumeracoes.
Figura 4.8: Diagrama de classes do driver I2C
A classe I2c8051 tem tres atributos e dezoito metodos. Os atributos, ambos da
instancia, permitem caracterizar o objeto I2C, nomeadamente o modo de funciona-
mento e o endereco (seja ele o proprio endereco, no caso de ser slave, ou entao o
endereco do dispositivo com o qual pretende comunicar, no caso de ser master). No
que diz respeito aos metodos, os primeiros nove permitem instanciar e configurar um
objeto I2C, enquanto os outros permitem activar, iniciar, enviar, receber e parar a
100
Capıtulo 4. Implementacao do Sistema
transferencia de dados. Por exemplo, o metodo start envia a condicao de start do
protocolo, o metodo send address o endereco do dispositivo com o qual se pretende
comunicar, e o metodo read char recebe um byte de um dispositivo slave.
A estrutura i2c8051 Config e utilizada para configurar o dispositivo I2C no mo-
mento da sua instanciacao ou configuracao. Desta forma, e possıvel configurar, por
exemplo, o modo (master ou slave), o sentido da comunicacao (escrita ou leitura), o
endereco e a taxa de transferencia de dados.
I2C DD: Implementacao
Na classe I2c8051 (listagem 4.17) os primeiros tres metodos representam o cons-
trutor e destrutor da classe. Os seis metodos seguintes permitem fazer a configuracao
total ou parcial do device driver, isto e, permitem configurar, por exemplo, o modo,
o endereco e velocidade da comunicacao. Os ultimos metodos permitem a ativacao
(enable), iniciacao(start e rstart), envio (send address e write char), rececao
(read address e read char) e paragem (stop) da transferencia de dados. Os atri-
butos permitem configurar os dispositivos I2C.
class I2c8051
{public:
I2c8051();
I2c8051(i2c8051 Config ∗ Config);
˜I2c8051();
void config(i2c8051 Config ∗ Config);
void config mode(i2c mode mode);
void config rw(i2c rw rw);
void config adress(unsigned char addr);
void config rate(i2c rate rate);
void config assertACK(i2c assert ack assert ack);
void enable(i2c en enable);
void start();
void rstart();
void stop();
bool send address();
bool read address();
bool write char(unsigned char c);
bool read char(unsigned char ∗ c);
void end read char();
private:
i2c mode mode;
i2c rw rw;
unsigned char address;
101
4.2. Upgrade do ADEOS
};
Listagem 4.17: Declaracao da classe I2c051
A estrutura que permite fazer a configuracao do driver e composta por cinco
elementos, que permitem configurar o modo, o sentido (leitura ou escrita), o endereco,
a taxa de transferencia e o envio de confirmacoes (acknowledges).
A implementacao do construtores default da classe e apresentado na listagem 4.18.
Sempre que um dispositivo I2C e instanciado este e configurado como dispositivo
master de escrita, cujo endereco do dispositivo slave com o qual pretende comunicar
e 0x00. Por defeito, o registo de controlo SSCON e configurado de modo a desabilitar
o modulo I2C, a taxa de transmissao igual a frequencia do relogio do CPU com pre-
escalar de 256, e envio de acknowledge.
I2c8051::I2c8051()
{address = 0x00;
rw = write;
mode = master;
SSCON|= fclk 256 | not en | assert;
}
Listagem 4.18: Construtor por defeito da classe I2c051
Os metodos apresentados na listagem 4.19 implementam tanto o envio da condicao
de start como o envio de um byte utilizando o protocolo I2C. O envio da condicao
de start consiste em habilitar a respectiva flag no registo de controlo e aguardar que
o registo de estado (SSCS) sinalize o sucesso no envio. Por sua vez, para o envio
de dados e necessario preencher o registo SSDAT com o byte a enviar, e aguardar
que o registo de estado sinalize a transmissao correta, ou entao notifique a ocorrencia
de alguma anomalia. Daı que o metodo retorne verdade em caso de rececao de
acknowledge, ou falso caso isso nao aconteca.
void I2c8051::start()
{SSCON&=˜isr; //Clear SI interrupt
SSCON|=start ; //TWI start sending
do
{}while(SSCS != start t);//Wait to transmitt ACK
SSCON&=˜start ; //Clear start Condition
}
bool I2c8051::write char(unsigned char c)//return 1 OK, return 0 Error
{SSCON&=˜isr; //Clear SI interrupt
102
Capıtulo 4. Implementacao do Sistema
SSDAT = c;
do //Wait Data byte has been transmitted and ACK returned
{}while(SSCS != data t ack r && SSCS != data t nack r
&& SSCS != arbitation lost);
if(SSCS == data t ack r)
{return true;
}else
{return false;
}}
Listagem 4.19: Metodos start e write char da classe I2c8051
A implementacao dos restantes metodos pode ser consultada no codigo fonte do
driver I2C desenvolvido.
Device Driver: SPI
Serial Peripheral Interface Bus (SPI) e um protocolo de comunicacao serie sıncrono,
desenvolvido pela Motorola, que opera no modo full-duplex. Muitas vezes e tambem
designado por protocolo four-wire, isto porque utiliza quatro linhas para a comu-
nicacao: SCLK (Serial Clock); MOSI ou SIMO (Master Out Slave In); MISO ou
SOMI (Master In Slave Out); e SS (Slave Select). As linhas de MOSI e MISO sao
responsaveis pela transferencia dos dados, a linha SCLK pela sincronizacao da trans-
ferencia, e a linha SS pela selecao do dispositivo. Assim, neste protocolo existe um
dispositivo master e um ou mais dispositivos slave. Se existir mais do que um dis-
positivo slave no sistema, entao sao necessarias tantas linhas de selecao quantos os
dispositivos (figura 4.9a [70]).
A figura 4.9b [71] ilustra o diagrama temporal do protocolo. Sempre que o dis-
positivo master pretende iniciar a comunicacao este seleciona o dispositivo slave de-
sabilitando (nıvel logico ’0’) a respetiva linha de SS. Depois disso, habilita o sinal
de relogio (SCLK) com uma frequencia inferior a frequencia maxima do dispositivo
slave (tipicamente entre 1 a 30MHz). A polaridade do sinal de relogio pode ser ajus-
tada com as opcoes CPOL e CPHA. A comunicacao e full-duplex, pelo que o master
envia um byte para o slave enquanto recebe tambem um byte do mesmo. Quando
nao existirem mais dados para serem transmitidos, o dispositivo master interrompe
o sinal de relogio. Tipicamente o que acontece e manter o sinal de relogio ativo e
103
4.2. Upgrade do ADEOS
habilitar (nıvel logico ’1’) a linha de SS.
(a) Barramento SPI: um master etres slaves independentes
(b) Diagrama temporal do protocolo SPI
Figura 4.9: SPI: barramento e diagrama temporal
SPI no 8051
No 8051 classico, tambem nao existe uma implementacao por hardware do pro-
tocolo de comunicacao SPI. No entanto, tal como fizeram com o protocolo I2C,
os fabricantes decidiram implementa-lo em algumas das versoes mais modernas. O
AT89C51ID2 e exemplo disso.
Neste microcontrolador, os modulos de SPI incluem comunicacao full-duplex,
operacao no modo master ou slave, oito taxas de transferencia programaveis, si-
nal de relogio com polaridade e fase programaveis, e protecao contra colisoes. O
CPU controla a logica do protocolo atraves de tres registos especiais: SPCON (Serial
Peripheral Control); SPSTA (Serial Peripheral Status); e SPDAT (Serial Peripheral
Data). O registo SPCON e usado para ativar a interface SPI, configurar o modo de
operacao, programar a frequencia de transferencia, e selecionar a polaridade e fase do
sinal de relogio. O registo SPSTA contem as flags que traduzem o estado da logica e
barramento do protocolo. Por exemplo, se os dados foram transferidos com sucesso e
ativada a flag SPIF (Serial Peripheral Data Transfer Flag), enquanto se houver uma
colisao de informacao e ativada a flag WCOL (Write Collision Flag). Finalmente, o
registo SPDAT representa o buffer de escrita/leitura para a rececao de dados. Uma
escrita para este registo coloca os dados diretamente no shift register.
SPI DD: Design
104
Capıtulo 4. Implementacao do Sistema
O diagrama de classes da figura 4.10 representa a estrutura de classes do dri-
ver SPI. Este e composto por uma classe, uma estrutura de configuracao e varias
enumeracoes.
Figura 4.10: Diagrama de classes do driver SPI
A classe tem seis atributos e dezassete metodos. Relativamente aos atributos,
quatro sao atributos da instancia, enquanto dois sao atributos da classe. Os atributos
da instancia permitem caracterizar cada objeto com a especificacao do modo (mode),
operacao (rw), endereco (address) e linha de selecao (chip select). Os atributos da
classe permitem gerir e garantir a unicidade dos modulos SPI, mais concretamente, a
linha de selecao. No que diz respeito aos metodos, a classe Spi8051 possui dezasseis
metodos da instancia e unicamente um metodo da classe. Os primeiros dez permitem
instanciar e configurar um objeto, enquanto os restante permitem activar, iniciar,
enviar, receber e parar a transferencia de dados. O unico metodo da classe permite
gerir as linhas de selecao, de forma a que sao seja instanciados objetos de dispositivos
105
4.2. Upgrade do ADEOS
com a mesma linha de selecao.
A estrutura spi8051 Config e utilizada para configurar o dispositivo SPI no mo-
mento da sua instanciacao ou configuracao. Desta forma, e possıvel configurar por
exemplo o modo (master ou slave), a operacao (escrita ou leitura), a taxa de trans-
ferencia de dados, e a polaridade e fase do sinal de sincronismo.
SPI DD: Implementacao
Na classe Spi8051 (listagem 4.20) os primeiros tres metodos representam o cons-
trutor e destrutor da classe. Os sete metodos seguintes permitem fazer a configuracao
total ou parcial do device driver, isto e, permitem configurar, por exemplo, o modo, a
operacao, e a polaridade e fase do sinal de relogio. Os restantes metodos da instancia
permitem a ativacao, envio e rececao de dados. O ultimo metodo e o metodo da
classe, responsavel por verificar a unicidade de cada instancia. Os primeiros quatro
atributos permitem a configuracao do dispositivo. A enumeracao limita o numero
maximo de dispositivos SPI (limitado ao numero de linhas de selecao). Os ultimos
dois atributos da classe permitem fazer essa gestao das linhas de selecao.
class Spi8051
{public:
Spi8051();
Spi8051(spi8051 Config ∗ Config);
˜Spi8051();
void config(spi8051 Config ∗ Config);
void config mode(spi mode mode);
void config clk pol(spi clk pol clk pol);
void config clk phase(spi clk phase clk phase);
void config rate(spi rate rate);
void config RW(spi rw rw);
void config address(unsigned char addr);
void enable(spi enable enable);
bool send address();
int read address();
bool write char(unsigned char c);
bool read char(unsigned char ∗c);
void enableCS(bool value);
static bool check Device(unsigned char CS);
private:
spi mode mode;
spi rw rw;
unsigned char address;
unsigned char chip select;
enum {max spi devices = 7};static unsigned char devices[];
106
Capıtulo 4. Implementacao do Sistema
static unsigned char num devices;
};
Listagem 4.20: Declaracao da classe Spi8051
A estrutura que permite fazer a configuracao do driver e composta por sete ele-
mentos, que permitem configurar o modo, a linha de selecao, a taxa de transferencia,
o endereco, a operacao, e a polaridade e fase do sinal de relogio.
A implementacao do construtor que permite a configuracao do dispositivo SPI e
apresentado na listagem 4.21. Este recebe como parametro o apontador da estrutura
de configuracao. As primeiras tres linhas de codigo permitem inicializar os atributos
intrınsecos ao objeto com os respetivos parametros da estrutura e configuracao. De-
pois disso, e feita a configuracao no hardware atraves do registo de controlo SPCON.
As ultimas tres linhas de codigo permitem especificar a linha de selecao e adiciona-la
aos atributos da classe.
Spi8051::Spi8051(spi8051 Config ∗ Config)
{Address = Config−>addr;
RW = Config−>rw;
Mode = Config−>mode;
SPCON|= Config−>mode | Config−>rate |Config−>clk pol | Config−>clk phase;
Chip Select = (1<<Config−>cs);
Devices[Num Devices] = Chip Select;
Num Devices++;
}
Listagem 4.21: Construtor da classe Spi8051 com configuracao
O metodo da listagem 4.22 implementa a rececao de um byte de dados. Para
receber a informacao e necessario limpar o registo de status e de dados, e esperar
que esse registo sinalize a finalizacao da transferencia ou a ocorrencia de algum erro.
Caso os dados sejam recebidos corretamente, estes sao lidos do registo SPDAT e a
funcao retorna true (!0). Caso contrario a funcao retorna false (0).
bool spi8051::read char(unsigned char ∗ c)
{SPSTA=reset; //Clear
SPDAT=reset; //Data
do
{}while(SPSTA != data t complete && SPSTA != write collision && SPSTA !=
ss slave error && SPSTA != mode fault);
if(SPSTA == data t complete)
{∗ c = SPDAT;
107
4.2. Upgrade do ADEOS
return true;
}else
{return false;
}}
Listagem 4.22: Metodos read char da classe Spi8051
A implementacao dos restantes metodos pode ser consultada no codigo fonte do
driver SPI desenvolvido.
4.2.3 Upgrade: escalonador power-aware
Nos ultimos anos, o consumo de energia tem sido uma das principais metricas no
projeto e concepcao de dispositivos digitais, devido ao aumento crescente na procura
de sistemas portateis como telemoveis, tablets, maquinas fotograficas e dispositivos
medicos, onde se pretende minimizar o consumo de energia e simultaneamente maxi-
mizar a performance e a complexidade das funcionalidades. O design destes sistemas
requer obviamente o uso de processadores reprogramaveis (microcontroladores, mi-
croprocessadores, DSPs), que funcionam como o nucleo do sistema. Assim sendo, o
constante aumento de funcionalidades dos sistemas tende a ser realizado por software,
que e sustentado pela elevada performance dos processadores mais modernos. Por
outras palavras, existe um conflito no desenho e concepcao destes sistemas: como
sistemas portateis, estes devem ser desenhados para maximizar a duracao da bateria;
mas, como dispositivos inteligentes, estes necessitam de processadores com elevada
capacidade de processamento (que consomem mais energia que os que sao usados em
dispositivos simplistas), o que se traduz numa reducao do tempo util da bateria.
Reconhecendo a necessidade de reducao do consumo de energia nos processadores
destes dispositivos modernos, a comunidade cientıfica propos um conjunto de solucoes
a nıvel de hardware e software. A nıvel de software, os metodos propostos podem
ser classificados em duas categorias: (i) tecnicas de compilacao power-aware; (ii)
tecnicas de gestao do consumo de energia atraves do sistema operativo. A segunda
abordagem tem sido mais explorada, devido ao reconhecimento da importancia dos
sistemas operativos na gestao do consumo dos componentes do sistema.
E neste sentido que surge o escalonador power-aware. Um escalonador power-
aware e um escalonador que procura tirar partido das funcionalidades dos processa-
108
Capıtulo 4. Implementacao do Sistema
dores mais modernos, de modo a minimizar o consumo de energia (dos processadores),
todavia sem comprometer a execucao das aplicacoes. Por outras palavras, um esca-
lonador power-aware implementa ou modifica uma estrategia de escalonamento com
base no facto dos processadores mais modernos disponibilizarem diferentes modos
de operacao, bem como frequencia e tensao de operacao variaveis. Resumindo, estas
estrategias de escalonamento so sao implementaveis caso os processadores disponham
desses recursos.
Como foi mencionado no inıcio do documento, a presente dissertacao e apenas uma
fracao de um trabalho conjunto de hardware-software co-design, que inclui tambem o
desenvolvimento de um microcontrolador de baixo consumo customizavel. O micro-
controlador sera implementado em FPGA, daı que apenas dara suporte a frequencias
de operacao diferentes. Assim sendo, o estrategia de escalonamento a implementar
tera de explorar apenas essa caracterıstica para minimizar o consumo do microcon-
trolador, visto que a variacao da tensao apenas e possıvel de implementar em ASIC.
Algoritmo de escalonamento power-aware
Dos inumeros trabalhos desenvolvidos na area [72, 73, 74, 75, 76], o autor reco-
nheceu especial interesse ao trabalho desenvolvido por Pillai e Shin [75]. O trabalho
desenvolvido pelos investigadores distingue-se dos demais, pois os metodos de reducao
de energia implementados garantem as deadlines das tarefas, daı poderem ser apli-
cados em sistemas de tempo-real. Os autores exploram as alteracoes necessarias a
aplicar a escalonadores usados em sistemas operativos de tempo-real, de modo a con-
seguir reduzir o consumo energetico, sem porventura comprometer as deadlines das
tarefas.
Os metodos implementados sao baseados nos algoritmos DVS (Dynamic Voltage
Scaling), que diminuem a tensao de operacao e a frequencia do processador nos mo-
mentos em que a carga de processamento e baixa. Neste caso, os investigadores
exploram apenas a variacao da frequencia nos metodos implementados, daı a especial
atencao do autor da dissertacao para este trabalho. Os tres metodos implementa-
dos - (i) statically-scaled, (ii) cycle-conversing e (iii) look-ahead - modificam duas
estrategias de escalonamento de tempo-real: rate-monotonic e earliest deadline first.
O primeiro metodo (statically-scaled) e estatico e consiste na reducao da frequencia
para um valor que garanta as deadlines de um conjunto de tarefas. Para selecio-
nar a frequencia apropriada e calculado um fator baseado na frequencia maxima de
109
4.2. Upgrade do ADEOS
operacao e a frequencia discreta selecionada. A frequencia mınima e aceite com base
na menor frequencia discreta que garanta a deadline das tarefas. A garantia da dea-
dline e testada com base no perıodo e o pior tempo de execucao (WCET - worst case
execution time) de cada tarefa. Este metodo nao explora completamente a reducao
da frequencia, pois ignora os casos nos quais a tarefa executa menos que o seu WCET.
Apesar de nao ser o mais agressivo e sem duvida o mais facil de implementar, pois
os calculos sao realizados estaticamente.
Algoritmo 5 Look-Ahead DVS para o escalonador EDF
select frequency(x):lowest freq. fi ∈ {f1, ... ,fm|f1< ... <fm}such that x ≤ fi/fm
upon task release(Ti):set c lefti = Ci;defer();
upon task completation(Ti):set c lefti = 0;defer();
during task execution(Ti):decrement c lefti;
defer():set U = C1/P1 + ... + Cn/Pn;set s = 0;for i = 1 to n, Ti ∈ {T1, ... ,Tn|D1≥ ... ≥Dn};
set U = U - Ci/Pi;set x = max(0 , c lefti - (1-U)(Di-Dn));set U = U + (c lefti-x)/(Di-Dn);set s = s + x;
endforselect frequency (s/(Dn - current time));
Por sua vez, o metodo cycle-conserving, contrariamente ao metodo estatico, pro-
cura aproveitar os ciclos que sobram das execucoes das tarefas anteriores para execu-
tar as proximas tarefas a velocidades mais baixas, isto e, com frequencias mais baixas.
Para isso, cada vez que uma tarefa termina ou e suspensa, o escalonador recalcula
a utilizacao do sistema. Este metodo e mais agressivo e mais difıcil de implementar
110
Capıtulo 4. Implementacao do Sistema
que o anterior, pois o calculo da utilizacao do processador e feito dinamicamente.
O ultimo metodo, look-ahead, ao contrario dos outros metodos, comeca a execucao
das tarefas a baixa frequencia e apenas aumenta a frequencia se precisar de garantir
as deadlines. Este metodo aproveita da melhor forma a existencia de ciclos mortos,
resultantes da execucao da tarefa em menos tempo que o WCET. De todos este e o
metodo mais agressivo, e o mais difıcil de implementar. Em contrapartida, e o que
apresenta melhores resultados, segundo Pillai e Shin, conseguindo atingir reducoes de
consumo na ordem dos 66% quando comparado com a execucao plena do algoritmo
EDF.
Com base nesses resultados, o autor ira implementar o metodo look-ahead (algo-
ritmo 5). No pseudo-codigo fi representa a frequencia selecionada entre as frequencias
discretas disponıveis, fm a frequencia maxima, Ti representa a tarefai da lista de ta-
refas, Ci o WCTE da tarefai, c lefti o tempo que falta para atingir o WCET da
tarefai, Pi o perıodo da tarefai, Di a deadline da tarefai, U a utilizacao do sistema,
e s o numero total de ciclos mınimo que e necessario executar antes da deadline mais
proxima.
Implementacao do escalonador power-aware
A implementacao do metodo look-ahead com escalonamento EDF para o sistema
operativo ADEOS, consistiu basicamente na reimplementacao da classe Sched, Task e
TaskList. Estas classes foram implementadas com uma nova designacao (Sched PW,
Task PW e TaskList PW) ja a pensar na customizacao e configuracao do sistema ope-
rativo.
Na definicao da classe Task PW (listagem 4.23), foram acrescentadas alguns atri-
butos, cuja informacao se torna essencial para o metodo look-ahead, nomeadamente o
perıodo, deadline e WCET da tarefa. No construtor e tambem calculada a utilizacao
global do sistema (U) para determinar se o conjunto de tarefas e escalonavel ou nao.
Assim sendo, antes de adicionar a tarefa, e verificado se o e possıvel fazer. Caso nao
o seja, o construtor da classe retorna sem adicionar a tarefa a lista de tarefas.
Task PW::Task PW(void (∗function)(), int stackSize, Deadline Task deadline, WCET Task wcet)
{stackSize /= sizeof(int);
//Power−Aware: global Utilization test
int tempUtili = os.GlobUtiliz + ((float)Task wcet/(float)Task deadline )∗100;
if(tempUtili >=100) return; //If U > 100% return and dont insert this task
else
111
4.2. Upgrade do ADEOS
{if(!(function == idle)) //If Not idle, update GlobalUtilization
{os.GlobUtiliz = tempUtili;
}}
enterCS(); ////// Critical Section Begin
// Initialize the task−specific data.
...
period = Task deadline;//PowerAware
deadline = Task deadline;//PowerAware
wcet = Task wcet; //PowerAware
cleft = wcet; //PowerAware
...
exitCS(); ////// Critical Section End
}
Listagem 4.23: Construtor da classe do escalonador power-aware
Na classe TaskList PW foi necessario reimplementar os metodos de insercao e
remocao das tarefas. Isto porque foi necessario implementar uma lista duplamente
ligada, uma vez que o metodo look-ahead necessita de percorrer a lista de tarefas nos
dois sentidos.
Por sua vez, na classe Sched PW, para alem das modificacoes ao nıvel da classe,
foram tambem modificadas a rotina da interrupcao de overflow do temporizador 1,
bem como o metodo schedule. Alem disso, foram introduzidos os metodos defer e
select freq essenciais para a implementacao do algoritmo look-ahead especificado
anteriormente.
Na rotina de interrupcao de overflow do temporizador 1 (listagem 4.24), res-
ponsavel pelo clock-tick do escalonador, sao entao atualizadas as variaveis responsaveis
pelo tempo total decorrido no sistema (os.currentTime), assim como o tempo que
falta para a conclusao do WCET da tarefa (os.pRunningTask->cleft).
#pragma vector = TF1 int
interrupt void Sched::tick(void)
{enterCS();
recharge sched tick(˜os.cycles tick);
os.pRunningTask−>cleft−=os.tick;//Power Aware
os.currentTime+=os.tick;//Power Aware
os.schedule();
exitCS();
}
Listagem 4.24: Alteracoes na ISR do clock-tick do escalonador
112
Capıtulo 4. Implementacao do Sistema
No metodo schedule, e entao introduzida a chamada do metodo defer, res-
ponsavel por determinar se e possıvel baixar a frequencia de relogio do CPU sem
comprometer as deadlines das tarefas. Alem disso, caso a proxima tarefa a entrar
em execucao seja a idle, entao a frequencia e baixada para o mınimo e os atributos
do escalonador que permitem gerir as temporizacoes dinamicamente sao atualizados
(os.F cpu e os.cycles tick). A implementacao da funcao defer e apresentada na
listagem 4.25. Aqui nao ha muito a explicar porque consiste na traducao fidedigna
do pseudo-codigo apresentado no algoritmo look-ahead.
void Sched PW::defer(void)
{...
// Look−Ahead Algorithm
do
{Utilization= Utilization + (((float)pPrev−>wcet/(float)pPrev−>period) ∗ 100);
pPrev = pPrev−>pNext;
}while (pPrev != &os.idleTask && readyList.pTop != &os.idleTask);
pPrev = pPrev−>pPrevious;//Idle−>pPrevious
s = 0;
while(pPrev!= NULL)
{long temp = (((float)pPrev−>wcet/(float)pPrev−>period) ∗ 100);
Utilization = Utilization − temp;
temp = (long)(pPrev−>cleft − (long)(100 − Utilization)∗(long)(pPrev−>deadline − readyList.pTop−>deadline));
if(temp<0) x = 0;
else x= temp;
Utilization = Utilization + (pPrev−>cleft − x)/
(pPrev−>deadline − readyList.pTop−>deadline);
s=s+x;
pPrev = pPrev−>pPrevious;
}SelectFreq(((unsigned int)(s∗16)/(unsigned int)(readyList.pTop−>deadline − currentTime))∗100);
}
Listagem 4.25: Implementacao do metodo defer
Finalmente, a tabela 4.4 apresenta o codigo C++ e assembly da implementacao
do metodo de seleccao de frequencia, invocado no final do metodo defer. Esta funcao
para alem de invocar a funcao implementada em assembly de selecao de frequencia,
atualiza tambem os atributos que permitem gerir a temporizacao dinamicamente. A
implementacao em assembly atualiza os registos do microcontrolador customizavel
dedicados ao escalonador.
113
4.3. Refactoring do ADEOS
Tabela 4.4: Implementacao C++ e assembly da selecao da frequencia
Metodo C++ CDP (8051)
void Sched PW::SelectFreq(unsigned int x){
os.F cpu = F max>>select freq(x);
os.cycles tick = (((F cpu)∗os.tick)/12);}
select freq:CODE;HWFSH = ((unsigned char)x >> 8) | 0xC0;MOV A,R5ORL A,#0xC0MOV HWFSH,A;HWFSL = (unsigned char)x;MOV A,R4MOV HWFSL,Aflag freq:;while ( HWFSH & (1<<7))MOV A,HWFSHMOV C,A.7JC flag freq; CKRL = ((HWFSH >> 3) & 0x7)MOV A,HWFSHRR ARR ARR AANL A,#0x07MOV CKRL,A;return CKRL in R1MOV R1,CKRLRET
4.3 Refactoring do ADEOS
A terceira e ultima parte do desenvolvimento do sistema, e a fracao fundamental
do problema da dissertacao. Basicamente, consiste na reestruturacao ou refactoring
do sistema operativo ADEOS, aplicando a tecnica de programacao template metapro-
gramming. Desta forma, e possıvel gerir a variabilidade das funcionalidades e permitir
a customizacao do sistema operativo, sem comprometer o desempenho e introduzir
overhead de memoria.
4.3.1 Diagrama de Funcionalidades
O diagrama de funcionalidades e uma representacao visual do modelo de funcio-
nalidades. Este modelo surgiu com o conceito da orientacao a funcionalidades [77],
permitindo a gestao das funcionalidades comuns e variaveis de um sistema em linha de
producao, sem ter em conta o mecanismo de implementacao a utilizar. O diagrama de
funcionalidades representa um conjunto de funcionalidades, organizadas hierarquica-
mente, onde o nodo da raiz representa o conceito do sistema e os nodos descendentes
as funcionalidades [7]. Este contem quatro tipos possıveis de funcionalidades:
• Funcionalidades obrigatorias: O sistema deve ter obrigatoriamente certas
114
Capıtulo 4. Implementacao do Sistema
funcionalidades. Estas funcionalidades sao representadas com um cırculo pre-
enchido a preto.
• Funcionalidades opcionais: O sistema pode, ou nao, ter certas funcionali-
dades. Estas funcionalidades sao representadas com um cırculo sem preenchi-
mento.
• Funcionalidades alternativas: O sistema apenas tem uma funcionalidade
em cada instante de tempo. Estas funcionalidades sao representadas com um
arco sem preenchimento.
• Funcionalidades combinadas: O sistema pode ter uma combinacao de fun-
cionalidades. Estas funcionalidades sao representadas com um arco preenchido
a preto.
Figura 4.11: Diagrama de funcionalidades do ADEOS
A figura 4.11 apresenta o diagrama de funcionalidades do sistema operativo ADEOS.
O no raiz representa o conceito (ADEOS) que e composto por quatro funcionalidades:
Task, IPC, Driver e Scheduler. As funcionalidades apresentadas correspondem aos
componentes do sistema operativo. A funcionalidade Task tem cardinalidade [1..*], o
115
4.3. Refactoring do ADEOS
que significa que o ADEOS tem que ser composto no mınimo por uma tarefa (idle).
No entanto, pode ter outras tarefas, consoante as necessidades do utilizador. A funci-
onalidade Scheduler tem cardinalidade [1], ou seja, e obrigatorio a presenca de um, e
apenas um, escalonador no nucleo do sistema. As funcionalidades IPC e Driver tem
cardinalidade [0..*], que indica que estas funcionalidades sao opcionais. Por exemplo,
so e necessario ter a funcionalidade Driver caso seja necessario comunicar com algum
periferico. Da mesma forma, so e necessario a funcionalidade IPC, caso se pretenda
ter comunicacao entre as tarefas.
A funcionalidade Task tem variabilidade. Por exemplo, uma tarefa pode ser ca-
racterizada pela prioridade, caso a intencao seja utilizar a estrategia de escalonamento
(highest priority first), ou entao pela sua deadline, caso se pretenda utilizar o algo-
ritmo earliest deadline first. Neste sentido, e possıvel ter tantos gestores de tarefas
quantos os desejados, todavia mutuamente exclusivos. Apenas um deles pode ser
usado em cada configuracao.
A funcionalidade IPC e constituıda por tantas funcionalidades cumulativas quan-
tas as pretendidas. Como exemplo apresenta-se os mecanismos semaphores e mutex,
mas tambem podem ser utilizados message queue e shared memory. Estas funcionali-
dades sao cumulativas, porque podem ser utilizadas todas ao mesmo tempo, de forma
combinada, ou ate podem nao ser utilizadas. Cada uma das funcionalidades tambem
apresenta variabilidade. No entanto as subfuncionalidades sao exclusivas. Quer isto
dizer que o sistema operativo ADEOS pode utilizar, por exemplo, o mecanismo de
semaphore e mutex ao mesmo tempo, no entanto so pode utilizar uma implementacao
de cada mecanismo em cada configuracao.
A funcionalidade Driver e semelhante a funcionalidade anterior. Desta forma,
podem existir tantos drivers quantos os perifericos com quem se pretende comunicar.
Porta-serie, I2C, bem como SPI, PWM, sao tudo funcionalidades cumulativas que
podem ser utilizadas ao mesmo tempo, mas com implementacoes exclusivas. Ou
seja, as funcionalidades sao cumulativas, no entanto a variabilidade dentro delas
(subfuncionalidades) e exclusiva.
Finalmente, a funcionalidade Scheduler e semelhante a funcionalidade Task. Isto
significa que o sistema operativo pode ter diferentes implementacoes do escalonador,
no entanto mutuamente exclusivas.
116
Capıtulo 4. Implementacao do Sistema
4.3.2 Estrategia de Gestao da Variabilidade
Conforme foi visto na seccao 3.3 a tecnica de template metaprogramming nao e
intuitiva e a sintaxe e por vezes um pouco isoterica. Neste sentido, para gerir a
variabilidade do sistema operativo, e consequentemente as diversas funcionalidades,
e necessario definir uma metodologia que sistematize a restruturacao de cada uma.
Assim, como foi visto anteriormente, a variabilidade dentro de cada funcionalidade
especifica e mutuamente exclusiva, o que significa que, por exemplo, se for definido o
driver usart1 na configuracao, implica que nao pode ser usado mais nenhum. Assim
sendo, a metodologia de gestao da variabilidade de uma funcionalidade com template
metaprogramming completa-se em tres etapas.
Tabela 4.5: Classes especificas da funcionalidade example
example1.h example2.hclass example1{
public:example1() {} //Constructor˜example1() {} //Destructorvoid func();void set attr(unsigned char);unsigned char get attr();
private:unsigned char attr 1;
};
//Method examplevoid example1::func(){}
//Attribute set examplevoid example1::set attr(unsigned char attr){
attr 1 = attr;}
//Attribute get exampleunsigned char example1::get attr(){
return attr 1;}
class example2{
public:example2() {} //Constructor˜example2() {} //Destructorvoid func();void set attr(unsigned int);unsigned int get attr();
private:unsigned int attr 2;
};
//Method examplevoid example2::func(){}
//Attribute set examplevoid example2::set attr(unsigned int attr){
attr 2 = attr;}
//Attribute get exampleunsigned int example2::get attr(){
return attr 2;}
A primeira etapa consiste na divisao de cada uma das implementacoes da funcio-
nalidade em tantos ficheiros cabecalhos quantas as implementacoes. Supondo que o
sistema operativo inclui a funcionalidade example, com variabilidade exclusiva a dois
nıveis, isto e, ou e utilizada a implementacao example1 ou entao a implementacao
example2. Assim, a primeira etapa consiste entao em definir cada uma das clas-
117
4.3. Refactoring do ADEOS
ses que implementa cada uma das funcionalidades especıficas, em diferentes ficheiros
cabecalho. A tabela 4.5 apresenta a definicao de cada uma dessas hipoteticas classes.
Estas classes servem apenas para explicar a estrategia que deve ser utilizada, nao im-
plementando portanto qualquer funcionalidade. Para alem do construtor e destrutor
da classe, implementam um metodo generico, bem como os metodos set e get de
um atributo. Importa salientar que os atributos tem tipos diferentes, para ilustrar a
possibilidade de o utilizar.
Por sua vez, na segunda etapa e definido um ficheiro cabecalho (* tmp.h) onde
e feita entao a implementacao da funcionalidade com template metaprogramming.
Basicamente, consiste em definir o prototipo da template e a funcionalidade especifica
a utilizar. Depois disso e implementada a template generica, bem como cada uma
das templates especıficas (example1 e example2 ). O codigo 4.26 apresenta o ficheiro
example tmp.h, que corresponde a implementacao com template metaprogramming da
funcionalidade example.
#include ”example1.h”
#include ”example2.h”
template <typename exampleType> class exampleManager; //Specify Template Prototype
typedef example1 example; //Specify Specific Template
typedef exampleManager<example> Example;
example example object; //Define object
//Generic Template
template <>
class exampleManager <exampleGeneric>
{public:
inline static void func() {return; /∗error∗/}inline static void set attr() {return; /∗error∗/}inline static void get attr() {return; /∗error∗/}
};
//Specific Template 1
template <>
class exampleManager <example1>
{public:
inline static void func()
{example object.func();
}inline static void set attr(unsigned char attr)
{example object.set attr(attr);
}
118
Capıtulo 4. Implementacao do Sistema
inline static unsigned char get attr()
{return example object.get attr();
}};
//Specific Template 2
template <>
class exampleManager <example2>
{public:
inline static void func()
{example object.func();
}inline static void set attr(unsigned int attr)
{example object.set attr(attr);
}inline static unsigned int get attr()
{return example object.get attr();
}};
Listagem 4.26: Ficheiro example tmp.h
Finalmente, a ultima etapa consiste em utilizar a funcionalidade com a abstracao
necessaria independentemente da funcionalidade especifica. Quer isto dizer, que o
codigo produzido que utiliza a funcionalidade nao deve ser diferente independente-
mente da funcionalidade especificada na configuracao pretendida. Seguindo o exem-
plo da funcionalidade example, o codigo da listagem 4.27 permite aceder tanto aos
metodos da classe example1 como da classe example2. A escolha e feita exclusiva-
mente no ficheiro example tmp.h na linha typedef exampleX example. Substituindo
X por 1 ou por 2 e possıvel definir a configuracao pretendida. Todavia, o codigo que
usa a funcionalidade e exatamente o mesmo.
...
int var = 0;
Example ex;
ex.func();
ex.set attr(0x12);
var = ex.get attr();
...
Listagem 4.27: Transparencia no codigo de acesso a funcionalidade example
Esta metodologia sistematiza entao a estrategia de implementacao das diversas
119
4.3. Refactoring do ADEOS
funcionalidades com TMP. No exemplo anterior, apenas foi tratada a variabilidade
a dois nıveis. No entanto, caso a variabilidade fosse a tres nıveis, a metodologia
era a mesma. Simplesmente bastava definir mais um ficheiro cabecalho (example3.h)
com a implementacao da classe pretendida, e no ficheiro example tmp.h especificar a
template especifica para esse caso. O codigo que usa a funcionalidade permanece o
mesmo, e e otimizado para a configuracao escolhida no ficheiro example tmp.h, nao
incluindo portanto o codigo das implementacoes excluıdas.
4.3.3 Reestruturacao do ADEOS
Na reestruturacao do sistema operativo ADEOS para permitir a gestao da vari-
abilidade das funcionalidades, o autor centra-se mais em implementar o suporte a
variabilidade do que a propria variabilidade. Nesse sentido, e normal que a variabi-
lidade dentro de uma funcionalidade apareca replicada, pois o importante e aplicar
a metodologia explicada anteriormente a cada uma das funcionalidades do ADEOS.
Sao reestruturadas as funcionalidades Sched, Task, Mutex, bem como todos os device
drivers desenvolvidos. No entanto, o autor decidiu explicar apenas duas: a gestao
do escalonador e a gestao das tarefas. Isto porque apesar destas funcionalidades
seguirem todas a mesma estrategia, apresentam pequenas variantes. Todas as ou-
tras funcionalidades que nao sao apresentadas, sao reestruturadas de forma analoga,
podendo os detalhes da implementacao serem consultados no codigo do sistema ope-
rativo configuravel.
Escalonador com Template Metaprogramming
Tabela 4.6: Declaracao da classe template da funcionalidade Sched
sched tmp.h config adeos.h#include ”sched1.h”#include ”sched2.h”
template <typename SchedType> class schedManager;typedef schedManager<sched> Sched;sched sched obj;
...
...
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ sched tmp ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/#define sched Sched1
...
A reestruturacao do escalonador para permitir a sua customizacao consiste exata-
mente na aplicacao da metodologia explicada anteriormente. Isto porque no sistema
120
Capıtulo 4. Implementacao do Sistema
Tabela 4.7: Definicao das templates generica e especificas da funcionalidade Sched
Template Codigo Template
Generic
template <>class schedManager <SchedGeneric>{
public:inline static void start() {return; /∗error∗/}inline static void schedule() {return; /∗error∗/}inline static void add Task(Task ∗ pTask) {return; /∗error∗/}inline static void enterIsr() {return; /∗error∗/}inline static void exitIsr() {return; /∗error∗/}inline static void get pRunningTask() {return; /∗error∗/}inline static void get pIdleTask() {return; /∗error∗/}inline static void get pReadyList() {return; /∗error∗/}
};
Sched1
template <>class schedManager <Sched1>{
public:inline static void start() {sched obj.start();}inline static void schedule() {sched obj.schedule();}inline static void add Task(Task ∗ pTask) {sched obj.add Task(pTask);}inline static void enterIsr() {sched obj.enterIsr();}inline static void exitIsr() {sched obj.exitIsr();}inline static Task ∗ get pRunningTask() {return sched obj.pRunningTask;}inline static Task ∗ get pIdleTask() {return &sched obj.idleTask;}inline static ReadyList ∗ get pReadyList() {return &sched obj.readyList;}
};
Sched2
template <>class schedManager <Sched2>{
public:inline static void start() {sched obj.start();}inline static void schedule() {sched obj.schedule();}inline static void add Task(Task ∗ pTask) {sched obj.add Task(pTask);}inline static void enterIsr() {sched obj.enterIsr();}inline static void exitIsr() {sched obj.exitIsr();}inline static Task ∗ get pRunningTask() {return sched obj.pRunningTask;}inline static Task ∗ get pIdleTask() {return &sched obj.idleTask;}inline static ReadyList ∗ get pReadyList() {return &sched obj.readyList;}
};
operativo existe apenas uma instancia dessa funcionalidade. Assim sendo, em pri-
meiro lugar cada uma das classes que implementa o escalonador deve ser definida
num ficheiro cabecalho. Como o autor implementa duas estrategias de escalona-
mento diferentes, havera dois ficheiros cabecalhos. No entanto, caso surjam novas
implementacoes, sera necessario criar tantos ficheiros cabecalhos quantas as novas
implementacoes. Depois disso, e criado o ficheiro cabecalho sched tmp.h, responsavel
por fazer a gestao da funcionalidade estaticamente com template metaprogramming.
No inicio do ficheiro e feita a inclusao a todos os ficheiros cabecalhos que implemen-
tam os algoritmos de escalonamento, e e definido o prototipo da template. Depois
disso, simplifica-se a sintaxe isoterica das templates, e e definida uma instancia de
121
4.3. Refactoring do ADEOS
um objeto do tipo escalonador. No ficheiro cabecalho config adeos.h configura-se o
algoritmo especifico de escalonamento a utilizar. A tabela 4.6 apresenta o codigo do
que foi descrito.
O proximo passo consiste na especificacao da template generica, e de cada uma das
templates especıficas para cada algoritmo de escalonamento. Basicamente, consiste
em definir uma classe template que tem metodos comuns a toda a funcionalidade
do escalonador, mas que sao substituıdos pelos metodos especıficos da estrategia de
escalonamento configurada. O facto dos metodos serem inline, significa que no local
onde sao utilizados sao substituıdos pelo codigo da implementacao, evitando um salto
adicional. A tabela 4.7 resume essa implementacao.
Desta forma, o codigo transparente que gere a funcionalidade e sempre o mesmo,
pois todas as classes templates tem a mesma especificacao. No entanto, a imple-
mentacao dos metodos de cada template e que e diferente. Contudo, como o codigo
generico e substituıdo apenas pelo codigo especıfico da template configurada, garante-
se assim que apenas a funcionalidade pretendida e incorporada, gerando codigo oti-
mizado de acordo com a configuracao. A listagem 4.28 ilustra como o codigo da
funcionalidade Sched permanece transparente, apesar da inclusao da variabilidade
na funcionalidade.
Sched os;
...
void main(void)
{os.add Task(os.get pIdleTask());
os.add Task(&taskA);
os.add Task(&taskB);
os.start();
}
Listagem 4.28: Transparencia no codigo de acesso a funcionalidade Sched
Tarefas com Template Metaprogramming
O refactoring do codigo relativo a funcionalidade Task segue a mesma metodologia
ate agora apresentada, no entanto com umas ligeiras modificacoes. Isto porque a
estrategia apresentada funciona corretamente quando existe apenas um objeto da
classe especıfica da funcionalidade. No entanto, no caso da funcionalidade Task isso
nao acontece. Primeiro porque o sistema operativo pode executar varias tarefas
(varias instancias da classe Taskx), e segundo porque para gerir as tarefas este e
122
Capıtulo 4. Implementacao do Sistema
composto por varias listas de tarefas (varias instancias da classe TaskListx). Uma
lista de tarefas e responsavel por reter as tarefas prontas a executar (readyList),
enquanto outras listas estao associadas a cada mutex responsavel por remover a
tarefa da lista de tarefas e coloca-la na waitList. Como cada objeto do tipo mutex
tem associado uma waitList, entao havera tantas listas quantos os mutex.
Neste sentido, e necessario modificar a metodologia ate agora utilizada, de modo
a suportar diferentes e multiplas instancias da mesma classe. No caso da gestao
da lista de tarefas, e necessario um objeto do tipo readyList e tantos objetos do tipo
waitList quantos os mutex (tarefas) utilizados. A solucao encontrada passa entao por
utilizar um meta-argumento na definicao da template da classe. Este meta-argumento
permite distinguir uma readyList duma waitList. Por sua vez, para distinguir cada
uma das waitList, e utilizado um atributo (id) na chamada dos metodos associados.
Este atributo e intrınseco a cada mutex, e incrementado a cada nova instanciacao.
Tabela 4.8: Declaracao da classe template da funcionalidade Task
task tmp.h config adeos.h#include ”task1.h”#include ”task2.h”
template <unsigned char n, typename taskType> class taskManager;typedef taskManager<0,TaskList> ReadyList;typedef taskManager<1,TaskList> WaitList;TaskList readyList, waitList[num waitList];
...
...
/∗∗∗∗∗∗∗∗ task tmp ∗∗∗∗∗∗∗∗/#define num waitList 3#define Task Task1#define TaskList TaskList1
...
Explicando concretamente a reestruturacao do codigo da funcionalidade Task, a
primeira parte consiste entao na definicao de tantos ficheiros cabecalho tantas as
especificacoes. O autor implementa a variabilidade a dois nıveis, daı haver dois fi-
cheiros cabecalhos (task1.h e task2.h). Depois disso, e criado o ficheiro cabecalho
task tmp.h, responsavel por fazer a gestao da funcionalidade estaticamente com tem-
plate metaprogramming. No inıcio do ficheiro (tabela 4.8) e feita a inclusao a todos
os ficheiros cabecalhos, e e definido o prototipo da template. De notar a utilizacao do
meta-argumento n, do tipo unsigned char, que permite especificar 256 variantes da
mesma lista. A utilizacao da keyword typedef no codigo permite simplificar a sintaxe
na designacao atribuıda a ReadyList e WaitList. A ultima linha de codigo define um
objeto do tipo ReadyList e tantos objetos do tipo WaitList quantos os especificados
no ficheiro de configuracao. Nesse ficheiro tambem se define qual a funcionalidade
especifica das tarefas a utilizar.
123
4.3. Refactoring do ADEOS
De seguida sao especificadas as templates generica e especıficas de cada uma das
implementacoes (tabela 4.9). Basicamente, consiste em definir uma classe template
que tem metodos comuns a toda a funcionalidade das tarefas, mas que sao subs-
tituıdos pelos metodos especıficos da classe configurada (no ficheiro config adeos.h).
Aqui importa justificar o porque de implementar metodos overloading. Esta foi a
forma mais simples de implementar a existencia de diferentes objetos. Como existe
apenas uma lista ReadyList, entao nao e preciso identificar qual delas e. Daı que os
metodos sejam implementados sem a utilizacao do argumento id. Por outro lado,
como existem varios objetos do tipo WaitList, entao e necessario implementar os
mesmos metodos, mas com o argumento extra de identificacao da lista. Daı ser uti-
lizado o argumento id. Tal como foi referido este argumento utilizado no metodo
e um atributo intrınseco de cada objeto mutex, que permite identificar no array de
objetos WaitList, a respetiva lista associada ao mutex. Por isso e comum utilizar
waitList[id ] na implementacao dos metodos da template.
Com esta abordagem, o codigo do sistema operativo que gere esta funcionalidade
permanece praticamente o mesmo (listagem 4.29), isto e, semelhante ao codigo do
sistema operativo sem variabilidade, apenas nos metodos da waitList e necessario
especificar o id do mutex. Alem disso o codigo e suficientemente transparente e
abstracto para que alterando a configuracao da funcionalidade, nao seja necessario
modificar esse codigo que a gere.
...
ReadyList readyList;
readyList.insert(pTask);
readyList.set pTop(NULL);
readyList.get pTop();
...
WaitList waitingList;
waitingList.insert(pCallingTask,this−>id);
waitingList.set pTop(NULL,this−>id);
waitingList.get pTop(this−>id)
...
Listagem 4.29: Transparencia no codigo de acesso a funcionalidade Task
124
Capıtulo 4. Implementacao do Sistema
Tabela 4.9: Definicao das templates generica e especificas da funcionalidade Task
Template Codigo Template
Generic
template <unsigned char n>class taskManager <n, TaskListGeneric>{
public:inline static void insert(Task ∗ pTask) {return; /∗error∗/}inline static void insert(Task ∗ pTask, unsigned char id ) {return; /∗error∗/}inline static void remove(Task ∗ pTask) {return; /∗error∗/}inline static void remove(Task ∗ pTask, unsigned char id ) {return; /∗error∗/}inline static void get pTop() {return; /∗error∗/}inline static void get pTop(unsigned char id ) {return; /∗error∗/}inline static void set pTop(Task ∗ pTask) {return; /∗error∗/}inline static void set pTop(Task ∗ pTask, unsigned char id ) {return; /∗error∗/}
};
Task1
template <>class taskManager <0, TaskList1>{
public:inline static void insert(Task ∗ pTask) { readyList.insert(pTask); }inline static Task ∗ remove(Task ∗ pTask) { return readyList.remove(pTask); }inline static Task ∗ get pTop() { return readyList.pTop; }inline static void set pTop(Task ∗ pTask) { readyList.pTop = pTask; }
};template <>class taskManager <1, TaskList1>{
public:inline static void insert(Task ∗ pTask, unsigned char id ) { waitList[id ].insert(pTask); }inline static Task ∗ remove(Task ∗ pTask, unsigned char id )
{ return waitList[id ].remove(pTask); }inline static Task ∗ get pTop(unsigned char id ) { return waitList[id ].pTop; }inline static void set pTop(Task ∗ pTask, unsigned char id )
{ waitList[id ].pTop = pTask; }};
Task2
template <>class taskManager <0, TaskList2>{
public:inline static void insert(Task ∗ pTask) { readyList.insert(pTask); }inline static Task ∗ remove(Task ∗ pTask) { return readyList.remove(pTask); }inline static Task ∗ get pTop() { return readyList.pTop; }inline static void set pTop(Task ∗ pTask) { readyList.pTop = pTask; }
};template <>class taskManager <1, TaskList2>{
public:inline static void insert(Task ∗ pTask, unsigned char id ) { waitList[id ].insert(pTask); }inline static Task ∗ remove(Task ∗ pTask, unsigned char id )
{ return waitList[id ].remove(pTask); }inline static Task ∗ get pTop(unsigned char id ) { return waitList[id ].pTop; }inline static void set pTop(Task ∗ pTask, unsigned char id )
{ waitList[id ].pTop = pTask; }};
125
Capıtulo 5
Resultados Experimentais
No capıtulo anterior foi apresentada a implementacao do sistema, comecando pelo
porting do ADEOS para a arquitetura MCS-51, seguindo-se o upgrade e refactoring
do sistema operativo. A reestruturacao do ADEOS para a gestao da variabilidade foi
conseguida utilizando a tecnica de template metaprogramming.
Neste capıtulo, sao apresentados os resultados experimentais dos testes realizados,
numa placa de desenvolvimento com o microcontrolador da famılia 8051 da Atmel,
para avaliar o desempenho e overhead de memoria, bem como as metricas de gestao
do codigo. Foram efetuados dois testes distintos. No primeiro, o sistema operativo
e as diversas funcionalidades foram implementadas de duas formas diferentes: a im-
plementacao na linguagem C++ onde e utilizado template metaprogramming para
gerir da variabilidade; e a implementacao na linguagem C++ onde e utilizado poli-
morfismo dinamico para gerir a variabilidade do sistema operativo. Por sua vez, no
segundo teste, apenas foi averiguado um modulo de device driver. Isto porque para
alem das duas implementacoes em C++, surge uma terceira implementacao em C
utilizando compilacao condicional.
5.1 Ambiente de Testes
Caracterizar o ambiente em que decorreram os testes realizados implica caracte-
rizar essencialmente tres componentes: o hardware onde os testes foram realizados; o
compilador usado para compilar o codigo fonte dos testes realizados; e as ferramentas
de software para avaliacao das metricas em teste.
Para acelerar o desenvolvimento e avaliar o sistema operativo customizavel no
127
5.1. Ambiente de Testes
Tabela 5.1: Caracterısticas de hardware da placa de desenvolvimento 8051DKUSB
Placa de desenvolvimento Caracterısticas
8051DKUSB
Arquitetura: 8051Processador: AT89C51ID2Velocidade CPU: 12MHzRAM: 256-bytesXRAM: 1792-bytesFlash: 64-kbytesEEPROM: 2048-bytes
desempenho e footprint de memoria, sem depender do trabalho de terceiros, os testes
foram realizados na plataforma de hardware 8051DKUSB (figura 5.1). Esta placa
de desenvolvimento, desenvolvida in-house (ESRG), vem equipada com um micro-
controlador AT89C51ID2 da Atmel, alimentacao USB, conector de 44 pinos para
expansao dos quatro portos do microcontrolador, comunicacao serie atraves da porta
USB (FTDI), display de 7-segmentos ligado ao porto 1, e programacao ISP (In-
System Programming) manual ou automatica. A tabela 5.1 resume as caracterısticas
fundamentais do microcontrolador.
Figura 5.1: Placa de desenvolvimento 8051DKUSB
Para compilar o codigo fonte do sistema operativo ADEOS, incluindo o codigo
das tarefas a executar, foi utilizado o compilador C/C++ da IAR para o 8051. Nas
opcoes de compilacao foi definida a opcao de otimizacao None, de modo a obter codigo
maquina sem qualquer otimizacao. Desta forma sera possıvel avaliar de forma mais
fidedigna da influencia do template metprogramming nas metricas de desempenho,
128
Capıtulo 5. Resultados Experimentais
sem grande interferencia do compilador.
Para obter os resultados das metricas pretendidas foram utilizados essencialmente
tres utilitarios. Para obter os resultados relacionados com o desempenho e memoria,
foram utilizados o debugger do IAR Embedded Workbench for 8051 e o Flip da Atmel,
respetivamente. Por sua vez, para obter os resultados relacionados com as metricas
de gestao do codigo foi utilizado o software Understand da Scientific Toolworkss [78].
5.2 Metricas de Teste
Na seccao 2.3 o autor justificou a escolha da tecnica de template metaprogramming
como a solucao adequada para gerir a variabilidade do sistema operativo implemen-
tado com o paradigma da programacao orientada a objetos. Isto porque apesar do
overhead associado a algumas caracterısticas desse paradigma de programacao, a
tecnica de template metaprogramming permite reestruturar o software de forma a ge-
rir a variabilidade do mesmo, sem porventura comprometer o desempenho e memoria
do sistema.
Assim sendo, faz todo sentido que as metricas em teste estejam relacionadas
essencialmente com o tempo de execucao (desempenho) e o tamanho do ficheiro de
codigo (memoria). No entanto, apesar das metricas desempenho e memoria serem
fatores preponderantes no projeto e concepcao de qualquer sistema, a facilidade de
gestao e expansao do codigo tambem desempenha um papel importante. Isto porque
codigo ilegıvel e mal organizado requer um esforco de engenharia superior. Portanto,
para verificar o grau de complexidade inerente a gestao do codigo bem como a sua
expansao sao analisadas as seguintes metricas:
• Linhas de Codigo (LOC): numero de linhas de codigo, excluindo comentarios e
linhas em branco, presentes nos ficheiros de codigo fonte;
• Numero de Classes (NOC): numero de classes presentes nos ficheiros de codigo
fonte.
5.3 Testes Realizados
Como o sistema operativo pode ser configurado de tantas formas quantas as fun-
cionalidades disponıveis, entao a realizacao dos testes e recolha de resultados torna-se
129
5.3. Testes Realizados
uma tarefa complexa. Isto devido ao aumento substancial de configuracoes a cada
introducao de uma nova funcionalidade.
Para simplificar essa tarefa, o autor decidiu realizar um primeiro teste, limitando a
variabilidade de cada funcionalidade a dois nıveis. Por outras palavras, apenas com a
variabilidade a dois nıveis, o sistema permite 32 (variabilidadefuncionalidades = 25) con-
figuracoes. A figura 5.2 ilustra o diagrama de funcionalidades do teste em causa. Para
este teste, o autor implementou o sistema utilizando duas metodologias diferentes: (i)
a implementacao na linguagem C++ onde e utilizado template metaprogramming ; e
(ii) a implementacao na linguagem C++ onde e utilizado polimorfismo dinamico. Isto
para tentar sustentar a premissa de que e possıvel utilizar a programacao orientada
a objetos e template metaprogramming para implementar software customizavel em
sistemas embebidos, pois a maioria das funcionalidades da POO (com excecao do po-
limorfismo dinamico, multipla heranca e abstracao) nao compromete o desempenho
do sistema, e facilita a gestao do codigo.
Figura 5.2: Diagrama de funcionalidades do sistema operativo (teste ao sistemaoperativo)
130
Capıtulo 5. Resultados Experimentais
Tabela 5.2: Configuracao usada no teste ao sistema operativo
Funcionalidade ImplementacaoSched Sched HPFTask Task HPFIPC - Mutex Mutex1Driver - USART USART AT89C51Driver - SPI SPI AT89C51
Contudo, uma vez que o primeiro teste apenas permite fazer uma comparacao
entre duas implementacoes que utilizam programacao orientada a objetos, somente
com isso nao e possıvel perceber concretamente qual o potencial de otimizacao da
tecnica de template metaprogramming, quando comparada com uma implementacao
imperativa como linguagem C. No entanto, implementar todo o sistema operativo
assim como as diversas funcionalidades em linguagem C, seria para o autor uma
tarefa inexequıvel. Por este motivo, o segundo teste centra-se apenas numa funcio-
nalidade de um driver, ou seja, sao comparadas e avaliadas as duas implementacoes
em C++ bem como uma implementacao em C do device driver UART (tambem
com variabilidade a dois nıveis). A implementacao em linguagem C utiliza com-
pilacao condicional. Tambem poderia ser implementada utilizando apontadores para
funcoes, no entanto esta metodologia nao e tao otimizada quanto a anterior. Com
este teste, e entao possıvel estabelecer um ponto de comparacao (embora pequeno)
entre a implementacao C++ TMP e a implementacao C otimizada.
5.3.1 Teste ao Sistema Operativo
Com base no diagrama de funcionalidades da figura 5.2, foi possıvel definir a con-
figuracao do sistema operativo (tabela 5.2) para a realizacao do teste. A configuracao
implementa um sistema operativo baseado em prioridades, e utiliza a implementacao
dos drivers UART e SPI na variante Atmel (AT89C51). O teste consiste na execucao
de duas tarefas periodicas: (i) envio de um caracter via serie; e (ii) comunicacao
com um dispositivo SPI slave. O envio do caracter (tarefa de maior prioridade) e
feito a cada dois segundos, enquanto a comunicacao com o dispositivo slave e feita
a cada cinco segundos. O dispositivo SPI slave esta implementado numa placa de
circuito impresso (PCB) concebida pelo autor para avaliar e testar os drivers SPI e
I2C desenvolvidos (apendice A).
131
5.3. Testes Realizados
Resultados de Desempenho e Footprint de Memoria
Os resultados de desempenho traduzem os resultados a nıvel de tempo de execucao.
Estes indicam os ciclos de relogio necessarios para executar o teste com cada uma das
implementacoes - C++ template metaprogramming e C++ polimorfismo dinamico.
Os resultados de footprint de memoria indicam qual a memoria de codigo necessaria
para executar o teste com cada uma das implementacoes.
Para obter os tempos de execucao de cada uma das implementacoes do sistema
operativo, foi utilizado o debugger do ambiente de desenvolvimento. Nessa avaliacao
nao foi considerado o tempo que demora efetivamente a enviar o caracter via serie,
nem o tempo que demora a enviar a trama I2C. Por outras palavras, como os
drivers foram implementados utilizando o mecanismo de polling, significa dizer que
ao efetuar a depuracao a condicao de verificacao da flag que indica fim de transmissao
foi desprezada. Por outro lado, para avaliar o tamanho da memoria de codigo foi
utilizado o ficheiro de codigo produzido para executar na plataforma de teste.
(a) Tempo de execucao (b) Memoria de codigo
Figura 5.3: Resultados de desempenho e footprint de memoria (teste ao sistemaoperativo)
Os graficos da figura 5.3 apresentam os resultados do tempo de execucao e memoria
de codigo das implementacoes C++ com template metaprogramming (C++ TMP) e
polimorfismo dinamico (C++ PD) do sistema operativo, para a execucao das tarefas
anteriormente descritas.
Tal como os graficos ilustram, a implementacao com TMP apresenta tanto um
132
Capıtulo 5. Resultados Experimentais
tempo de execucao como dimensao de memoria de codigo inferior a outra imple-
mentacao. Basicamente, a implementacao com template metaprogramming reduz
cerca de 20% o tempo de execucao e 40% a memoria de codigo, relativamente a im-
plementacao com polimorfismo dinamico. Isto deve-se ao facto do codigo TMP ser
otimizado para a configuracao pretendida, enquanto na implementacao com polimor-
fismo dinamico o codigo e compilado com todas as funcionalidades selecionadas. Isto
afecta a linearidade do codigo, devido ao elevado numero de saltos (jumps), con-
sequentes do elevado numero de instrucoes que nao sao utilizadas, produzindo um
impacto negativo na performance do sistema.
Os resultados apresentados traduzem apenas dois graus de variabilidade em cada
uma das funcionalidades. Experiencias realizadas pelo autor com tres graus de vari-
abilidade indicam que a implementacao TMP pode reduzir o tempo de execucao em
cerca de 25% e a memoria de codigo em cerca de 50%. Resumindo, num sistema alta-
mente configuravel com elevado grau de variabilidade, a otimizacao usando a tecnica
de template metaprogramming permite atingir resultados significativos nas metricas
em causa.
Resultados de Gestao do Codigo
Embora as metricas de desempenho do sistema sejam de especial importancia em
tempo de execucao, nao implica que a forma como e feita a gestao e manutencao da
variabilidade do codigo nao tenha que ser tida em conta. Assim, torna-se tambem
importante avaliar e comparar as duas implementacoes do sistema operativo ao nıvel
da gestao do codigo, nomeadamente, na metricas LOC e NOC.
Os graficos da figura 5.4 apresentam os valores das metricas LOC e NOC para as
implementacoes C++ com template metaprogramming (C++ TMP) e com polimor-
fismo dinamico (C++ PD) do sistema operativo.
Dos graficos da figura 5.4, conclui-se que o numero de linhas de linhas de codigo
(LOC) das duas implementacoes e praticamente o mesmo (ligeira superioridade para
a implementacao com TMP). No que diz respeito a metrica relacionado com o numero
de classes (NOC), a implementacao C++ com template metaprogramming apresenta
um valor superior ao da implementacao C++ com polimorfismo dinamico. Isto in-
dica que o codigo e mais modular e apresenta um nıvel de encapsulamento superior.
Como consequencia, torna-se mais facil fazer a sua gestao, manutencao e possıvel
reutilizacao.
133
5.3. Testes Realizados
(a) Numero de linhas de codigo (b) Numero de classes
Figura 5.4: Resultados de gestao do codigo (teste ao sistema operativo)
5.3.2 Teste ao driver USART
Como o teste anteriormente apresentado permite apenas fazer uma comparacao
entre duas implementacoes que utilizam programacao orientada a objetos, por si so
esse teste nao permite aferir o potencial de otimizacao da tecnica de template me-
taprogramming quando comparada com uma implementacao em linguagem C. Nesse
sentido, o autor decidiu focar-se apenas num modulo e implementar a variabilidade
desse modulo com compilacao condicional. Isto para obter resultados conclusivos
acerca da comparacao das duas implementacoes C++ com uma implementacao em
C.
O teste realizado concentra-se no modulo do driver UART. Toda a variabilidade
nas interfaces do driver foram implementadas tambem com compilacao condicional.
Os resultados traduzem o tempo de execucao, memoria de codigo, e metricas de
gestao de codigo, para uma aplicacao sequencial que transmite e recebe um caracter
e uma string via serie.
Resultados de Desempenho e Footprint de Memoria
Os resultados de desempenho traduzem os resultados a nıvel de tempo de execucao.
Estes indicam os ciclos de relogio necessarios para executar o teste com cada uma
das implementacoes - C++ template metaprogramming, C++ polimorfismo dinamico,
C compilacao condicional. Os resultados de footprint de memoria indicam qual
134
Capıtulo 5. Resultados Experimentais
a memoria de codigo necessaria para executar o teste com cada uma das imple-
mentacoes.
Para obter os tempos de execucao de cada uma das implementacoes do sistema
operativo, foi utilizado o debugger do ambiente de desenvolvimento. Nessa avaliacao
nao foi considerado o tempo que demora efetivamente a enviar ou receber o caracter
via serie. Por outro lado, para avaliar o tamanho da memoria de codigo foi utilizado
o ficheiro de codigo produzido para executar na plataforma de teste.
(a) Tempo de execucao (b) Memoria de codigo
Figura 5.5: Resultados de desempenho e footprint de memoria (teste ao driver USART)
Os graficos da figura 5.5 apresentam os resultados do tempo de execucao e memoria
de codigo das implementacoes C com compilacao condicional (C CC), C++ com poli-
morfismo dinamico (C++ PD), e C++ com template metaprogramming (C++ TMP),
do driver, para a execucao da aplicacao anteriormente descrita.
Tal como seria de esperar a implementacao com TMP apresenta novamente um
melhor desempenho e gestao da memoria de codigo quando comparada com a imple-
mentacao com polimorfismo dinamico.
Na comparacao das implementacoes C (compilacao condicional) e TMP, tal como
os graficos ilustram, a implementacao C apresenta tanto um tempo de execucao
como dimensao de memoria de codigo inferior a implementacao TMP. No entanto,
a diferenca e relativamente mais baixa que a diferenca existente entre as duas im-
plementacoes com programacao orientada a objetos. Por exemplo, a implementacao
TMP apenas agrava o desempenho em 5% e o footprint de memoria em 20% quando
135
5.3. Testes Realizados
comparada com a implementacao C. Ja a implementacao com polimorfismo dinamico
agrava o desempenho em 17% e o footprint de memoria em 75% quando comparada
com a implementacao em linguagem C.
Os resultados apresentados traduzem apenas dois graus de variabilidade na fun-
cionalidade em analise. Experiencias realizadas pelo autor com mais graus de vari-
abilidade indicam que os valores apresentados anteriormente na comparacao entre a
implementacao TMP e a implementacao C mantem-se praticamente constantes com
o aumento da variabilidade. Contudo, quando se compara com a implementacao com
polimorfismo dinamico, o agravamento nas metricas em analise pode ser muito supe-
rior (sobretudo em termos de footprint de memoria) com o aumento da variabilidade
na funcionalidade.
Resultados de Gestao do Codigo
Se as metricas de gestao e manutencao da variabilidade do codigo tenham sido
importantes na interpretacao dos resultados do teste realizado ao sistema operativo,
entao agora neste caso desempenham um papel preponderante. Isto porque como foi
visto anteriormente, apesar da tecnica de template metaprogramming ser muito mais
otimizada que a implementacao com polimorfismo dinamico, esta agrava ligeiramente
o desempenho e memoria da aplicacao quando comparada com a linguagem C. No
entanto, como o overhead e relativamente baixo, as metricas de gestao de codigo
desempenham um papel fundamental na comparacao entre as mesmas.
Os graficos da figura 5.6 apresentam os valores das metricas LOC e NOC para
as implementacoes C com compilacao condicional (C CC), C++ com polimorfismo
dinamico (C++ PD), e C++ com template metaprogramming (C++ TMP), na fun-
cionalidade em analise.
Dos graficos da figura 5.6, conclui-se que o numero de linhas de linhas de codigo
(LOC) das tres implementacoes e praticamente o mesmo (ligeira superioridade para
a implementacao em C). No que diz respeito a metrica relacionado com o numero
de classes (NOC), a implementacao C++ com template metaprogramming apresenta
um valor superior ao da implementacao C++ com polimorfismo dinamico e C com
compilacao condicional. Alias, a implementacao em C, embora apresente uma ligeira
melhoria no desempenho e footprint de memoria que a implementacao com TMP,
nao apresenta qualquer modularidade e encapsulamento no codigo. Em sistemas com
enorme variabilidade, isso reflete-se numa degradacao da organizacao do codigo, pois
136
Capıtulo 5. Resultados Experimentais
(a) Numero de linhas de codigo (b) Numero de classes
Figura 5.6: Resultados de gestao do codigo (teste ao driver USART)
este e poluıdo com as diretivas de pre-processador. Portanto, a gestao e manutencao
deste tipo de sistemas torna-se uma tarefa fastidiosa e suscetıvel a erros, que acaba
por nao compensar os ganhos obtidos nas outras duas metricas.
137
Capıtulo 6
Conclusoes
Neste ultimo capitulo da dissertacao, sao apresentadas as ilacoes retiradas pelo
autor, com base no que foi implementado. Alem disso, sao apresentadas algumas
sugestoes para melhorar e expandir o trabalho realizado.
6.1 Conclusao
A dissertacao apresenta o porting, expansao e customizacao de um sistema ope-
rativo orientado a objetos para a arquitetura MCS-51. No entanto, esta distingue-se
essencialmente pela aplicacao de template metaprogramming como metodologia para
a gestao da variabilidade do sistema operativo.
Este foi sem duvida um projeto desafiante pela variedade e profundidade de co-
nhecimentos necessarios no domınio dos sistemas embebidos. Desde a compreensao
de diferentes arquiteturas de processadores (80188 e 8051), passando pelos sistemas
operativos (sobretudo de sistemas operativos de tempo-real baseados em microker-
nel), linguagem assembly, programacao orientada a objetos (sobretudo C++), tem-
plate metaprogramming e compiladores, todas estas tematicas foram utilizadas no
desenvolvimento da dissertacao.
Relativamente aos objetivos do trabalho, estes foram efetivamente cumpridos.
Depois de analisados alguns sistemas operativos orientados a objetos, o sistema ope-
rativo ADEOS foi selecionado como a melhor solucao para os recursos da arquitetura
alvo. Assim, foi realizado com sucesso o porting desse sistema operativo para a
plataforma MCS-51. Depois disso, foram expandidas uma serie de funcionalidades
no sistema operativo, principalmente um conjunto de device drivers para comunicar
139
6.2. Trabalho Futuro
com os perifericos do microcontrolador, bem como um escalonador power-aware para
aplicacoes cujo principal foco seja o baixo consumo energetico. O objetivo seguinte,
e de todo o mais importante do trabalho, consistiu na aplicacao de template meta-
programming para efetuar o refactoring do sistema operativo. Por outras palavras,
a gestao da variabilidade do sistema foi realmente conseguida utilizando essa tecnica
de programacao avancada. Finalmente, o ultimo objetivo concretizado com sucesso
focou-se na validacao da premissa de que e possıvel utilizar C++ template metapro-
gramming (POO), sem comprometer consideravelmente o desempenho e recursos de
memoria, para implementar software embebido altamente customizavel, reutilizavel
e de facil gestao e manutencao. Os resultados obtidos demonstraram que isso e efe-
tivamente possıvel a custa de um overhead reduzido.
6.2 Trabalho Futuro
Apesar do cumprimento de todos os objetivos inicialmente propostos, existem
bastantes funcionalidades e melhorias que podem expandir o trabalho desenvolvido.
A primeira esta relacionada com os device drivers. Conforme foi referido na seccao
4.2.2, mais do que desenvolver controladores de hardware sob a forma de classes, o
conceito de device drivers tem intrinsecamente associado uma determinada abstracao,
que implica disponibilizar servicos comuns a todos os dispositivos. Assim sendo,
propoe-se o desenvolvimento de uma camada de abstracao recorrendo a template
metaprogramming para encapsular todos os perifericos na mesma interface.
A segunda sugestao consiste na expansao das funcionalidades e da sua variabi-
lidade. Mais do que implementar a propria variabilidade em cada funcionalidade,
esta dissertacao preocupou-se mais com a metodologia para gerir essa variabilidade.
Assim sendo, na tentativa de expandir ainda mais o trabalho desenvolvido, propoe-se
implementar mais mecanismos de IPC (semaphore, shared memory, message queue),
mais algoritmos de escalonamento (rate-monotonic, round robin), mais device drivers
(CAN, ADC, DAC), e mais variantes dos mesmos.
A terceira sugestao diz respeito as interrupcoes. O sistema operativo nao dispo-
nibiliza uma interface que permita configurar as interrupcoes do microcontrolador.
Inclusive os device drivers foram implementados apenas com o mecanismo de polling.
Assim sendo, propoe-se a expansao do sistema operativo com uma interface para
configuracao das interrupcoes disponibilizadas pelo 8051.
140
Capıtulo 6. Conclusoes
A quarta sugestao esta ligada aos resultados experimentais. Como foi possıvel
constatar, a avaliacao do sistema operativo nas metricas em causa so foi possıvel en-
tre duas implementacoes: polimorfismo dinamico e template metaprogramming. Isto
porque implementar todo o sistema operativo e respetiva variabilidade em linguagem
C tornava-se uma tarefa inexequıvel para o autor. Neste sentido, propoe-se a imple-
mentacao do sistema operativo (e de todas as funcionalidades) em linguagem C, e
consequente estudo comparativo das metricas de desempenho, footprint de memoria
e gestao do codigo. Desta forma, sera possıvel sustentar fidedignamente os resultados
aqui apresentados.
A quinta e ultima sugestao propoe o porting do sistema operativo para outras
plataformas. Basicamente, consiste na reimplementacao do codigo dependente do
processador para arquiteturas como a AVR ou ARM. Desta forma, reestruturando
o IDE seria possıvel gerar o sistema operativo orientado a objetos customizado para
diferentes arquiteturas alvo. Tudo de forma facil e simplificada.
141
Apendices
Apendice A
Placa Circuito Impresso: spi2c
Para validar o codigo dos drivers SPI e I2C, o autor decidiu projetar e implemen-
tar um add-on para a plataforma de desenvolvimento de testes (8051DKUSB). Isto
porque por si so, essa plataforma nao dispoe de hardware capaz de comunicar com
as interfaces desses protocolos do microcontrolador.
O add-on designado spi2c foi concebido de forma a ser acoplado ao conector de
expansao da placa 8051DKUSB. Desta forma e possıvel aceder facilmente aos pinos
dedicados a cada um dos protocolos de comunicacao. A nıvel de hardware, a placa
vem equipada essencialmente com dois I/O expanders de 16-bit e dois conversores
analogico-digital (ADC). Dos I/O expanders, ambos da Microchip Technology [79],
o MCP23S17 [80] tem interface SPI, enquanto o MCP23017 [80] tem interface I2C.
Quanto aos ADCs, o ADS7834 [81] tem interface SPI, e o ADS7823 [82] tem interface
I2C. Nos pinos de ambos os I/O expanders sao ligados LEDs, em logica negada, para
visualizar as saıdas, bem como switchs para avaliar as entradas. Nas entradas dos
ADCs sao ligados divisores de tensao com potenciometro, para variar o valor da
tensao lida. A alimentacao do add-on e feita com a alimentacao da plataforma de
desenvolvimento, disponıvel no conector de acoplamento. Sao usadas resistencias de
polarizacao para limitar a corrente nos LEDs, resistencias de pull-up nas linhas I2C,
bem como alguns condensadores de desacoplamento. O esquematico e o layout da
placa spi2c pode ser visto nas figuras A.1 e A.2, respetivamente.
145
Figura A.1: PCB spi2c: esquematico
146
Apendice A. Placa Circuito Impresso: spi2c
Figura A.2: PCB spi2c: layout
147
Bibliografia
[1] D. Tennenhouse, “Proactive computing,” Communications of the ACM, pp. 43–
45, May 2000.
[2] A. McHoes and I. M. Flynn, Understanding Operating Systems, 6th ed. Course
Technology, 2010.
[3] AUTOSAR, “Requirements on operating system,” Automotive Open System
Architecture GbR, Tech. Rep., June 2006.
[4] P. J. Plauger, “Embedded c++: An overview,” Embedded Systems Programming,
1997.
[5] D. Herity, “C++ in embedded systems: Myth and reality,” EE Times India,
1998.
[6] K. Czarnecki, “Generative programming: Principles and techniques of software
engineering based on automated configuration and fragment-based component
models,” Ph.D. dissertation, University of Ilmenau, 1998.
[7] K. Czarnecki and U. Eisenecker, Generative Programming: Methods, Tools, and
Applications, 1st ed. Addison-Wesley Professional, 2000.
[8] N. Cardoso, P. Rodrigues, O. Ribeiro, J. Cabral, J. Monteiro, J. Mendes, and
A. Tavares, “An agile software product line model-driven design environment for
video surveillance systems,” September 2012.
[9] N. Cardoso, J. Vale, O. Ribeiro, J. Cabral, P. Cardoso, J. Mendes, and A. Ta-
vares, “Model-driven template metaprogramming,” September 2012.
149
[10] N. Cardoso, J. Vale, J. Cabral, J. Mendes, P. Cardoso, A. Tavares, and J. Mon-
teiro, “Use of template metaprogramming to address the heterogeneity of video
surveillance systems,” March 2012.
[11] N. Cardoso, J. Cabral, P. Cardoso, J. Mendes, A. Tavares, and J. Monteiro, “A
novel approach to manage the complexity and heterogeneity of video surveillance
systems,” March 2012.
[12] C. Steup, M. Schulze, and J. Kaiser, “Exploiting template-metaprogramming
for highly adaptable device drivers - a case study on canary an avr can-driver,”
in 12th Brazilian Workshop on Real-Time and Embedded Systems, 2010.
[13] D. Abrahams and A. Gurtovoy, “The boost mpl library.”
[14] B. W. Kernighan and D. M. Ritchie, C Programming Language, 2nd ed. Prentice
Hall, 1988.
[15] D. G. Alcock, Illustrating BASIC (A Simple Programming Language), 1st ed.
Cambridge University Press, 1977.
[16] S. Leestma and L. Nyhoff, Pascal Programming and Problem Solving, 4th ed.
Prentice Hall, 1993.
[17] M. A. Covington, D. Nute, and A. Vellino, Prolog Programming in Depth, 1st ed.
Prentice Hall, 1996.
[18] G. Hutton, Programming in Haskell, 1st ed. Cambridge University Press, 2007.
[19] P. Winston and B. Horn, Lisp, 3rd ed. Addison-Wesley, 1989.
[20] B. Stroustrup, C++ Programming Language, 3rd ed. Addison-Wesley Profes-
sional, 1997.
[21] J. Smiley, Learn to Program with Java, 1st ed. Osborne/McGraw-Hill, 2002.
[22] G. G. Abraham Silberschatz, Peter Galvin, Operating System Concepts, 8th ed.
Wiley, 2008.
[23] GNU Operating System. [Online]. Available: http://www.gnu.org/
150
[24] QNX: Operating systems, development tools, and professional services for
connected embedded systems. [Online]. Available: http://www.qnx.com/
[25] D. Lewis, Fundamentals of Embedded Software: Where C and Assembly Meet,
1st ed. Prentice Hall, 2001.
[26] LynxOS RTOS: The real-time operating system for complex embedded systems.
[Online]. Available: http://www.lynuxworks.com/rtos/
[27] Using the FreeRTOS Real Time Kernel. [Online]. Available: http:
//www.freertos.org/
[28] V. F. Russo, “An object-oriented operating system,” Ph.D. dissertation, Univer-
sity of Illinois at Urbain-Champaign, 1990.
[29] Choices. [Online]. Available: http://choices.cs.uiuc.edu/
[30] Trion Development Object Oriented Operating System. [Online]. Available:
http://trion.sourceforge.net/index.php
[31] F. Afonso, C. Silva, S. Montenegro, and A. Tavares, “Middleware fault tolerance
support for the boss embedded operating system,” in Aspects, Components, and
Patterns for Infrastructure Software, International Workshop on, 2007.
[32] ——, “Applying aspects to a real-time embedded operating system,” in Intelli-
gent Solutions in Embedded Systems (WISES), International Workshop on, 2006.
[33] S. Montenegro and F. Zolzky, “Boss/evercontrol os/middleware target ultra high
dependability,” in Data Systems on Aerospace (DASIA), 2005.
[34] CERG. Embedded System Research Group . [Online]. Available: http:
//esrg.dei.uminho.pt/
[35] M. Barr, Programming Embedded Systems in C and C ++, 1st ed. O’Reilly
Media, 1999.
[36] Y. Hu, E. Merlo, M. Dagenais, and B. Lague, “C/c++ conditional compilation
analysis using symbolic execution,” 2000.
[37] G. team. GCC, the GNU Compiler Collection. [Online]. Available: http:
//gcc.gnu.org/
151
[38] H. Spencer and G. Collyer, “]ifdef considered harmful, or portability experience
with c news,” in USENIX ’92, June 1992.
[39] D. Lohmann, F. Scheler, R. Tartler, O. Spinczyk, and W. Schroder-Preikschat,
“A quantitative analysis of aspects in the ecos kernel,” in EuroSys ’06, April
2006.
[40] M. Franz, P. Frohlich, and T. Kistler, “Towards language support for component-
oriented real-time programming (position paper).”
[41] C. Prehofer, “Feature oriented programming: A fresh look at objects,” 1997.
[42] D. Batory, “A tutorial on feature oriented programming and product-lines,” in
25th International Conference on Software Engineering (ICSE’03), 2003.
[43] G. Kiczales, J. Lamping, A. Mendhekar, C. Maeda, C. V. Lopes, J.-M. Loing-
tier, and J. Irwin, “Aspect-oriented programming,” in European Conference on
Object-Oriented Programming (ECOOP), 1997.
[44] O. Spinczyk, A. Gal, and W. Schroder-Preikschat, “Aspectc++: An aspect-
oriented extension to the c++ programming language,” in 40th Internacional
Conference on Technology of Object-Oriented Languages and Systems, 2002.
[45] D. Abrahams and A. Gurtovoy, C++ Template Metaprogramming: Concepts,
Tools, and Techniques from Boost and Beyond, 1st ed. Addison-Wesley Profes-
sional, 2004.
[46] D. D. Gennaro, Advanced C++ Metaprogramming, 1st ed. CreateSpace Inde-
pendent Publishing Platform, 2011.
[47] Intel. [Online]. Available: http://www.intel.com
[48] M. A. Mazidi, J. G. Mazidi, and R. D. McKinlay, The 8051 Microcontroller and
Embedded Systems, 2nd ed. Prentice Hall, 2005.
[49] Texas Instruments. [Online]. Available: http://www.ti.com/
[50] CC1111/CC2511 USB HW User’s Guide. [Online]. Available: http://www.ti.
com/lit/ug/swru082b/swru082b.pdf
152
[51] A True System-on-Chip Solution for 2.4-GHz IEEE 802.15.4 and ZigBee Appli-
cations. [Online]. Available: http://www.ti.com/lit/ds/swrs081b/swrs081b.pdf
[52] A. Tavares, C. Lima, C. Silva, J. Cabral, and P. Cardoso, Programacao de Mi-
crocontroladores, 1st ed. Netmove Comunicacao Global, Lda. Editora, 2009.
[53] ATMEL - 8051 Microcontroller Instruction Set. [Online]. Available: http:
//www.atmel.com/Images/doc0509.pdf
[54] Intel - 80186/80188 HIGH-INTEGRATION 16-BIT MICROPROCESSORS.
[Online]. Available: http://www.ieeta.pt/∼jaf/apoio ip/praticas/data sheets/
ds80188red.pdf
[55] N. Cardoso, J. Vale, O. Ribeiro, J. Cabral, P. Cardoso, J. Mendes, and A. Ta-
vares, “Model-driven template metaprogramming,” 2012.
[56] Atmel - FLIP. [Online]. Available: http://www.atmel.com/tools/FLIP.aspx
[57] K. C. Louden, Compiler Construction: Principles and Practice, 1st ed. Course
Technology, 1997.
[58] Ceibo Offers 8051 C++ Compiler. [Online]. Available: http://www.keil.com/
pr/article/1032.htm
[59] IAR Embedded Workbench for 8051. [Online]. Available: http://www.iar.com/
en/Products/IAR-Embedded-Workbench/8051/
[60] SILICON LABS - C8051F120/1/2/3/4/5/6/7 C8051F130/1/2/3. [Online].
Available: http://www.silabs.com/Support%20Documents/TechnicalDocs/
C8051F12x-13x.pdf
[61] A True System-on-Chip Solution for 2.4-GHz IEEE 802.15.4/ZigBee. [Online].
Available: http://www.ti.com/lit/ds/symlink/cc2430.pdf
[62] DS80C390 Dual CAN High-Speed Microprocessor. [Online]. Available:
http://datasheets.maximintegrated.com/en/ds/DS80C390.pdf
[63] DS80C400 Network Microcontroller. [Online]. Available: http://datasheets.
maximintegrated.com/en/ds/DS80C400.pdf
153
[64] 8051 IAR C/C++ Compiler - Reference Guide, 4th ed., IAR Systems, February
2008.
[65] Tabela de Instrucoes do 8086. [Online]. Available: http://dcc.ufrj.br/∼renancg/
hs/cp/uteis/INSTRUCOES 8086.pdf
[66] J. Corbet, A. Rubini, and G. Kroah-Hartman, Linux Device Drivers, 3rd ed.
O’Reilly Media, 2005.
[67] MAX232, MAX232I - DUAL EIA-232 DRIVERS/RECEIVERS. [Online].
Available: http://www.ti.com/lit/ds/symlink/max232.pdf
[68] I2C Bus - Technical Overview. [Online]. Available: http://www.mcc-us.com/
I2CBusTechnicalOverview.pdf
[69] AT89C51ID2 - Datasheet. [Online]. Available: http://www.atmel.com/Images/
doc4289.pdf
[70] SPI three slaves. [Online]. Available: http://upload.wikimedia.org/wikipedia/
commons/f/fc/SPI three slaves.svg
[71] SPI timing diagram. [Online]. Available: http://upload.wikimedia.org/
wikipedia/commons/6/6b/SPI timing diagram2.svg
[72] B. Mochocki, X. S. Hu, and G. Quan, “A realistic variable voltage scheduling
model for real-time applications,” 2002.
[73] H. Aydin, R. Melhem, D. Mosse, and P. Mejıa-Alvarez, “Dynamic and aggressive
scheduling techniques for power-aware real-time systems,” 2001.
[74] Y. Shin and K. Choi, “Power conscious fixed priority scheduling for hard real-
time systems,” 1999.
[75] P. Pillai and K. G. Shin, “Real-time dynamic voltage scaling for low-power
embedded operating systems,” 2001.
[76] Y. SHIN, K. CHOI, and T. SAKURAI, “Power-conscious scheduling for real-time
embedded systems design,” 2001.
154
[77] K. C. Kang, S. G. Cohen, J. A. Hess, W. E. Novak, and A. S. Peterson, “Feature-
oriented domain analysis (foda) feasibility study,” Carnegie-Mellon University
Software Engineering Institute, Tech. Rep., 1990.
[78] S. Toolworks. Understand - Source Code Analysis & Metrics. [Online].
Available: http://www.scitools.com/
[79] M. Technology. Microchip. [Online]. Available: http://www.microchip.com/
[80] ——. MCP23017/MCP23S17 Datasheet: 16-Bit I/O Expander with Se-
rial Interface. [Online]. Available: http://ww1.microchip.com/downloads/en/
devicedoc/21952b.pdf
[81] T. Instruments. ADS7834 Datasheet: 12-Bit High-Speed, Low-Power
Sampling ANALOG-TO-DIGITAL CONVERTER. [Online]. Available: http:
//www.ti.com/lit/ds/sbas098a/sbas098a.pdf
[82] ——. ADS7823 Datasheet: 12-Bit, Sampling A/D Converter with I2C
INTERFACE. [Online]. Available: http://www.ti.com/lit/ds/symlink/ads7823.
[83] Trion Design Proposition. [Online]. Available: http://sourceforge.net/cvs/
?group id=90198
[84] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of
Reusable Object-Oriented Software, 1st ed. Addison-Wesley Professional, 1994.
155