Análise de abstração : um belo desafio..

Programação C em geral

Moderadores: 51, guest2003

Mensagempor polesapart » 05 Dez 2008 10:16

... cortei ...
Marcelo Samsoniuk escreveu:
por outro lado, o que temos ainda eh um emaranhado de funcoes chamadas diretamente de um loop principal e algumas interrupcoes. pensando em algumas melhorias operacionais, sera que conceitos de RTOS usados em microcontroladores maiores de 16 e 32 bits sao aplicaveis em microcontroladores menores de 8 bits ?

aparentemente sim, a ideia seria extender alguns conceitos de modulos (viram tasks) e interfaces (fila de mensagens)... mas isso eh mais para frente! eu implementei ateh o momento um sisteminha de escalonamento de processos baseado em uma queue de mensagens. ateh agora esta funcionando relativamente bem, atingindo 5000 mensagens/segundo em um HC908 de 2.5MHz.

provavelmente conforme for colocando mais e mais tasks, a performance deve cair... ou sera que nao ? :)


Hmm eu li a thread meio por cima, mas acho que como você sugeriu o conceito de filas de mensagens e máquinas de estado mais atrás, e agora a idéia de algo escalavel mesmo pra sistemas pequenos, eu lembrei de um conceito bem interessante que tenho estudado nas horas vagas: um "nano" rtos orientado ao processamento de eventos, através de máquinas de estado recursivas (embora a minha implementação nem seja recursiva hehe).

A idéia original eu descaradamente emprestei de uma plataforma comercial. O nome da encrenca é quantum platform, http://www.state-machine.com/products/index.htm

A idéia deles é muito bacana, voce tem um loop principal que despacha mensagens entre tarefas; Só que estas tarefas não seguem o conceito tradicional: todas elas tem prioridades diferentes. Quando uma tarefa de prioridade superior tenta enviar uma mensagem a uma inferior, a mensagem é colocada numa fila. Quando é inverso, a tarefa superior é ativada para tratar a mensagem, sincronizadamente. Cada mensagem é como um input para uma maquina de estados, e cada passo é processado de modo não preemptivo. O interessante é que a implementação prevê, opcionalmente, um trocador de contexto preemptivo: as interrupções tem prioridades superiores a todas as tarefas, mas ao retorno da interrupção, caso uma tarefa superior a que estava rodando recebeu uma mensagem (ex. do handler de irq), ela é ativada no lugar.

Estas ativações não ocorrem como um salvamento de contexto completo: como uma tarefa superior executa uma mensagem até o final e retorna (ou seja, deixa de existir) para a tarefa inferior, o custo é apenas um ajuste na pilha para o retorno da função e a própria chamada de função. O fato de usar uma só pilha é uma facilidade muito grande para sistemas embarcados onde cada byte de RAM vale ouro.

Eu fiquei surpreso como esta metodologia é produtiva.

A escalabilidade desta solução também é muito boa, o calcanhar de aquiles é que como ela não suporta threads de verdade, não há aproveitamento nativo de múltiplos processadores, quando rodar isto numa plataforma mais avançada. Mas isto pode ser facilmente contornado com pequenas alterações no sistema de envio de mensagens e implementando threads reais como uma camada mais acima, do ponto de vista da abstração, encapsulando um número de "objetos ativos" (o nome que eles dão para estes pseudo-processos) relacionados entre si dentro de threads que seriam totalmente preemptivas e independentes entre si, trocando mensagens com uma fila protegida por semáforos ou outros mecanismos de contenção. O que seria muito escalável, já que um grande problema da maior parte das implementações de programação estruturada usando threads é o abuso de semáforos, grande parte do tempo de cpu de um sistema com muitas threads é perdido fazendo spin locking e há um impacto de I/O muito grande por causa do acesso atômico a memória, que ignora o cache ou força um flush, etc.

Eu tou brincando com uma implementação própria deste conceito, mas ainda não cheguei ao ponto de integrar multiplas threads. O que eu fiz roda em um microcontrolador (qualquer, se duvidar até um pic) e no linux, de forma bastante reduzida, pois manipular o contexto no linux usando sinais não deu muito certo, então a parte preemptiva não funciona :P

