Vous êtes sur la page 1sur 25

MicroKernel

O projeto do microkernel foi iniciado mais como prova de conceito e para aprendizado.
O projeto acabou crescendo e uma idia conseguir chegar num S.O. completo, voltado
para sistemas embarcados de baixo custo.

At o momento publiquei uma srie de artigos sobre os conceitos de programao por
trs de um kernel alm de gerar uma apostila e um conjunto de slides sobre o tema.

O projeto do kernel est bem documentado e devo partir um pouco para a rea de
drivers. Quem sabe eu chego no carregamento dinmico de apps seguindo o pessoal do
BRTOS!

Seguem os links dos artigos sobre o kernel
Parte 1 - Mquinas de Estado
Parte 2 - Ponteiros de Funo - Verso 0.1
Parte 3 - Structs e Buffers Circulares - Verso 0.2
Parte 4 - Condies Temporais - Verso 0.3
Parte 5 - Condies Temporais, decrementando os timers
Parte 6 - Verso 1.0 para microcontroladores PIC18F4550
PIC18F Kernel - 01
postado em 19/12/2010 14:58 por Rodrigo Almeida | 18/01/2011 06:12 atualizado (s) ]
Bom dia a todos.

Vou iniciar uma serie de posts sobre kernel: o que , como funciona e como fazer. Sim,
a idia desenvolver um kernel (simples, cooperativo e sem controladores de memria)
para o microcontrolador PIC18F4550. Minha idia projet-lo inteiro em ISO-C, desse
modo o cdigo pode ser facilmente portado para qualquer arquitetura.

Kernel - Wikipedia

Na figura acima podemos ver que o kernel o responsvel por realizar a interface entre
o hardware (CPU, Memria e Perifricos) e as aplicaes. Quando no existe kernel
todo o processo de organizao das tarefas, sejam elas de hardware ou de aplicao
feita pelo programador. Uma primeira tentativa de organizar o cdigo quando um kernel
no est disponvel utilizar uma mquina de estados:


Mquina de estado para o sistema proposto.

A traduo da mquina de estados para o cdigo principal bem simples. Basta usar
uma estrutura switch...case.



Obs: a funo principal realmente void main(void) interrupt 0. Isso feito assim por
causa do compilador SDCC.

Esta estrutura traz diversas vantagens para o sistema: organizao, facilidade de
manuteno e principalmente previsibilidade. Conhecendo os tempos de execuo de
cada funo possvel dizer com que frequncia cada uma das funes/processos ser
executada. Esta a base de um sistema de tempo real.

Mesmo o cdigo se apresentando de modo organizado, a manuteno e expanso do
sistema fica prejudicada, pois a frequncia de execuo fica a cargo do programador. A
primeira funo de um kernel essa:
1) Organizar e permitir a execuo dos processos, com base em "algum critrio"

O "algum critrio" pode ser o tempo mximo de execuo, a prioridade das funes, a
criticidade dos eventos, a sequncia programada de execuo etc. este critrio que
diferencia os kernels preemptivos, cada processo tem um tempo mximo para ser
executado, dos cooperativos, cada processo executado at o final e apenas depois d
sequncia ao prximo. Como ele responsvel por organizar os processos, necessrio
ter funes que permitam a incluso de um novo processo e a remoo de um processo
antigo. Deste modo o loop apresentado no cdigo acima fica "invisivel" para o
programador. A execuo das tarefas passa a ser coordenada pelo kernel.

Cada processo utiliza internamente uma quantidade de memria para suas variveis
locais, passagem de parmetros para as funes que ele chama etc. Essa a segunda
funo de um kernel:

2) Permitir o acesso dos programas memria disponvel no sistema

Nesse ponto o kernel tambm deve se preocupar em informar ao programa quando a
requisio dele no pode ser cumprida, ou seja, interceptar todos os comandos malloc e
tratar os possveis erros.

Alm da memria os processos envolvidos precisam de "conversar" com os demais
recursos do computador/microcontrolador: porta serial, ethernet, dispositivo de vdeo,
teclado, etc. A permisso de uso destes dispositivos feita atravs de pedidos feitos ao
kernel. Esta a terceira responsabilidade do kernel:
3) Intermediar a comunicao entre os perifricos e os processos.

O kernel deve possuir ento uma funo que recebe como parmetros (no mnimo) qual
o perifrico a ser acessado e qual a ordem a ser enviada ao perifrico. Ele ento
direciona estes pedidos aos drivers que, s ento, devolvem a resposta para o processo
requisitante.

Definio do objetivo:

Como disse, vou, nos prximos posts, apresentar um passo a passo do desenvolvimento
deste kernel. Para simplificar o processo, o kernel atender apenas aos requisitos 1 e 3.
O modo de organizao dos processos ser no-preemptivo, cooperativo e com nveis
de prioridade ajustveis. Os drivers tero suas operaes padronizadas.

J vislumbrando o funcionamento do kernel, o prximo post ser sobre ponteiros de
funo.
At mais.
PIC18F Kernel - 02
postado em 22/12/2010 14:44 por Rodrigo Almeida
Este o segundo artigo sobre o processo de criao de um kernel para o
microcontrolador PIC18F. Por enquanto vou montar o kernel no desktop para que as
pessoas que estiverem acompanhando esses tpicos possam tambm replicar os
resultados. Vou deixar apenas para o fim as questes relativas ao hardware ou ao
assembler.

Como citei no artigo anterior para desenvolver o kernel essencial o uso de ponteiros
de funo. Vou passar antes uma reviso sobre o tpico.

Em algumas situaes queremos que o programa possa escolher qual funo deseja
executar, por exemplo num editor de imagens: Quero que o editor possa usar a funo
blur ou a funo sharpen na imagem desejada:

Montando as funes teramos:
//declaracao do tipo ponteiro para funo
typedef imagem (*ptrFunc)(imagem nImg);

imagem Blur(imagem nImg){
// implementao da funo
}

imagem Sharpen(imagem nImg){
// implementao da funo
}

//chamado pelo editor de imagens
imagem ExecutaProcedimento(ptrFunc nFuncao, imagem nImg){
imagem temp;
temp = (*nFuncao)(nImg);
return temp;
}
Como podemos perceber pelo cdigo a funo ExecutaProcedimento() recebe dois
parmetros: um a funo e o outro a imagem que ser processada. Essa funo tem
que ser do tipo ptrFunc, ou seja, receber apenas um parmetro: imagem e retornar
imagem. O conjunto dos tipos dos parmetros que a funo recebe mais o tipo de
retorno da funo damos o nome de assinatura.
A atribuio de uma funo a um ponteiro de funo, ou passagem por parmetros
como vimos no exemplo, s pode ser feita se ambas as funes tiverem a mesma
assinatura. Percebemos que tanto Blur() quanto Sharpen() obedecem este quesito. Por
isso possvel executar o cdigo a seguir:
//...
imagem nImagem = recebeImagemCamera();
nImagem = ExecutaProcedimento(Blur, nImagem);
nImagem = ExecutaProcedimento(Sharpen, nImagem);
//...
As funes Blur() e Sharpen() so passadas como se fossem variveis para serem usadas
apenas internamente da funo ExecutaProcedimento.

Por se tratar de um ponteiro necessrio de-referenciar a varivel antes de chamar a
funo como no cdigo abaixo.
temp = (*nFuncao)(nImg);
Notar que aps a de-referncia so passados os parmetros como numa funo qualquer.
Alm da passagem por parmetro a funo pode ser armazenada. Basta se criar uma
varivel do tipo do ponteiro da funo (prtFunc). Obs: apenas o endereo da funo
armazenado, no so criadas cpias da funo.

Aps esta explanao inicial vamos ao que interessa: Kernel 0.1

O cdigo pode ser dividido em trs sees: 1 - processos, 2 - kernel, 3 - rotina
principal/inicializao

1 - Processos: As funes que sero executadas pelo kernel (tst1(), tst2(), tst3())
passaro a ser denominadas processos, com idia semelhante aos processos de um
desktop. Alm disso todos os processos tm que ter a mesma assinatura do ponteiro
ptrFunc, no caso void F(void);
#include "stdio.h"

//prottipos dos "processos"
static void tst1(void);
static void tst2(void);
static void tst3(void);

//"processos"
static void tst1(void) { printf("1\n");}
static void tst2(void) { printf("22\n");}
static void tst3(void) { printf("333\n");}

//declaracao do tipo ponteiro para funcao
typedef void(*ptrFunc)(void);
2 - Kernel: Este primeiro kernel possui trs funes: uma para se inicializar, uma para
adicionar processos no pool de processos (vetFunc), que um vetor esttico de
tamanho 4 de ponteiros para funo, e uma para executar o kernel em si. Em geral a
funo que executa o kernel possui um loop infinito. Nesta primeira verso apenas
executamos as funes uma vez e encerramos o sistema. O kernel que apenas executa as
funes que lhe so passadas, uma a uma, na ordem em que foram passadas. No existe
nenhum outro tipo de controle. Este armazenamento e posterior chamado s possvel
atravs do uso de ponteiros de funo.
O tamanho do pool (vetFunc) definido estaticamente. Qualquer outra implementao
(lista lincada, malloc, etc) ir consumir muitos recursos do sistema tornando
(possivelmente) o cdigo grande demais para caber no microcontrolador escolhido. Por
isso necessrio fazer alguns testes antes para se conhecer o tamanho ideal do pool para
sua aplicao.
//variaveis do kernel
static ptrFunc vetFunc[4];
static int fim;