Em cima disto, seria relativamente fácil criar APIs para gerenciar drivers e dispositivos de I/O em geral, usando troca de mensagens, quase exatamente da forma que você sugeriu nesta thread.

Se alguém mais tiver interesse em brincar com este conceito, eu tenho até um projeto aberto na sourceforge, eu só não postei nenhum código pra lá ainda por quê ainda está meio primitivo e por quê como eu descaradamente copiei as *idéias* da quantum platform, embora não tenha usado código, ainda quero averiguar como fica do ponto de vista legal, pois a idéia é colocar a coisa em licença LGPL ou BSD e não ser processado mais tarde por nada disto :-)
Avatar do usuário
polesapart
Byte
 
Mensagens: 477
Registrado em: 19 Nov 2007 12:56
Localização: Curitiba

Mensagempor msamsoniuk » 07 Dez 2008 16:04

eu acho que queues (filas de mensagens) sao essenciais!

na verdade a ideia nem eh nova, eu vi isso implementado em um multiplex de portas seriais jah no principio da decada de 90, mais tarde vi a mesma coisa implementada em RTOS para mcus maiores e suponho que eh um conceito presente a decadas na industria. de fato, qq desenvolvedor rapidamente percebe que tecnicas simples nao dao conta do recado!

por exemplo, uma abordagem simples eh realizar o IO de forma sincrona:

Código: Selecionar todos
input_isr() interrupt
{
  output_buffer(input_buffer());
}


uma desvantagem evidente eh que o output nem sempre pode ser realmente sincrono e ele acaba fazendo pooling, aumentando consideravelmente o tempo preso no tratamento de interrupcao e causando a perda de eventos.

colocando uma queue no meio, temos IO de forma assincrona:

Código: Selecionar todos
input_isr() interrupt
{
  send(input_buffer());
}

output_isr() interrupt
{
  output_buffer(recv());
}


a grande vantagem, neste caso, eh que o input e output sao ativados de acordo com a cadencia dos respectivos dispositivos, eliminando o pooling. como os dispositivos operam de forma assincrona, a divergencia de velocidade entre eles eh compensada pela queue, que atua como um buffer elastico.

no caso de IO associado a processos:

Código: Selecionar todos
input_isr() interrupt
{
  process_buffer(input_buffer());
}


causaria problema similar se process_buffer() for muito longo. assim seria mais interessante:

Código: Selecionar todos
input_isr() interrupt
{
  send(input_buffer());
}

process_task()
{
  msg = recv();
  ...
}


alem do tempo preso no atendimento de interrupcao ser pequeno, a queue de mensagens pode ser usada para escalonar e priorizar as tarefas a serem executadas.

poderia pensar em algo do genero:

Código: Selecionar todos
struct ipc_msg
{
  void (*to)(void);
  ..
} *ipc_queue_recv,ipc_queue_send;

...

for(;;)
  if(ipc_queue_recv)
  {
    ipc_queue_recv->to();
  }
  /* idle */
}


mas neste caso temos uma queue unica. em um sistema com multiplas queues, seria possivel criar um mecanismo de prioridade, mas acho que daih sai um pouco fora do contexto para pequenos mcus.

eventualmente, eu percebi que em um sistema saturado o maior entrave para atingir a performance maxima eh a forma como as mensagens sao alocadas. obviamente alocar dinamicamente pode ser complexo, entao uma solucao rapida eh criar um pequeno ring buffer. no caso de um input ativado por interrupcao, eu consegui uma boa performance com apenas 3 mensagens! quando uma mensagem eh enviada, eu seto um flag de lock. depois que ela eh processada e liberada, o flag eh desativado pelo receptor e a mensagem volta a estar disponivel no pool de envio.

eventualmente, com uma unica queue, um mecanismo similar teria que ser utilizado no output:

input->ring-buffer->queue->process_task->ring-buffer->output

nao eh muito diferente de um device driver para ethernet no linux. em particular, para o powerquicc, temos:

quicc_rx->ring_buffer->receive_isr->packet_queue->router_task->packet_queue->transmit_task->ring_buffer->quicc_tx