//prottipos das funes do kernel
static void InicializaKernel(void);
static void AddKernel(ptrFunc newFunc);
static void ExecutaKernel(void);
//funes do kernel
static void InicializaKernel(void){
fim = 0;
}
static void AddKernel(ptrFunc newFunc){
if (fim <4){
vetFunc[fim] = newFunc;
fim++;
}
}
static void ExecutaKernel(void){
int i;
for(i=0; i<fim;i++){
(*vetFunc[i])();
}
}
3 - Rotina Principal/Inicializao: Para utilizar o kernel desenvolvido bastante
simples, com os processos j implementados de acordo com a assinatura padro basta: 1
- inicializar o sistema, 2 - adicionar os processos que devem ser executados na ordem
em que sero executados e por fim 3 - executar o kernel.
int main(int argc,char **argv){
printf("Inicio\n\n");
InicializaKernel();
AddKernel(tst1);
AddKernel(tst2);
AddKernel(tst3);

ExecutaKernel();

printf("\nFim\n");
return 0;
}

No prximo artigo veremos como criar uma estrutura de controle mais apurada, que
permite que os processos possam ser automaticamente re-executados ao fim de cada
ciclo se for necessrio. Ser apresentado tambm uma modificao no pool de processos
de modo a permitir posteriormente a utilizao de prioridade e requisitos temporais para
cada processo.

At o prximo artigo.

-----------------------
Obs: Todo o cdigo foi testado com o GCC sob plataforma Linux. Por usar apenas
termos/conceitos compatveis com ISO-C deve funcionar em qualquer compilador para
desktop.
https://sites.google.com/site/rmaalmeida/extra/pic18fkernel-
02/kernel0_1.c?attredirects=0&d=1
PIC18F Kernel - 03
postado em 27/12/2010 15:17 por Rodrigo Almeida | 27/12/2010 16:29 atualizado (s) ]
No artigo anterior comentei sobre ponteiros de funo e porque so teis/necessrios
para montar um kernel. J no artigo de hoje abordaremos dois conceitos: structs e
buffers circulares.

Structs

Quando precisamos de reunir diversas informaes sobre um mesmo conceito em uma
nica varivel fazemos o uso (em linguagem C) de structs. As structs podem ser
comparadas com vetores onde cada posio permite o armazenamento de variveis
diferentes.
Pegando o exemplo emprestado da wikipedia:
#include <stdio.h>

struct pessoa
{
unsigned short int idade;
char nome[51]; /* vetor de 51 chars para o nome */
unsigned long int rg;
}; /* estrutura declarada */

int main(void)
{
struct pessoa exemplo = {16, "Fulano", 123456789}; /* declarao de
uma varivel tipo struct pessoa */

printf("Idade: %hu\n", exemplo.idade);
printf("Nome: %s\n", exemplo.nome);
printf("RG: %lu\n", exemplo.rg);

return 0;
}
Podemos notar que possvel criar um tipo novo, no exemplo pessoa, e utiliz-lo como
se fosse um tipo nativo. Para termos acesso a cada campo da funo devemos usar um
ponto separando a varivel do campo do cdigo: exemplo.idade retornar a idade que
est armazenada na varivel exemplo do tipo pessoa.

Buffers circulares

Buffers so regies de memria que servem para armazenar dados temporrios. Os
buffers circulares podem ser implementados utilizando uma lista lincada onde o ltimo
elemento se conecta com o primeiro. O problema dessa abordagem o gasto extra de
memria que ser inserido para colocar os ponteiros. A maneira de resolvermos esta
situao utilizando apenas um vetor com dois indices, indicando o incio e o fim da
lista.


Representao de um buffer circular implementado usando um vetor de 7 posies.
(Fonte wikipedia)
O problema desta abordagem conseguir definir quando o vetor est cheio ou vazio, j
que em ambos os estados o indicador de inicio e fim esto no mesmo local.
Existem pelo menos 4 alternativas de se resolver este problema: manter um slot sempre
aberto, usar um indicador de cheio/vazio, contar a quantidade de leituras/escritas ou
utilizar os ndices absolutos. Visando a simplificao optamos por manter sempre um
slot vazio.

Kernel verso 0.2

Nesta verso faremos duas alteraes principais: o vetor de processos passar a ser um
vetor de uma estrutura denominada processo e os processos retornaro um valor. Este
valor indicar se o processo em questo precisa ser executado novamente ou no. Deste
modo o kernel ser capaz de rodar as funes corriqueiras de forma automtica e se
alguma condio especial for encontrada possvel, em tempo de execuo, pedir que o
kernel execute uma funo apenas uma vez.

Vamos ento s defines, variveis e prottipos:
#include "stdio.h"
//pelo menos 1 a mais que a quantidade de funcoes
#define SLOT_SIZE 4

//cdigos de retorno
#define FIM_OK 0
#define FIM_FALHA 1
#define REPETIR 2

static int tst1(void);
static int tst2(void);
static int tst3(void);

//declaracao do tipo ponteiro para funcao
typedef int(*ptrFunc)(void);

//estrutura do processo
typedef struct {
ptrFunc Func;
} processo;

//variaveis do kernel
static processo vetProc[SLOT_SIZE];
static int ini;
static int fim;