claro, com o bonus de que os ring buffers sao gerenciados pelo hardware inteligente do powerquicc...em um mcu menor, o gerenciamento dos ring buffers eh feito por software.

em relacao a multitarefa preemptiva, bom, temos que pensar direito no caso de um RTOS.

em sistemas orientados a mensagens, o codigo de tratamento eh realmente muito similar, basicamente:

Código: Selecionar todos
task_cooperativa()
{
  msg = recv();
  /* tratamento da mensagem recebida */
}

task_preemptiva()
{
  while()
  {
     msg = recv();
     /* tratamento da mensagem recebida */
  }
}


em termos funcionais, devem ser equivalentes em um RTOS.

no primeiro caso, a task processa as mensagens disponiveis na queue. se nao houver mais nada, ela retorna para outra task rodar. no segundo caso, a task processa as mensagens disponiveis na queue de forma similar. mas se nao houver nada, o RTOS forca a preempcao da proxima task. nao muda muito, porem:

Código: Selecionar todos
task_preemptiva()
{
  while()
  {
    if(semaforo == 1)
    {
       /* processa algo */
    }
  }
}


eh possivel apenas com multitarefa preemptiva, porem representa um fabuloso desperdicio de processamento. um mecanismo mais eficiente seria possivel se o RTOS checasse o flag e soh entao escalonasse a task:

Código: Selecionar todos
task_preemptiva()
{
  while()
  {
     check(flag);
     /* processa algo */
  }
}


mas isto acaba equivalente a:

Código: Selecionar todos
task_cooperativa()
{
  check(flag);
   /* processa algo */
}


se sao equivalentes, eh mais vantagem implementar a opcao mais simples e economizar uns bytes! :)
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor ivan » 07 Dez 2008 21:19

Sim, sim, sim, sim... Nada se cria, tudo se copia... já disse um grande sábio da atualidade - Ninguem Sabe Quem da Silva.

O questionamento era quanto a capacidade de Mcus de pequeno porte gerenciarem filas, pois em computadores maiores isto é fato a muitos anos sem dúvida.

Como é conceitual, gosto mais de desenhos:

Código: Selecionar todos
                 
                  ||                 |                 ||----|  Driver1   ||
                  ||                 |                 ||----|  Driver2   ||
                  ||    Dispa     |                 ||----|  Driver3   ||
    MAIN   ---||    tcher      |  Threads    ||----|  Driver4   ||---  HWs
                  ||                 |                 ||----|  Driver5   ||
                  ||                 |                 ||----|  Driver...  ||
                  ||                 |                 ||----|  Drivern   ||


OBS:
- Threads seria mais um modulo a ser usado ou não dependendo do HW, mas sempre presente em termos de interface entre dispatcher/drivers.
- A camada de Drivers pode ser dividida em duas partes onde a superior conteria as abstracoes(APIs) e a inferior tocaria o HW em si.
"A mente que se abre a uma nova idéia jamais volta ao seu tamanho original." (Albert Einstein).
Avatar do usuário
ivan
Word
 
Mensagens: 618
Registrado em: 12 Out 2006 21:27

Mensagempor ivan » 07 Dez 2008 21:33

Dúvidas...

- Esse post e sobre uma forma padronizada de construir aplicações modulares/configuraveis que sejam adaptaveis aos HW de uma unica empresa?
"A mente que se abre a uma nova idéia jamais volta ao seu tamanho original." (Albert Einstein).
Avatar do usuário
ivan
Word
 
Mensagens: 618
Registrado em: 12 Out 2006 21:27

Mensagempor msamsoniuk » 08 Dez 2008 00:36

a ideia eh apenas discutir alguns conceitos genericos sobre modularidade, adaptaveis a qualquer combinacao de HW e SW...o linux seria um bom exemplo, mas infelizmente nao roda em mcus pequenas.

acho que um dos fatores principais mesmo eh determinar se os conceitos sao uteis ou nao. na maioria dos casos os usuarios de pequenos mcus torcem o nariz ateh mesmo para compiladores C! :)
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor joao » 08 Dez 2008 06:53