//funcoes do kernel
static int InicializaKernel(void);
static int AddProc(processo newProc);
static void ExecutaKernel(void);
Conforme foi citado, agora todos os processos retornam um int que servir de indicador
se:
1. a funo foi executada corretamente e no quer ser reexecutada (#define
FIM_OK 0)
2. a funo teve algum erro na execuo (#define FIM_FALHA 1)
3. a funo foi executada corretamente e quer ser reexecutada (#define REPETIR
2)
Alm disso foi criado o tipo processo que, por enquanto, possui apenas um ponteiro
para funo do tipo int ptrFunc(void). Notar que o tamanho do slot deve ser no
mnimo uma unidade maior que o total de funes que estaro no kernel
simultneamente. Cuidado, este nmero diferente da quantidade total de funes, pode
ser inclusive maior. Ao decorrer dos artigos abordarei como mensurar esse nmero de
maneira mais precisa.

O cdigo principal praticamente no teve alterao, apenas no mtodo de adicionar
processos no kernel, que agora so feitos via estrutura processo, e no mais pelo
ponteiro de funo ptrFunc.
int main(void)
{
processo p1 = {tst1,1};
processo p2 = {tst2,2};
processo p3 = {tst3,3};

printf("Inicio\n\n");
InicializaKernel();

if (AddProc(p1) == FIM_OK)
{
printf("P1 adicionado\n");
}
if (AddProc(p2) == FIM_OK)
{
printf("P2 adicionado\n");
}
if (AddProc(p3) == FIM_OK)
{
printf("P3 adicionado\n");
}
printf("\nExecutando o kernel\n\n");

ExecutaKernel();

printf("\nFim\n");
return 0;
}
static int tst1(void) { printf("1\n"); return REPETIR;}
static int tst2(void) { printf("22\n"); return FIM_OK;}
static int tst3(void) { printf("333\n"); return REPETIR;}
Alm das alteraes mencionadas, a funo AddProc() agora retorna um sinal indicando
se foi bem sucedida ou no. Isto se faz necessrio pois temos uma quantidade limitada
no buffer dos processos. Com relao s tarefas, a nica alterao o parmetro de
retorno. No exemplo apenas as funes tst1() e tst3() querem ser reexecutadas.
static int InicializaKernel(void){
ini = 0;
fim = 0;
return FIM_OK;
}
static int AddProc(processo newProc)
{
//para poder adicionar um processo tem que existir espaco
//o fim nunca pode coincidir com o inicio
if (((fim+1)%SLOT_SIZE)!= ini )//se incrementar nessa condicao fim
vai ficar igual a ini
{
vetProc[fim] = newProc;
fim++;
if (fim>=SLOT_SIZE) //loop da lista
{
fim = 0;
}
return FIM_OK; //sucesso
}
return FIM_FALHA;//falha
}

static void ExecutaKernel(void)
{
int i,j;
for(i=0; i<10;i++) //aqui entraria o loop infinito
{
if (ini != fim)
{
printf("Ite. %d, Slot. %d: ",i, ini);
//executa e v se a funcao quer ser re-executada
if ( (*(vetProc[ini].Func))() == REPETIR )//retorna se precisa
repetir novamente ou no
{
//coloca a funcao no fim da lista, esta posicao esta sempre
livre.
vetProc[fim] = vetProc[ini];
fim++;
if (fim>=SLOT_SIZE) //loop da lista
{
fim = 0;
}
}
//prxima funcao
ini++;
if (ini>=SLOT_SIZE) //loop da lista
{
ini = 0;
}
}
}
}
A funo AddProc() agora deve verificar onde inserir o processo em questo,
lembrando que o buffer cclico e deve possuir uma posio sempre vazia.
A maior alterao est por conta da funo ExecutaKernel(). Antes ela apenas corria o
vetor executando as funes. Agora ela verifica o parmetro de retorno da funo e se a
funo precisa ser re-executada. Em caso afirmativo ela recoloca a funo no final do
buffer e processa a prxima funo. Obs: O printf serve apenas como informativo,
indicando qual a iterao atual e qual a funo que est sendo executada.

A seguir temos a sada do programa acima. Notar que a funo 2 s executada uma
nica vez. Posteriormente as funes 1 e 3 so executadas alternadamente mas sempre
obedecendo a posio/slot correta no buffer.
-----------------------------
Inicio

P1 adicionado
P2 adicionado
P3 adicionado

Executando o kernel

Ite. 0, Slot. 0: 1
Ite. 1, Slot. 1: 22
Ite. 2, Slot. 2: 333
Ite. 3, Slot. 3: 1
Ite. 4, Slot. 0: 333
Ite. 5, Slot. 1: 1
Ite. 6, Slot. 2: 333
Ite. 7, Slot. 3: 1
Ite. 8, Slot. 0: 333
Ite. 9, Slot. 1: 1

Fim
-----------------------------

No prximo artigo abordaremos os efeitos da condio temporal, como garantir que um
processo ser executado a cada X milissegundos.
https://sites.google.com/site/rmaalmeida/extra/pic18fkernel-
03/kernel0_2.c?attredirects=0&d=1
PIC18F Kernel - 04
postado em 11/01/2011 04:32 por Rodrigo Almeida | 11/01/2011 15:41 atualizado (s) ]
Nesta quarta parte abordaremos, dentro do processo de desenvolvimento do kernel,
como garantir as necessidades temporais dos processos. Pra isso necessrio ter alguma
ferramenta/dispositivo que permita a contagem do tempo. Para os microcontroladores
este procedimento feito atravs de um hardware dedicado. Neste artigo utilizaremos a
interface "time.h" das bibliotecas padres da ISO C.

Fonte da imagem: Wikipedia

A biblioteca time.h fornece ao programador uma srie de funes interessantes para
manipular o tempo, geralmente com base em algum contador do processador. Para
nossa aplicao precisamos apenas de uma funo que retorne o valor do tempo atual,
para isso utilizamos o valor retornado pela funo clock() dividido por
(CLOCKS_PERSEC/1000). Deste modo o resultado o tempo atual dados em
millisegundos.


A primeira alterao necessria na struct que define os processos. Agora ela deve
conter tambm um campo indicando qual o intervalo requisitado para a funo ser
executada. Alm do intervalo necessrio saber quando ela foi executada pela ltima
vez. Para facilidade de implementao melhor gravar, ao invs da ltima vez que foi
executada, qual deve ser a prxima vez que o processo ser executado.
//estrutura do processo
typedef struct {
ptrFunc Func;
int t_ms;
int start;
} processo;
Apenas as funes AdicionarProcesso() e ExecutaKernel() precisam ser executadas.
Na funo AdicionarProcesso() ser responsvel por inicializar a funo com o valor
adequado no campo start, que ser o prximo tempo no qual o processo ser executado.
static int AddProc(processo newProc)
{
//para poder adicionar um processo tem que existir espao
//o fim nunca pode coincidir com o inicio
if (((fim+1)%SLOT_SIZE)!= ini )//se incrementar nessa condio fim
vai ficar igual ini
{
vetProc[fim] = newProc;
vetProc[fim].start = clock()/(CLOCKS_PER_SEC/1000) +
vetProc[fim].t_ms;//atualizando quando deve ser executada
fim++;
if (fim>=SLOT_SIZE) //loop da lista
{
fim = 0;
}
return FIM_OK; //sucesso
}
return FIM_FALHA;//falha
}
Na funo ExecutaKernel() haver um loop que ao invs de correr o buffer de processos
executando um a um, devemos correr o buffer inteiro procurando o processo que est
mais prximo de ser executado e os ordena. Aps isso o kernel gasta tempo a toa
esperando chegar ao tempo de executar a funo. Aps executar a funo, se necessrio
reinsere a mesma no pool de funes.
static void ExecutaKernel(void)
{
int i,j;
int prox;
int now;
processo tempProc;
for(i=0; i<10;i++) //aqui entraria o loop infinito
{
if (ini != fim)
{
//Pega o tempo agora
now = clock()/(CLOCKS_PER_SEC/1000); //milissegundos
//Procura a prxima funo a ser executada com base no tempo
j = ini;
prox = ini;
while(j!=fim)
{
if (vetProc[j].start < vetProc[prox].start)
{
prox = j;
}
j = (j+1)%SLOT_SIZE;
}
//troca e coloca o processo com menor tempo como a prxima
tempProc = vetProc[prox];
vetProc[prox] = vetProc[ini];
vetProc[ini] = tempProc;
while(now < vetProc[ini].start)
{
now = clock()/(CLOCKS_PER_SEC/1000); //milissegundos
}
printf("T:%d, Ite. %d, Slot. %d: ",now, i, ini);
//executa e v se a funcao quer ser re-executada
if ( (*(vetProc[ini].Func))() == REPETIR )//retorna se precisa
repetir novamente ou no
{
//coloca a funo no fim da lista, esta posio esta sempre
livre.
vetProc[fim] = vetProc[ini];
vetProc[fim].start += vetProc[fim].t_ms;
fim++;
if (fim>=SLOT_SIZE) //loop da lista
{
fim = 0;
}
}
//prxima funo
ini++;
if (ini>=SLOT_SIZE) //loop da lista
{
ini = 0;
}
}
}
}
O tempo gasto a toa necessrio para garantir a sincronia, mas uma tima
oportunidade de usar a economia de energia se o microcontrolador possui com
instrues do tipo sleep/wait.

As alteraes na funo main so muito pequenas comparadas com as verses
anteriores. Elas podem ser vista no arquivo em anexo a seguir.

No prximo artigo faremos as alteraes necessrias para que este cdigo execute no
microcontrolador e abordaremos o inicio do desenvolvimento dos drivers.
https://sites.google.com/site/rmaalmeida/extra/pic18fkernel-
04/kernel0_3.c?attredirects=0&d=1
PIC18F Kernel - 05
postado em 20/01/2011 04:25 por Rodrigo Almeida
Mudana de planos!