Posso dar um exemplo de que isso realmente ajuda:

Na siemens, quando eu trabalhava, existia um sistema que rodava em WxWorks. Ele tinha várias funcionalidades e podíamos facilmente dividir o sistema em dois blocos: HW e SW.
Existia realmente arquivos de defines que simplesmente jogavam para nós apenas o que o endereço X fazia e pronto. Ou seja, o SW nao precisava nem saber sobre endereçamento.
Claro que em algumas excessões a gente tinha que pegar o datasheet para dar uma estudada, mas a facilidade disso era enorme.

Dai fizeram um novo produto. E neste caso usaram o linux! Era o mesmo produto de antes, mas que rodava mais rápido e que teria que ter como OS o linux! Mas vejam só! Como já existia essa divisao entre SW e HW, ficou simples migrar o código.

Ao ver essa pergunta eu realmente me lembrei deste exemplo.

Sei que em muitos casos, não precisamos fazer isso, mas para códigos grandes(o binario da siemens dava 20MB! Sem contar libs e o OS), isso é totalmente necessário. Se a empresa está pensando em fazer um update do produto no futuro, eu acoselho a fazerem camadas de abastração para poderem fazer essas mudanças de maneira rápida.

Aqui aonde trabalho, que é com celular, a gente trabalha entre várias plataformas e também criamos camadas. Mas infelizmente não posso comentar mais do que isso no momento, já que ainda trabalho na empresa. :)

[]'s
Avatar do usuário
joao
Byte
 
Mensagens: 463
Registrado em: 17 Out 2006 08:21

Mensagempor msamsoniuk » 08 Dez 2008 10:41

eu francamente acho dificil uma migracao bem sucedida (rapida e simples) de vxworks para linux! as arquiteturas, conceitos e apis sao bastante diferentes e migrar do vxworks para linux sinceramente nao me parece representar vantagem alguma: o linux consome mais memoria, mais recursos e eh mais complexo. seria simples o processo inverso, pq uma aplicacao padrao posix do linux pode ser facilmente migrada para o vxworks, mas nao o contrario... e device drivers entao, nao sao migraveis em nenhum dos dois sentidos, pq sao dedicados ao sistema operacional, nao tem jeito... exceto pelo fator custo, nao parece ter logica: eh arriscado e custa caro em termos de pessoal.

nao digo que vc nao esta certo em parte, linux, uclinux e vxworks sao excelentes solucoes, mas tem uma sutileza no negocio! o que vc falou eh possivel com um middleware... mesmo assim, duvido muito desse tipo de migracao, nao tem a minima logica mexer em um produto que jah funciona! :)

bom, sisteminha funcionando em cima de ipc:

Código: Selecionar todos
HC908 RTOS
Copyright (c) 2008, Marcelo Samsoniuk
All rights reserved.

starting HC908 system...

sci: serial controller interface
sci: 9600 bps console
rtc: realtime clock
rtc: 305Hz isr

system ready.

app: up 00:00:01
app: up 00:00:02
app: up 00:00:03
app: up 00:00:04
app: up 00:00:05
...


sem muitos problemas ateh o momento...uma task de rtc cuida do hardware de rtc, uma task de aplicacao periodica, pede um read no rtc, recebe um reply e imprime o valor do rtc... depois posto o source! :)
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor msamsoniuk » 11 Dez 2008 21:06

por sinal, achei um artigo muito interessante sobre o assunto:

Imagem

http://www.cotsjournalonline.com/home/a ... ?id=100030
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor msamsoniuk » 13 Dez 2008 18:58

bom, eu tinha prometido os fontes dos meus testes... postei alguns resultados neste outro topico:

http://www.asm51.eng.br/phpbb/viewtopic ... 7058#47058

vamos ver se os colegas de 68k se interessam em reutilizar algo.
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor joao » 13 Dez 2008 20:20

Bom, em relação a migração, não ouve migração e sim um novo Hardware para atender uma nova demanda, que no caso era muito maior.
Uma central trabalha com XYZ chamadas e esse novo terminal trabalhava com 100 vezes mais XYZ chamadas.

O que eu ainda me lembro, existia em ambos as camadas que tratavam de HW e SW, mas no HW eles fizeram ainda mais uma camada pois ela trabalhava com um sistema de pacotes internos.

Como eu trabalhava com a parte mais alta, que era as features, foi só um copy/paste das funções e algumas mudanças, mas na internet foi fácil achar as mesmas funcoes do wxworks implementadas para linux. Claro que teve várias modificoes nas camadas de baixo, mas eu não trabalhei com elas, apenas quando tinha que fazer debug de valores que vinham do HW.

O motivo é claro foi o preço. Até onde eu me lembro, disseram que o projeto rodando linux mas fazendo 100x mais chamadas, conseguia ser mais barato que o antigo que usava wxworks, pois era um sistema obsoleto(HW e SW).

De qualquer maneira, não vamos fugir do assunto. Só era um exemplo simples de que se vc dividir o seu projeto em camadas, vc tem uma maneira rápida de fazer uma migração ou ainda dar suporte para mais uma plataforma.

[]'s
Avatar do usuário
joao
Byte
 
Mensagens: 463
Registrado em: 17 Out 2006 08:21

Mensagempor jeanfernandes » 16 Dez 2008 16:25

Gente, historia eh bom de se contar...mas vamos manter o foco no lado real do mundo....
Pelo bem de conteudo, nao acham ?
Valew.
:lol:
Jean P. Fernandes - Eng. Eletrônico - (83) 2102-2116 - APEL - www.apel.com.br - Campina Grande - PB
jeanfernandes
Word
 
Mensagens: 539
Registrado em: 11 Out 2006 15:36
Localização: Campina Grande - PB

Mensagempor jeanfernandes » 16 Dez 2008 16:38

Só reiterando ao pessoal que chegou depois
e ainda ta meio que boiando na coisa, xo tentar traduzir aqui
um pensamento que me vem toda vez q eu converso com o Marcelo

Voce pode pintar uma parede simplesmente abrindo uma lata de tinta
e jogando nela (a tinta e nao a lata).
Pode misturar a tinta com Nx o seu volume em agua e pintar novamente
Pode usar um pincel uma brocha tb....

Enfim...existe N maneiras de pintar uma parede o problema
é como se pinta. Existe um mundo e conceitos acima do que voce se auto-rotula programador. A ideia é trazer pelo menos ao conhecimento um desses conceitos, que é abstracao de dados em camadas para sistemas de pequeno porte, no melhor estilo Proex,....chega de programar porca-jumenta-desnecessariamente falando.

Eu estou feliz, porque pelo menos faz os caras apresentarem suas armas e deixarem algum tipo de discussao util a esse respeito. Hoje pode parecer que meia duzia sabe,...mas a ideia eh justamente expandir a coisa.
Jean P. Fernandes - Eng. Eletrônico - (83) 2102-2116 - APEL - www.apel.com.br - Campina Grande - PB
jeanfernandes
Word
 
Mensagens: 539
Registrado em: 11 Out 2006 15:36
Localização: Campina Grande - PB

Mensagempor chipselect » 17 Dez 2008 20:45

eu não acho que existe uma solução definitiva para todos os casos de micros, seria interessante delimitar ou criar mecanismos que possa auxiliar na especificação do escopo (hardware destino), e então trabalhar dentro de cada escopo definido. Obviamente que um uC poderia ser encaixado em mais de um escopo, dependendo da aplicação final.

Se for focar mais pra uC tosco com 63 bytes de RAM, acho que a tendência é no final ter um monte de libs com defines pra todo canto, e se for usar um uC melhor, a tendência seria um RTOS ou coisa do tipo, com uma API mais elaborada.

Outra coisa que acho importante é padronização do código (nomenclaturas, documentação, assinatura de função, etc...) para auxiliar na legibilidade do código. No mundo do C/C++ parece que isso vai por osmose nos novos programadores, mas sempre acaba existindo as diferenças no final. Já vi algumas recomendações sobre isso, todas com diferenças.
chipselect
Word
 
Mensagens: 744
Registrado em: 16 Out 2006 18:50

Mensagempor jeanfernandes » 19 Dez 2008 13:09