Estava portando o ltimo cdigo para a placa e fazendo as alteraes necessrias.
Surgiram alguns problemas em relao a estrutura que estava utilizando para sincronizar
os eventos. O contador de tempo usado no microcontrolador uma varivel unsigned
int que incrementada a cada 1 ms.

Vamos supor dois eventos: P1 agendado para acontecer daqui 10 segundos e P2
agendado para acontecer daqui 50 segundos e que o "relgio" est marcando 45(s)
(now_ms = 45.535).




Observem que o evento P2 foi colocado corretamente pois o P2.start = now_ms +
50.000 = 30.000 (acontece um estouro na varivel do tipo unsigned int em 65.535). A
varivel now_ms ser incrementada at 55.535, quando acontecer o evento P1,
exatamente 10s depois de agendado. Esta varivel agora seguir at 65.535 e retornar
para zero.

No momento do overflow da varivel, o tempo decorrido desde o agendamento de P1
foi de (65.535 - 45.535) = 20.000 ms, ou 20 segundos. Como ela foi agendada com
delay de 50 segundos necessrio que a varivel seja incrementada at 30.000 para
passar mais 30 segundos, perfazendo o total de 50s conforme estipulado.

O problema com essa arquitetura quando temos dois processos que devem ser
disparados com tempo muito prximos, ou at mesmo simultneos.



Supondo que P1 leve 10s (exagero didtico) para ser executado. Aps a execuo de P1
teremos a seguinte linha de tempo



Pergunta: Dada a linha de tempo acima (que a nica coisa que o kernel enxerga) P2
deveria ter sido executado e no foi OU ele foi agendado para acontecer apenas daqui a
50.535 segundos?

Para solucionar esse problema aparentemente temos duas opes:
1. Criar uma flag indicando se o contador de tempo j passou pelo tempo do
processo. Desse modo saberemos se o processo que est atrs do contador na
linha de tempo est atrasado ou apenas foi agendado por um tempo muito
grande.
2. Mudar a arquitetura. Ao invs de compararmos o tempo de inicio de cada
processo com um contador, cada processo tem seu contador que diminui a cada
clock. Quando chegarem ao zero devem ser executados o mais rpido possvel.

A segunda opo traz mais algumas vantagens. Olhando a linha de tempo temos
dificuldade de saber qual o prximo evento a ser executado, no basta ver o processo
com o menor tempo, devemos fazer a busca do processo mais prximo do tempo atual
apenas pelo lado direito (processos que ainda devem ocorrer), levando em conta
tambm os atrasados. Com a segunda opo isso no ocorre, basta procurar a que possui
o menor tempo.
Alm disso este procedimento facilita a utilizao das prioridades. Se dois processos
precisam ser executados aos mesmo tempo (ambos chegaram ao zero), aquele com
maior prioridade rodar primeiro.

Seguem abaixo as duas opes (para efeito de comparao): primeiro o kernel com
contador de tempo free-running, e depois cada processo com seu contador (o campo
start decrementado at chegar a zero).