ChipSelect
Poe pra gente de forma organizada os links e um descritivo do que vai achar dentro dele. É uma forma de manter organizada a coisa aqui pra nao desvirtuar a conversa.

Veja, a metodologia tem que ser avaliada. O grande barato por trás disso tudo é por exemplo mecanismos de acesso e otimizaçao de código e chamada de funções, que estão ou estarão dentro das apresentações.

Claro que usar ponteiro, ponteiro pra função, lookup table, pode parecer que o cara tá gastando "C" só para aparecer, mas quem conhece sabe que não é bem assim. Esses conceitos implícitos normalmente não são aplicáveis pela maioria e os vícios de linguagem se perpetuam. Ponto.

Voltando a abstração e metodo de acesso às camadas de código....eheeheheheh.....
Jean P. Fernandes - Eng. Eletrônico - (83) 2102-2116 - APEL - www.apel.com.br - Campina Grande - PB
jeanfernandes
Word
 
Mensagens: 539
Registrado em: 11 Out 2006 15:36
Localização: Campina Grande - PB

Mensagempor chipselect » 19 Dez 2008 16:05

1- adotar um padrão de código. Isso é dependente da linguagem, mas acho que seria C pra uC. Na verdade os padrões de códigos são adotados antes mesmo de qualquer projeto, e valem para todos os projetos... pelo menos deveria ser assim.
Sugestões de padrões de código, infelizmente são pra C++, mas aproveita-se muita coisa para C:
http://www.possibility.com/Cpp/CppCodingStandard.html
pra quem gosta de GNU:
http://www.gnu.org/prep/standards/

2- Levantamento de requisitos para a "abstração de hardware".
-> 100% de portabilidade de código fonte a nível de aplicativo?
-> portabilidade de código fonte a nível de drivers e OS.
-> Modularidade do código, permitindo adicionar/remover recursos
-> O código do driver de hardware deve ser inteligente para compilar o código adequado ao recurso existente? Se não houver recursos mínimos, dá um "assert"?
-> que mais? Tem que fechar essa lista de requisitos "essenciais"

Pelas mensagens do pessoal, esses requisitos resultaram em:
-> O código aplicativo do usuário final da plataforma deveria obeder a uma estrutura pré-definida
-> Os códigos de "driver" devem ser classificados em diversas categorias previamente padronizadas.

3- Estudar soluções já existentes ou em estudos...
Sugestões:
Microkernel pra 68HC11
http://fett.tu-sofia.bg/et/1997/Statii% ... roller.pdf

RTOS pra PIC (acho que é código aberto, deve ser portável também, e tem o recurso de "drivers" para alguns periféricos)
http://www2.eletronica.org/projetos/skm/manual-skm

Algo interessante para análise os "Beans" do processor Expert, que ninguém gosta de usar, mas ali ele quase que cria um "driver" para cada periférico... e não amarra o código do usuário a uma estrutura pré-definida, só que a IDE do PE faz muita coisa automatizada por baixo dos panos. Esse trabalho poderia ser feito pelo pré-processador do compilador, pra não exigir uma IDE do além, só que poderá resultar em um código de driver do além, ehehhe

Acho que esses links e as idéias já postadas aqui no fórum ajudam a ter uma boa idéia de plataforma de software, partindo do princípio de utilizar os uC mais toscos do mercado, ou seja, limitando chamada a funções e considerando quase nada de memória, principalmente pra pilha.

Resta estimar o quanto essa plataforma iria subutilizar um uC de 32 bits que possui capacidade de threads e etc, mas daí a idéia de escopo ajudaria a limitar a área de aplicação.

4- Análise de mercado para a solução proposta: será que uma solução com código que se adequa ao microcontrolador automaticamente, sem intervenção do usuário será bem aceito pelos programadores? Por que muita gente não gosta do PE (Processor Expert) então?
chipselect
Word
 
Mensagens: 744
Registrado em: 16 Out 2006 18:50

AnteriorPróximo

Voltar para Visual C++/C/C++/C#

Quem está online

Usuários navegando neste fórum: Nenhum usuário registrado e 0 visitantes

x