Primeira verso: checando quando o valor de start iguala ao tempo do free running
clock (now_ms)
1: void ExecutaKernel(void)
2: {
3: char j;
4: char prox;
5: unsigned int dj, dprox;
6: processo tempProc;
7: for(;;) {
8: if (ini != fim){
9: j = (ini+1)%SLOT_SIZE;
10: prox = ini;
11: while(j!=fim){
12: //Calculando o delta que falta
13: if(vetProc[j].start > now_ms){
14: dj = vetProc[j].start -
now_ms;
15: }else{//start est antes do tempo, tem
que levar em conta o overflow
16: dj = 0xffff -(now_ms -
vetProc[j].start);
17: }
18: if(vetProc[prox].start > now_ms){
19: dprox = vetProc[prox].start -
now_ms;
20: }else{
21: dprox = 0xffff -(now_ms -
vetProc[j].start);
22: }
23:
24: //menor tempo
25: if (dj < dprox){
26: prox = j;
27: }
28: j = (j+1)%SLOT_SIZE;//para poder
incrementar e ciclar o j
29: }
30: //troca e coloca o processo com menor tempo
como o proxima
31: tempProc.Func = vetProc[prox].Func;
32: tempProc.start = vetProc[prox].start;
33: tempProc.t_ms = vetProc[prox].t_ms;
34:
35: vetProc[prox].Func = vetProc[ini].Func;
36: vetProc[prox].start = vetProc[ini].start;
37: vetProc[prox].t_ms = vetProc[ini].t_ms;
38:
39: vetProc[ini].Func = tempProc.Func;
40: vetProc[ini].start = tempProc.start;
41: vetProc[ini].t_ms = tempProc.t_ms;
42:
43: //now_ms atualiza sozinho dentro da int
44: while(now_ms < vetProc[ini].start){
45: //adicionar sleep aqui, acorda na int.
46: }
47: //retorna se precisa repetir novamente ou no
48: if ( (*(vetProc[ini].Func))() == REPETIR
){
49: AddProc(&(vetProc[ini]));
50: }
51: //prxima funo
52: ini = (ini+1)%SLOT_SIZE;
53: }
54: }
55: }
Segunda verso: verificando quando o start chega zero
1: void ExecutaKernel(void){
2: unsigned char j;
3: unsigned char prox;
4: processo tempProc;
5: for(;;){
6: if (ini != fim){
7: //Procura a prxima funo a ser executada com
base no tempo
8: j = (ini+1)%SLOT_SIZE;
9: prox = ini;
10: while(j!=fim){
11: if (vetProc[j].start <
vetProc[prox].start){
12: prox = j;
13: }
14: j = (j+1)%SLOT_SIZE;//para poder
incrementar e ciclar o j
15: }
16: //troca e coloca o processo com menor tempo
como o proxima
17: tempProc.Func = vetProc[prox].Func;
18: tempProc.start = vetProc[prox].start;
19: tempProc.t_ms = vetProc[prox].t_ms;
20:
21: vetProc[prox].Func = vetProc[ini].Func;
22: vetProc[prox].start = vetProc[ini].start;
23: vetProc[prox].t_ms = vetProc[ini].t_ms;
24:
25: vetProc[ini].Func = tempProc.Func;
26: vetProc[ini].start = tempProc.start;
27: vetProc[ini].t_ms = tempProc.t_ms;
28: while(vetProc[ini].start!=0){
29: //adicionar sleep aqui, acorda na int.
30: }
31:
32: //retorna se precisa repetir novamente ou no
33: if ( (*(vetProc[ini].Func))() == REPETIR
){
34: AddProc(&(vetProc[ini]));
35: }
36: //prxima funo
37: ini = (ini+1)%SLOT_SIZE;
38: }
39: }
40: }
Pode-se notar claramente que o segundo cdigo mais facilmente compreendido
principalmente por no sofrer do problema do overflow. Em compensao a rotina que
gera a base de tempo se torna um pouco mais complexa para o segundo caso. No
primeiro ela apenas precisa incrementar a varivel now_ms a cada milissegundo
1: void isr(void) interrupt 1 {
2: if (BitTst(INTCON,2)) {
3: ResetaTimer(1000); //reseta com 1ms
4: now_ms++;
5: }
6: }
No segundo caso a rotina de interrupo precisa decrementar a varivel start de todos os
processos:
1: void isr1(void) interrupt 1{
2: unsigned char i;
3: if (BitTst(INTCON,2)) {
4: ResetaTimer(1000); //reseta com 1ms
5: i = ini;
6: while(i!=fim){
7: if((vetProc[i].start)>0){
8: vetProc[i].start--;
9: }
10: i = (i+1)%SLOT_SIZE;
11: }
12: }
13: }

Parte da complexidade da funo executa kernel foi passada para a funo de
interrupo. Apesar disso, esta alterao permitir no futuro a montagem de um sistema
de prioridades de maneira muito mais simples.

A verso 0.4 do kernel j est funcionando na placa (processador PIC18F4550) mas
deixarei para apresentar o cdigo no prximo artigo. Existem algumas alteraes que
foram necessrias por causa do compilador que estou usando (SDCC) e esse assunto
merece um artigo prprio.

At o prximo.
PIC18F Kernel - 06
postado em 08/04/2011 07:35 por Rodrigo Almeida | 08/04/2011 13:35 atualizado (s) ]
Depois de um bom tempo ocupado com outra atividades estarei falando sobre a primeira
verso funcional do kernel no PIC18F4550.

Falta ainda padronizar a parte de drivers e chamadas s API's. Comecei a trabalhar
bastante nisso e j estou com alguns resultados interessantes, mas isso para outro post!
Vamos ento ao que interessa.

Como citei no artigo anterior houve algumas mudanas, principalmente quanto ao modo
de reconhecer qual deve ser a prxima funo a ser executada. Uma vantagem que esse
sistema trouxe e que s percebi agora que eu posso continuar decrementando o tempo
mesmo que ele se torne negativo.


Se quiser um igual s comprar aqui no ThinkGeek

Como assim? Pode uma funo ter um tempo negativo? Poder pode, mas faz sentido?

Nesse caso sim ele indica h quando tempo aquela determinada funo deveria ter sido
executada mas ainda no foi. Ou seja, para nosso kernel qualquer funo com tempo
menor ou igual a zero deve ser executada imediatamente.

Essa aboradagem traz mais algumas vantagens: Eu consigo de maneira fcil
(inspecionando os tempos) verificar se existe alguma funo que est com problemas
para ser executada e qual o atraso mdio na execuo. Isso permite ao desenvolvedor
perceber quando a CPU passa a no dar conta de executar tudo com a velocidade
projetada. Outra grande vantagem, que eu devo implementar nas prximas verses,
permitir um modo simples de aumentar a prioridade da funo.

Agora o projeto est todo voltado para trabalhar com o PIC, desse modo temos que
realizar algumas mudanas. A primeira delas do compilador. Estou trabalhando com o
SDCC (verso 2.9.0) como compilador open source (leia-se gratuito) baseado no GCC.
Ele, por sua vez, faz uso do GPUtils (verso 0.13.7), uma sute de linker, assembler e
headers para as famlias PIC16 e PIC18 da Microchip.

Deste modo apresentamos algumas "bibliotecas", uma para trabalhar com as
interrupes(int.c e int.h), uma para operar com o timer (timer.c e timer.h), uma para
configurar os fusveis (config.h) e uma para funes bsicas e ponteiros para
registradores (basico.h). Segue abaixo apenas as funes que esses arquivos
disponibilizam
//CONFIG.H
//configuraes do microcontrolador
code char at 0x300000 CONFIG1L = 0x01; // No prescaler used
code char at 0x300001 CONFIG1H = 0x0C; // HS: High Speed Cristal
code char at 0x300003 CONFIG2H = 0x00; // Disabled-Controlled by SWDTEN
bit
code char at 0x300006 CONFIG4L = 0x00; // Disabled low voltage
programming

//INT.H
void InicializaInterrupt(void);

//TIMER.H
char FimTimer(void);
void AguardaTimer(void);
void ResetaTimer(unsigned int tempo);
void InicializaTimer(void);

//BASICO.H (apenas alguns trechos
#define FIM_OK 0
#define MIN_INT -30000

//funes de bit
#define BitFlp(arg,bit) ((arg) ^= (1<<bit))

//defines para registros especiais
#define PORTD (*(volatile __near unsigned char*)0xF83)
#define TRISC (*(volatile __near unsigned char*)0xF94)

Vamos agora ao que interessa.

Para o correto funcionamento do kernel necessrio que algumas operaes sejam
executadas com tempo definido, numa base de tempo fixa. Todas essas operaes foram
reunidas numa nica funo:
void KernelClock(void){
unsigned char i;
i = ini;
while(i!=fim){
if((vetProc[i].start)>(MIN_INT)){
vetProc[i].start--;
}
i = (i+1)%SLOT_SIZE;
}
}
Ser nesta funo que posteriormente faremos a verificao de tempo negativo e
faremos o "upgrade" da prioridade de cada funo para evitar que ela morra de fome.
Cada arquitetura (e compilador) possui um modo diferente de alocar uma funo como
responsvel pela interrupo. No SDCC essa tarefa bem simples, basta adicionar a
palavra interrupt X, onde X a interrupo desejada: 1 para as de alta prioridade e 2
para as de baixa prioridade.
//Interrupo
void isr1(void) interrupt 1
{
ResetaTimer(1000); //reseta com 1ms
KernelClock();
}
A funo ResetaTimer() faz os clculos para o registro do timer e rearma a interrupo,
enquanto chama a funo de clock do kernel.

J o kernel permanece praticamente inalterado, com a excesso de permitir que o tempo
possa ser negativo. Novamente importante notar que o SDCC no permite a atribuio
de ponteiros para structs por isso o cdigo nojento pra trocar os processos.
void ExecutaKernel(void){
unsigned char j;
unsigned char prox;
processo tempProc;
for(;;){
if (ini != fim){
//Procura a prxima funo a ser executada com
base no tempo
j = (ini+1)%SLOT_SIZE;
prox = ini;
while(j!=fim){
if (vetProc[j].start <
vetProc[prox].start){
prox = j;
}
j = (j+1)%SLOT_SIZE;//para poder
incrementar e ciclar o j
}

//troca e coloca o processo com menor tempo
como o proxima
//**Cdigo nojento
tempProc.Func = vetProc[prox].Func;
tempProc.start = vetProc[prox].start;
tempProc.t_ms = vetProc[prox].t_ms;

vetProc[prox].Func = vetProc[ini].Func;
vetProc[prox].start = vetProc[ini].start;
vetProc[prox].t_ms = vetProc[ini].t_ms;

vetProc[ini].Func = tempProc.Func;
vetProc[ini].start = tempProc.start;
vetProc[ini].t_ms = tempProc.t_ms;
while(vetProc[ini].start>0){
//adicionar sleep aqui, acorda na int.
}

//retorna se precisa repetir novamente ou no
if ( (*(vetProc[ini].Func))() == REPETIR ){
AddProc(&(vetProc[ini]));
}
//prxima funo
ini = (ini+1)%SLOT_SIZE;
}
}
}
Pronto! Segue em anexo tambm o main.c, com apenas 4 processos que fazem 4 leds
ligados porta D ficarem piscando com tempos diferentes.

Os prximos posts sobre o kernel devem demorar um pouco mais, em compensao no
prximo vou apresentar a nova IDE da microchip e como configurar o SDCC.

At mais!





v.1
basico.h
(3k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
config.h
(1k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
int.c
(1k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
int.h
(1k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
kernel.c
(3k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
kernel.h
(1k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
main.c
(2k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
timer.c
(1k)
Rodrigo Almeida,
08/04/2011 13:28




v.1
timer.h
(1k)
Rodrigo Almeida,
08/04/2011 13:28
Comentrios

Vous aimerez peut-être aussi