SlideShare ist ein Scribd-Unternehmen logo
1 von 29
Downloaden Sie, um offline zu lesen
Implementa¸c˜ao do algoritmo de exclus˜ao m´utua de Lamport
usando um framework de implementa¸c˜ao de algoritmos
distribu´ıdos
Alvaro Augustho de Souza Silva
Orientador: Luiz Eduardo Buzato
4 de novembro de 2011
Resumo
Neste documento ser´a apresentada uma implementa¸c˜ao em Java do algoritmo de ex-
clus˜ao m´utua de Lamport [1], baseado em rel´ogio l´ogicos, com o uso do framework
Neko. O Neko [3] ´e um framework desenvolvido para facilitar o desenvolvimento e im-
plementa¸c˜ao de algoritmos distribu´ıdos. O escolha do algoritmo de Lamport foi com
o objetivo de apresentar o funcionamento deste em um ambiente distribu´ıdo real (no
caso um cluster de 5 computadores) e os poss´ıveis usos dele e do pr´oprio Neko. Foi im-
plementado um mecanismo de gera¸c˜ao de log da troca de mensagens entre os processos
em um arquivo de texto simples, e com este log pˆode-se gerar um diagrama visual da
execu¸c˜ao correspondente a ele, em que um gr´afico representava a troca de mensagens
entre os n´os por meio de setas coloridas e etiquetas mostrando o tipos de mensagens
sendo enviadas. O programa usado para gerar este diagrama chama-se LogView e foi
feito em um projeto de semestre num instituto de tecnologia na Sui¸ca, com supervis˜ao
de um dos autores do Neko. O LogView [2] usado aqui foi modificado por mim, de
forma que ele pudesse exibir mais detalhes da troca de mensagens que a vers˜ao original
n˜ao exibia, como os tempos de envio e recebimento da mensagem, o tipo dela, algumas
mudan¸cas na interface de forma a tornar a visualiza¸c˜ao um pouco mais clara, entre
outras modifica¸c˜oes.
1 Introdu¸c˜ao
O problema da exclus˜ao m´utua ´e um ponto crucial de preocupa¸c˜ao em sistemas que com-
partilham recursos entre processos em v´arios computadores, ou mesmo threads de execu¸c˜ao
de um processo sendo executado em um ´unico computador. A quest˜ao ´e evitar que dois ou
mais processos ou threads tenham acesso simultaneamente a este recurso compartilhado,
comumente chamado de regi˜ao cr´ıtica (r.c.). A solu¸c˜ao apresentada para este problema
por Leslie Lamport em seu artigo Time, clocks, and the ordering of events in a distributed
system de 1978 se baseia na id´eia de timestamps. Timestamps s˜ao contadores monotonica-
mente crescentes mantidos unicamente por cada processo, e que seguem as trˆes seguintes
regras:
1
1. Um processo incrementa seu contador antes de cada evento neste processo.
2. Quando um processo envia uma mensagem, ele inclui seu pr´oprio contador na men-
sagem enviada.
3. Ao receber uma mensagem, o processo recipiente atualiza seu pr´oprio contador para
que ele seja maior que o m´aximo entre seu valor atual e o valor recebido na mensagem,
antes que ele considere a mensagem como recebida.
Com este contador e estas regras simples ´e poss´ıvel estabelecer uma rela¸c˜ao de an-
tecedˆencia entre eventos numa execu¸c˜ao distribu´ıda. A partir da id´eia de timestamps o
algoritmo de exclus˜ao m´utua de Lamport se desenvolve. Antes de mostr´a-lo ´e necess´ario
mencionar um elemento crucial em seu funcionamento: cada processo mant´em um vetor de
mensagens com tamanho igual ao n´umero de processos presentes no ambiente distribu´ıdo,
incluindo ele pr´oprio. Cada posi¸c˜ao deste vetor guarda a ´ultima mensagem recebida pelo
processo corresponte a esta posi¸c˜ao (todos os processos tem identificadores, que v˜ao de 0 a
n-1, sendo n o n´umero de processos; este identificador se chama PID). Assim, cada processo
consegue saber o timestamp da ´ultima mensagem recebida de cada outro processo, e isso
´e necess´ario para o funcionamente do algoritmo. Quando este texto se referir ao vetor de
mensagens ou simplesmente ao vetor ele estar´a se referindo a este elemento. O algoritmo
se baseia nas seguintes regras:
• Envio de requisi¸c˜ao para entrada na r.c.: um processo envia uma mensagem de req-
uisi¸c˜ao req para todos os outros processos com um timestamp (valor do seu rel´ogio
l´ogico atual), e adiciona a requisi¸c˜ao rotulada com o timestamp ao vetor de mensagens,
na posi¸c˜ao correspondente a ele mesmo.
• Recep¸c˜ao de requisi¸c˜ao: a mensagem de requisi¸c˜ao, com o timestamp gravado nela, ´e
colocada no vetor na posi¸c˜ao correspondente ao processo remetente, e um reconheci-
mento ack ´e enviado a ele.
• Libera¸c˜ao da r.c.: um processo envia uma mensagem de libera¸c˜ao rel para todos os
outros processos.
• Recep¸c˜ao de rel: a requisi¸c˜ao correspondente `a libera¸c˜ao ´e removida do vetor de
mensagens.
• Entrada na r.c. (guarda): um processo determina que pode entrar na r.c. se e
somente se:
1. ele tem uma requisi¸c˜ao no vetor com timestamp t
2. t ´e o menor timestamp no vetor
3. se t for igual a outro timestamp no vetor, o acesso ser´a concedido ao processo
que tiver o menor identificador de processo (PID)
2
A entrada na r.c. ´e determinada pela ordena¸c˜ao total gerada pelos rel´ogios l´ogicos.
Quando a guarda de de Pi torna-se verdadeira (o processo ´e autorizado a entrar na r.c.)
n˜ao existe outra requisi¸c˜ao no sistema (no vetor de mensagens ou em trˆansito) que tenha
timestamp menor que o da requisi¸c˜ao de Pi. Para que ele entre ´e necess´ario que o estado
das demais posi¸c˜oes do vetor de mensagens tenha sido atualizado com timestamps maiores
que o da requisi¸c˜ao de Pi; essa atualiza¸c˜ao ´e garantida pelo ack e pelo fato dos canais de
comunica¸c˜ao serem FIFO. Portanto, ´e garantida a justi¸ca e os deadlocks devido `a ordena¸c˜ao
total.
J´a a exclus˜ao m´utua ´e garantida porque o processo que entra na regi˜ao cr´ıtica somente
remover´a a sua requisi¸c˜ao do vetor dos demais processos depois que a tiver deixado e enviado
uma mensagem de release com rel´ogio l´ogico maior para todos eles. Novamente o mecanismo
de rel´ogio l´ogico garante que o algoritmo est´a correto.
Este texto trata da implementa¸c˜ao deste algoritmo para a execu¸c˜ao em um cluster
de cinco computadores, sendo cada computador um processo na execu¸c˜ao, atrav´es do uso
de um framework para a constru¸c˜ao de algoritmos distribu´ıdos chamado Neko. O Neko
´e um framework desenvolvido em Java, e dispon´ıvel na internet para download e uso (ver
bibliografia). Ele foi escrito no Distributed Systems Laboratory do Swiss Federal Institute of
Technology in Lausanne (EPFL), por X´avier D´efago e P´eter Urb´an, em conjunto com outros
estudantes. Seu desenvolvimento continuou no Japan Advanced Institute of Technology
(JAIST), grupo DDSG, e na Universid´ad Polit´ecnica de Madrid, laborat´orio LSD. Ele ´e
mantido principalmente por P´eter Urb´an, e veio sendo desenvolvido desde 2000.
Esta pequena introdu¸c˜ao explicou brevemente o algoritmo de exclus˜ao m´utua aqui im-
plementado e deu uma id´eia de o que ´e o Neko. O restante do texto est´a estruturado
da seguinte forma. A se¸c˜ao 2 explicar´a em mais profundidade o que ´e e como funciona o
framework Neko, e como algoritmos distribu´ıdos s˜ao implementados e executados quando
se usa ele. Em seguida, na se¸c˜ao 3 ´e apresentada a implementa¸c˜ao do algoritmo em si,
explicando cada classe em detalhes, a forma como elas se ligam, como a pilha de protocolos
em cada processo ´e montada antes da execu¸c˜ao, e como ela se comporta durante a execu¸c˜ao,
seja ela simulada (que n˜ao ser´a mostrada aqui) ou distribu´ıda num ambiente de rede real.
Em seguida, na se¸c˜ao 4 finalmente ´e apresentado como essa implementa¸c˜ao ´e colocada em
funcionamento, mostrando como inicializar cada processo e a execu¸c˜ao em si, e o que ´e
mostrado indicando que o algoritmo est´a realmente sendo executado. Ap´os isso, na se¸c˜ao
5 s˜ao mostradas as classes respons´aveis pela gera¸c˜ao do log da execu¸c˜ao. O log gerado
numa execu¸c˜ao e o uso dele para criar o diagrama de execu¸c˜ao no LogView ´e mostrado na
se¸c˜ao 6. Por fim, uma conclus˜ao ´e apresentada na se¸c˜ao 7, listando os pr´os e os contras do
Neko e comentando a vantagem de se poder visualizar o funcionamento de um algoritmo
executando de forma distribu´ıda de verdade num diagrama visual e intuitivo que mostra a
troca de mensagens que est´a ocorrendo.
2 Neko: Arquitetura e Funcionamento
O Neko ´e uma ´e uma plataforma de comunica¸c˜ao que permite tanto simular um algoritmo
distribu´ıdo numa ´unica m´aquina quanto execu¸c˜oes verdadeiramente distribu´ıdas em v´arias
3
Figura 1: Estrutura em camadas de uma aplica¸c˜ao usando o Neko
m´aquinas, numa rede de computadores, usando a mesma implementa¸c˜ao para um algoritmo.
Ele foi feito em Java, com o objetivo de simplificar o desenvolvimento de algoritmos dis-
tribu´ıdos, diminuindo o tempo de implementa¸c˜ao e testes e facilitando a execu¸c˜ao e estudo
dos resultados.
Como ´e mostrado na Figura 1, a arquitetura do Neko consiste de duas partes principais:
aplica¸c˜ao (aplication) e rede (networks). No n´ıvel da aplica¸c˜ao, uma cole¸c˜ao de processos
(numerados de 0 a n-1) comunicam-se atrav´es de uma interface simples de troca de men-
sagens: um processo sender coloca sua mensagem na rede com a primitiva ass´ıncrona send
e a rede ent˜ao entrega essa mensagem ao processo destinat´ario com a primitiva deliver.
Processo s˜ao implementados como programas em v´arias camadas.
A comunica¸c˜ao n˜ao ´e uma caixa-preta: a infrastrutura de troca de mensagens pode ser
controlada de v´arias maneiras. Primeiro, uma rede pode ser instanciada de uma cole¸c˜ao
de redes pr´e-definidas, como uma rede real usando TCP/IP ou uma Ethernet simulada.
Segundo, o Neko consegue controlar v´arias redes em paralelo. Terceiro, redes que extendem
e modificam as existentes s˜ao facilmente implement´aveis. O fato de o c´odigo ser aberto e a
linguagem de implementa¸c˜ao ser Java facilitam muito a moldagem da plataforma ao gosto
do usu´ario.
Mas, em geral, n˜ao ´e necess´aria a modifica¸c˜ao do c´odigo-fonte do Neko; ele j´a oferece uma
plataforma pronta, simples e completa para o teste e implementa¸c˜ao de muitos algoritmos.
O algoritmo de exclus˜ao m´utua de Lamport foi implementado apenas usando as ferramentas
e classes de Java disponibilizadas por ele na sua vers˜ao atual (Neko 1.0 beta 1, colocada `a
disposi¸c˜ao para download e uso no site oficial do projeto Neko em 16 de junho de 2009).
Para a execu¸c˜ao do algoritmo um arquivo simples de configura¸c˜ao ´e necess´ario. O que
foi usado aqui ser´a apresentado e explicado para maior clareza de sua fun¸c˜ao e para mostrar
melhor um detalhe importante da plataforma Neko. Os c´odigos das classes representando o
algoritmo de Lamport tamb´em ser˜ao explicados detalhadamente, e assim tanto ele quanto
4
a plataforma devem ser bem apresentados.
Vamos `a exposi¸c˜ao do c´odigo implementando o algoritmo e a exclus˜ao m´utua distribu´ıda.
3 A Implementa¸c˜ao do Algoritmo
3.1 A Interface MEAlgorithm
A exposi¸c˜ao da implementa¸c˜ao da exclus˜ao m´utua ser´a come¸cada mostrando uma interface
simples, criada com o objetivo de simplificar o programa:
package lamport;
public interface MEAlgorithm {
public void enterCriticalSection();
public void exitCriticalSection();
}
Os dois m´etodos da interface s˜ao enterCriticalSection e exitCriticalSection.
Como os pr´oprios nomes dizem, o primeiro ´e chamado quando uma classe que use um
objeto que faz uso desta interface deseja entrar numa se¸c˜ao cr´ıtica do c´odigo, que usa dados
compartilhados entre processos, e para isso quer garantir acesso exclusivo e sem risco de
encontrar ou criar incosistˆencias. Quando a aplica¸c˜ao terminar de usar essa regi˜ao cr´ıtica
ela deve chamar exitCriticalSection, que faz o que for necess´ario para liberar o acesso
`a essa regi˜ao e deixar os outros processos cientes de que ningu´em mais tem a prioridade.
3.2 A Classe Que Faz Uso da Exclus˜ao M´utua: Application.java
Antes de mostrar a implementa¸c˜ao do algoritmo propriamente dita ser´a apresentada a classe
que chama os m´etodos da interface acima para entrar e sair da regi˜ao cr´ıtica sem o perigo
de gerar incosistˆencias de dados:
package lamport;
// java imports:
import java.util.Random;
//lse.neko imports:
import lse.neko.ActiveReceiver;
import lse.neko.NekoProcess;
import lse.neko.SenderInterface;
public class Application extends ActiveReceiver
{
private SenderInterface sender;
public void setSender(SenderInterface sender)
{
this.sender = sender;
}
/* O algoritmo de exclus~ao m´utua deste processo.
* Este m´etodo ´e usado pela classe MEInitializer no momento da cria¸c~ao dos processos. */
private MEAlgorithm algorithm;
5
public void setMEAlgorithm(MEAlgorithm algorithm)
{
this.algorithm = algorithm;
}
/* o PID deste processo */
private int me;
/* o n´umero de processos
private int n;
/* objeto gerador de n´umeros aleat´orios */
private Random random;
public Application(NekoProcess process) {
super(process, "[Aplica¸c~ao] Thread-p" + process.getID());
me = process.getID();
n = process.getN();
}
A primeira parte do c´odigo faz as importa¸c˜oes de classes necess´arias. Esta classe estende
ActiveReceiver, uma classe do Neko que cria objetos capazes de receber ativamente men-
sagens que forem enviadas a eles, ao inv´es de simplesmente esperar elas serem entregues.
Seu funcionamento ser´a melhor entendido quando o algoritmo for apresentado; ele foi im-
plementado usando o funcionamento desta classe.
Neste come¸co tamb´em ´e criado o objeto Sender que tratar´a de enviar as mensagens que
porventura devam ser enviadas a outros processos, e por fim o algoritmo de exclus˜ao m´utua
a ser usado. Se eu tivesse implementado outro algoritmo em outra classe, bastava que ele
implementasse a mesma interface. Para escolher um e n˜ao outro, o arquivo de configura¸c˜ao
mencionado anteriormente seria usado.
Por fim, o construtor desta classe ´e mostrado. Ele chama o construtor da classe
NekoThread, a que ActiveReceiver estende, usando super. Esse construtor recebe um
objeto NekoProcess, que representa o processo que ser´a criado, e um nome ´e passado para
nomear a thread deste novo processo. ´E inicializada tamb´em uma vari´avel que guarda
o pr´oprio PID, me, e outra que guarda o n´umero de processos no ambiente de execu¸c˜ao.
Ambos ser˜ao usados `a frente.
public void run()
{
if(me == (n-1))
System.out.println("Processo " + me + " fazendo logging da execu¸c~ao...");
else {
System.out.println("Processo " + me + " tentando entrar na r.c..");
littleSleep(-1);
algorithm.enterCriticalSection();
6
System.out.println("nn--------------- Processo " + me + " entrou na r.c. ---------------n");
littleSleep(8000);
System.out.println("Processo " + me + " saindo da r.c..");
algorithm.exitCriticalSection();
System.out.println("n----------------- Processo " + me + " saiu da r.c.-----------------nn");
littleSleep(8000);
}
}
Este ´e o m´etodo run que ficar´a executando em loop na thread do processo at´e que o
usu´ario termine a execu¸c˜ao. Basicamente, o que ele faz ´e ficar entrando e saindo da regi˜ao
cr´ıtica, e dormindo v´arias vezes para que a execu¸c˜ao n˜ao fique muito r´apida e possa ser
acompanhada por quem estiver monitorando. Caso o processo atual n˜ao tenha a prioridade
para entrar na r.c. no momento, mensagens ser˜ao impressas na tela esperando que a prior-
idade seja passada a ele. Mas isso ´e tratado na classe que implementa o algoritmo e ser´a
mostrado daqui a pouco. O m´etodo littleSleep coloca o processo em sleep e ´e mostrado
abaixo. O teste inicial ´e feito porque o processo n´umero n-1 ficar´a respons´avel pelo logging
da execu¸c˜ao, e n˜ao executar´a normalmente um n´o do algoritmo.
private void littleSleep(int k) {
int sleepTime;
random = new Random();
if(k != -1) sleepTime = k;
else sleepTime = random.nextInt(8000);
try {
sleep(sleepTime);
} catch (InterruptedException e) {
System.out.println("Erro ao tentar dormir na aplica¸c~ao.");
}
}
}
Esse m´etodo coloca o processo em sleep por tempos aleat´orios de at´e 8 segundos, exceto
quando a thread est´a dentro da r.c., quando ela dorme exatamente por 8 segundos, para
depois acordar e sair da regi˜ao.
Como d´a para perceber, seria poss´ıvel implementar algo ´util durante o tempo que o
processo ganha a prioridade para a regi˜ao cr´ıtica. Basta retirar o sleep de 8 segundos e
colocar algo nessa parte. Aqui o sleep foi usado para demonstrar que a exclus˜ao m´utua
estava funcionando.
7
3.3 A Classe de Inicializa¸c˜ao: MEInitializer.java
Para montar a estrutura de processos necess´aria para a execu¸c˜ao de um algoritmo no Neko
package lamport;
// lse.neko imports:
import lse.neko.NekoProcess;
import lse.neko.NekoProcessInitializer;
import lse.neko.ReceiverInterface;
import lse.neko.SenderInterface;
// other imports:
import org.apache.java.util.Configurations;
public class MEInitializer
implements NekoProcessInitializer
{
public MEInitializer() {
}
public void init(NekoProcess process, Configurations config)
throws Exception
{
/* Primeiro, constr´oi a pilha de protocolos e configura a rede. */
/* Camada da aplica¸c~ao */
Class applicationClass =
Class.forName(config.getString("application"));
Class[] appConstructorParamClasses = { NekoProcess.class };
Object[] appConstructorParams = { process };
ReceiverInterface application = (ReceiverInterface)
applicationClass
.getConstructor(appConstructorParamClasses)
.newInstance(appConstructorParams);
application.setId("app");
/* Camada do algoritmo */
Class algorithmClass =
Class.forName(config.getString("algorithm"));
Class[] algConstructorParamClasses = { NekoProcess.class };
Object[] algConstructorParams = { process };
ReceiverInterface algorithm = (ReceiverInterface)
algorithmClass
.getConstructor(algConstructorParamClasses)
.newInstance(algConstructorParams);
algorithm.setId("alg");
/* Configura a rede. */
SenderInterface net = process.getDefaultNetwork();
Na primeira parte s˜ao criados os objetos usados na pilha de protocolos. Neste caso s˜ao
trˆes: a camada da aplica¸c˜ao, mostrada anteriormente, a camada do algoritmo, usando uma
8
classe que ser´a mostrada a seguir, e o objeto ’net’, que implementa a camada de rede e
tratar´a da troca de mensagens, envio e entrega, entre os processos.
Como pode-se perceber, foi usado uma propriedade do Java chamada Reflection. Isso
´e usado para podermos saber em tempo de execu¸c˜ao qual a classe da aplica¸c˜ao e a do
algoritmo, que dever˜ao estar no arquivo de configura¸c˜ao. Fazendo assim, podemos usar
uma classe de inicializa¸c˜ao apenas (esta mesma) e escrever v´arias aplica¸c˜oes e algoritmos,
bastando mudar uma linha no arquivo de configura¸c˜ao para trocar entre elas. Se n˜ao
quis´essemos isso, bastava criar os objetos fazendo ReceiverInterface application = new
Application(process) para a camada de aplica¸c˜ao, e ReceiverInterface algorithm =
new LamportME(process), mas a flexibilidade e modularidade da nossa implementa¸c˜ao
ficaria prejudicada. Essa decis˜ao fica a cargo do programador.
ReceiverInterface ´e uma interface do Neko que s´o tem um m´etodo: deliver. Ele tem
o papel de gerenciar mensagens recebidas. ActiveReceiver implementa esta interface e
sobrescreve este m´etodo de modo que ele joga numa queue as mensagens recebidas. O
m´etodo receive que ele tamb´em implementa, por sua vez, retira mensagens da fila e entrega
ao processo que o chama, o destinat´ario original delas. A classe da aplica¸c˜ao estende
ActiveReceiver, mas n˜ao faz uso deste m´etodo; a classe que implementa o algoritmo far´a
amplo uso dele, como ser´a mostrado em breve.
/* Segundo, liga as camadas. */
/* Configura o objeto Sender que a aplica¸c~ao pode usar para mandar mensagens. */
applicationClass
.getMethod("setSender", new Class[] { SenderInterface.class })
.invoke(application, new Object[] { net });
/* Configura a camada do algoritmo criada anteriormente para ser usada
* pela camada de aplica¸c~ao quando ela quiser garantir exclus~ao m´utua
* no acesso `a regi~ao cr´ıtica. */
applicationClass
.getMethod("setMEAlgorithm", new Class[] { MEAlgorithm.class })
.invoke(application, new Object[] { algorithm });
/* Configura o objeto Sender que a camada do algoritmo usar´a para mandar mensagens. */
algorithmClass
.getMethod("setSender", new Class[] { SenderInterface.class })
.invoke(algorithm, new Object[] { net });
Continuando a usar Java Reflection, esta segunda parte liga as camadas da pilha de
protocolos do processo que est´a sendo constru´ıdo. Usa m´etodos pr´oprios das classes Appli-
cation, que usa setMEAlgorithm para dizer que o objeto ’algorithm’ criado anteriormente
´e o que deve ser usado como gerenciador da exclus˜ao m´utua, e setSender em ambos Appli-
cation e LamportME (a classe do algoritmo) para dizer que ’net’ ´e o objeto que gerenciar´a
a troca de mensagens na rede entre os processos.
// Terceira, inicia a execu¸c~ao dos protocolos.
application.launch();
algorithm.launch();
9
}
}
Por ´ultimo, as camadas s˜ao lan¸cadas, e por conseguinte o processo. O m´etodo launch
come¸ca a execu¸c˜ao das threads e deve ser usado para este fim.
3.4 A Classe que Implementa o Algoritmo: LamportME.java
Ser´a apresentada finalmente a classe que implementa a execu¸c˜ao do algoritmo de exclus˜ao
m´utua de Lamport. O tamanho dela ´e relativamente extenso, mas cada m´etodo ´e bastante
simples de entender, apenas seguindo `a risca a id´eia original do algoritmo e procurando n˜ao
usar artif´ıcios que escapem do escopo dela. Come¸carei mostrando a vari´aveis usadas e o
m´etodo construtor.
package lamport;
// lse.neko imports:
import lse.neko.ActiveReceiver;
import lse.neko.MessageTypes;
import lse.neko.NekoMessage;
import lse.neko.NekoProcess;
import lse.neko.SenderInterface;
/**
* Lamport’s mutual exclusion algorithm.
* See the paper
* <blockquote>Lam78 <br>
* L.~Lamport. <br>
* Time, clocks, and the ordering of events in a distributed system. <br>
* Commun. ACM, 21(7):558--565, July 1978.</blockquote>
* for details of the algorithm.
*/
public class LamportME
extends ActiveReceiver
implements MEAlgorithm
{
// message types used by this algorithm, also used as states
private static final int req = 9032;
private static final int ack = 9033;
private static final int rel = 9034;
private static final int s = 9042;
private static final int r = 9043;
// registering the message types and associating names with the types.
static
{
MessageTypes.instance().register(req, "req");
MessageTypes.instance().register(ack, "ack");
MessageTypes.instance().register(rel, "rel");
MessageTypes.instance().register(s, "s");
MessageTypes.instance().register(r, "r");
}
private SenderInterface sender;
10
public void setSender(SenderInterface sender)
{
this.sender = sender;
}
/* objeto respons´avel pelo logging. */
/* Somente o processo n-1 ir´a tratar do logging, portanto somente ele usar´a este objeto */
MyLogger logger;
//algorithm variables
/* the id of this process */
private int me;
/* the number of processes */
private int n;
/* list of addresses that includes all processes but this one */
private int[] allButMe;
/* internal Lamport’s scalar clock */
int osn;
/* array of requests */
private NekoMessage[] q;
public LamportME(NekoProcess process) {
super(process, "LamportME-p" + process.getID());
n = process.getN();
me = process.getID();
allButMe = new int[n - 1];
for (int i = 0; i < n - 1; i++) {
allButMe[i] = (i < me) ? i : i + 1;
}
q = new NekoMessage[n];
osn = 0;
logger = new MyLogger(n);
}
Como dito anteriormente, esta classe estende ActiveReceiver e implementa a interface
MEAlgorithm, mostrada primeiro. Os tipos de menagens usados na execu¸c˜ao do algoritmo
s˜ao expostos no come¸co da classe: s˜ao associados n´umeros inteiros com as strings que
nomeiam os tipos da mensagens, e depois esses tipos s˜ao “registrados” no Neko com o uso
do m´etodo register da classe MessageTypes do Neko. Tudo que este m´etodo faz ´e associar
o nome do tipo da mensagem com o inteiro que a representar´a, e para isso usa um objeto
do tipo HashMap.
Ap´os isso s˜ao listadas as vari´aveis. Sender, como sabemos, ser´a o objeto que simular´a a
rede e gerenciar´a o envio de mensagens, e ´e inicializado usando o m´etodo setSender pela
classe de inicializa¸c˜ao, como mostrado anteriormente. Os inteiro me e n guardam o PID
do processo e o n´umero de processos no ambiente de execu¸c˜ao atual, respectivamente. O
vetor de inteiros allButMe serve para guardar o PID de todos os processos menos o pr´oprio
11
PID, e ele ser´a fundamental quando quisermos enviar mensagens em broadcast. O inteiro
osn guardar´a o rel´ogio l´ogico de Lamport do processo atual, e o vetor de NekoMessages q
guardar´a as mensagens que chegarem de outros processos, usando uma l´ogica pr´opria do
algoritmo que ser´a mostrada.
O construtor chama o m´etodo super para criar a thread de execu¸c˜ao e nome´a-la.
Tamb´em inicializa as vari´aveis me e n, os vetores allButMe e q, e o rel´ogio l´ogico osn ´e
setado inicialmente em zero, para mostrar que a execu¸c˜ao ainda n˜ao come¸cou. Cria tamb´em
o objeto respons´avel pelo logging da execu¸c˜ao. Somente o processo n-1 ´e respons´avel pelo
logging, portanto s´o ele usar´a este objeto.
public void run() {
if(me == (n-1)) {
while(true) {
NekoMessage m = receive();
logger.doLogging(m);
}
} else {
while(true) {
NekoMessage m = receive();
switch(m.getType())
{
case req:
requestMessageHandling(m);
break;
case ack:
ackMessageHandling(m);
break;
case rel:
releaseMessageHandling(m);
break;
default:
throw new RuntimeException("Unknown message received");
}
} // while(true)
}//method
Aqui est´a o m´etodo run que deve ser executado pela thread que controla o algoritmo. Se
o processo em quest˜ao n˜ao ´e o n-1, respons´avel pelo logging, o que ele faz ´e ficar num loop
infinito esperando mensagens e escolhendo como tratar elas, com base no seu tipo. Como
explicado antes, receive ´e um m´etodo da classe ActiveReceiver que retira mensagens da fila
de mensagens e entrega-as `a classe que a chamou. As mensagens s˜ao objetos NekoMessage,
e um dos campos que as definem ´e o seu tipo (type), que ´e obtido pelo m´etodo p´ublico
getType. Esses tipos de mensagens s˜ao os registrados anteriormente e associados a inteiros,
12
portanto este m´etodo retorna um inteiro. Vemos que, para cada tipo de mensagem, um
m´eodo foi escrito especialmente para tratar dela. Esses m´etodos ser˜ao mostrados abaixo.
/**
* M´etodo que gerencia mensagens do tipo "req"
* @param m: a mensagem do tipo "req" recebida
*/
private void requestMessageHandling(NekoMessage m) {
// obtem o valor do rel´ogio l´ogico enviado na mensagem
int k = ((Integer)m.getContent()).intValue();
// atualiza o pr´oprio rel´ogio l´ogico
update(k);
// coloca a mensagem no vetor de mensagens
int j = m.getSource();
q[j] = m;
// envia um "ack" de volta ao remetente, carregando o valor do pr´oprio rel´ogio
NekoMessage Ack = new NekoMessage(me, new int[] {j}, getId(), new Integer(osn), ack);
sender.send(Ack);
this.LogMessage(m, osn, r);
this.LogMessage(Ack, osn, s);
}
O tratamento de uma mensagem do tipo “req”, ou request ´e feito neste m´etodo. Primeiro
o rel´ogio l´ogico pr´oprio ´e atualizado com base no rel´ogio l´ogico recebido na mensagem envi-
ado de outro processo. Essa atualiza¸c˜ao ´e feita no m´etodo update. Ap´os isso, a mensagem
´e colocada na fila q na posi¸c˜ao destinada a mensagens recebidas pelo processo remetente
dela. Por fim, uma mensagem de resposta ack ´e enviada em unicast de volta ao processo
remetente, levando consigo o valor do rel´ogio l´ogico do processo atual.
O m´etodo send do objeto sender tem como ´unico argumento a mensagem a ser enviada.
Essa mensagem, encapsulada num objeto do tipo NekoMessage, ´e constru´ıda passando-se ao
m´etodo construtor como argumentos o PID do processo remetente, um vetor representando
os v´arios processos destinat´arios dela (neste caso o vetor s´o contem o remetente da mensagem
“req” recebida), um identificador do protocolo de destino (basta usar o m´etodo getId), o
conte´udo a ser transmitido (neste caso o valor do pr´oprio rel´ogio l´ogico, mas pode ser
qualquer objeto Java) e o tipo da mensagem, nesta ordem. send enviar´a a mensagem sem
que o usu´ario precise se preocupar com mais detalhes.
/**
* M´etodo que trata mensagens "rel"
* @param m: a mensagem do tipo "rel" recebida
*/
private void releaseMessageHandling(NekoMessage m) {
int k = ((Integer)m.getContent()).intValue(); // obtem o valor do rel´ogio l´ogico enviado na mensagem
update(k); // atualiza o pr´oprio rel´ogio l´ogico
int j = m.getSource();
13
q[j] = m;
this.LogMessage(m, osn, r);
}
Este ´e o m´etodo que trata mensagens do tipo “rel”, ou release. S˜ao feitas duas coisas: o
rel´ogio l´ogico pr´oprio ´e atualizado (ele sempre ´e atualiado quando uma mensagem ´e recebido,
como o algoritmo pede para ser feito) e a mensagem ´e colocada no vetor de mensagens, na
posi¸c˜ao destinada a mensagens do remetente dela.
/**
* Method that handles ack messages.
* It also counts the number of acks received since a request was sent.
* @param m: the message received
*/
private void ackMessageHandling(NekoMessage m) {
int k = ((Integer)m.getContent()).intValue(); // obtem o valor do rel´ogio l´ogico enviado na mensagem
update(k); // atualiza o pr´oprio rel´ogio l´ogico
int j = m.getSource();
if(q[j] != null && q[j].getType() != req)
q[j] = m;
this.LogMessage(m, osn, r);
}
Por fim, mensagens do tipo “ack” s˜ao tratadas por este m´etodo. Como nas mensagens do
tipo release, nenhuma resposta precisa ser enviada de volta ao remetente. Portanto, apenas
a atualiza¸c˜ao do rel´ogio l´ogico pr´oprio e o coloca¸c˜ao da mensagem no vetor de mensagens
´e feito. Por´em, essa coloca¸c˜ao do “ack” no vetor deve ser feita com um cuidado extra:
o “ack” n˜ao pode sobrescrever request que por acaso tenham sido enviados pelo processo
remetente deste “ack” anteriormente; somente releases podem fazˆe-lo. Caso contr´ario, o
processo atual poderia julgar que a prioridade para entrar na ´area cr´ıtica pudesse ser sua,
quando na verdade era do processo que teve sua mensagem de request apagada pelo “ack”.
Os dois processos entrariam na r.c. e a exclus˜ao m´utua n˜ao aconteceria. Portanto, esse
cuidado ´e tomado no teste do if, antes de atribuir q[j] a m.
public void enterCriticalSection() {
// multicast de uma mensagem do tipo ’req’ requisitando a entrada na r.c.
NekoMessage Req = new NekoMessage(me, allButMe, getId(), new Integer(osn), req);
sender.send(Req);
this.LogMessage(Req, osn, s);
// adiciona pr´opria requisi¸c~ao `a queue de mensagens e atualiza rel´ogio l´ogico
q[me] = Req; osn = osn + 1;
while(!testPriority()) {
try {
14
sleep(500);
} catch (InterruptedException e) {
System.out.println("Erro ao testar prioridade da thread.");
}
}
}
Este ´e m´etodo que coloca o algoritmo em funcionamento. ´E a implementa¸c˜ao do
enterCriticalSection presente na interface MEAlgorithm e, como sabemos, ´e o que deve
ser chamado quando a aplica¸c˜ao deseja entrar na regi˜ao cr´ıtica. Como a defini¸c˜ao do al-
goritmo pede, o que ´e feito ´e o seguinte: ´e feito um broadcast de uma mensagem “req”,
carregando o pr´oprio PID e o pr´oprio valor do rel´ogio l´ogico do processo atual antes do envio
da mensagem. Essa mensagem “req” tamb´em ´e guardada no pr´oprio vetor de mensagens,
na posi¸c˜ao destinada `as pr´oprias mensagens, e o rel´ogio l´ogico ´e atualizado. Entao, um loop
de teste fica rodando em ciclos de 500ms, testando se o processo atual tem a prioridade
para entrar na regi˜ao cr´ıtica. Quando esta prioridade ´e ganha, o loop termina, e o m´etodo
retorna. A aplica¸c˜ao continua sua execu¸c˜ao, agora com o processo atual utilizando-se da
regi˜ao cr´ıtica.
public void exitCriticalSection() {
// multicast de release
NekoMessage Rel = new NekoMessage(me, allButMe, getId(), new Integer(osn), rel);
sender.send(Rel);
this.LogMessage(Req, osn, s);
q[me] = Rel;
osn = osn + 1;
}
Quando a aplica¸c˜ao termina de usar a regi˜ao cr´ıtica, o m´etodo exitCriticalSection,
tamb´em presente na interface MEAlgorithm, ´e executado. O que ele faz ´e enviar em broad-
cast uma mensagem de release, “rel”, mostrando que terminou de usar sua prioridade e que
outro processo requisitante pode fazer uso da regi˜ao. Essa mensagem de release ´e colocada
no vetor de mensagens, na posi¸c˜ao destinada `as pr´oprias mensagens, e o rel´ogio l´ogico ´e
incrementado de uma unidade de tempo.
private void update(int k)
{
if(osn < k) osn = k;
osn = osn + 1;
}
Este m´etodo simples trata da atualiza¸c˜ao do rel´ogio l´ogico como deve ser feita na especi-
fica¸c˜ao do algoritmo de exclus˜ao m´utua de Lamport. Um inteiro k ´e recebido; se este k for
maior que o rel´ogio l´ogico atual, osn ´e atualizado com esse valor (acontece no caso que uma
mensagem recebida tinha um rel´ogio l´ogico maior que o do processo atual, significando que
15
ele estava atrasado em rela¸c˜ao ao processo remetente). Para terminar, osn ´e aumentado em
mais uma unidade.
public boolean testPriority() {
int myReqClock = ((Integer)q[me].getContent()).intValue();
for(int i = 0; i < n; i++) {
if(q[i] == null) return false;
if(q[i].getType() == req) {
int otherReqClock = ((Integer)q[i].getContent()).intValue();
if((otherReqClock < myReqClock)
|| (otherReqClock == myReqClock && i < me)) {
System.out.println(me + ": Processo " + i + " tem prioridade para entrar na r.c.!"
+ " O rel´ogio da minha req ´e " + myReqClock
+ " e o da dele ´e " + otherReqClock + ".");
return false;
}
}
}
osn = osn + 1;
return true;
}
} // class
Para terminar a descri¸c˜ao desta classe, o ´ultimo m´etodo ´e o que controla o processo
da exclus˜ao m´utua, e portanto ´e o mais importante. O teste para saber se o processo
em quest˜ao vai ganhar ou n˜ao a prioridade de entrada na ´area cr´ıtica ´e feito da seguinte
maneira, como especificado na defini¸c˜ao do algoritmo: o rel´ogio da mensagem de request do
pr´oprio processo presente na fila de mensagens ´e comparado com todas as outras mensagens
de request de outros processos presentes na fila. Se nenhuma delas tiver um rel´ogio de valor
menor que o da “req” pr´opria, ou tiver um valor igual, por´em o PID do outro processo for
maior que o PID do processo que est´a testando a prioridade, ent˜ao esse processo ganha a
prioridade e pode entrar na regi˜ao cr´ıtica. Caso contr´ario, ele n˜ao ganha a prioridade, e o
teste ser´a executado novamente no loop mostrado no m´etodo enterCriticalSection, at´e
que ele ganhe a prioridade e possa usufruir da ´area cr´ıtica. Dessa forma, ´e assegurado que
somente um processo por vez tem acesso a essa ´area, e a exclus˜ao m´utua funciona durante
a execu¸c˜ao da aplica¸c˜ao.
Quando o processo entra na ´area cr´ıtica, o rel´ogio dele aumenta em uma unidade, por
isto ser tratado como um evento na execu¸c˜ao. Isso tamb´em facilita um pouco a visualiza¸c˜ao
das mensagens de release no gr´afico gerado pelo logView, mostrado mais `a frente.
16
3.5 O Arquivo de Configura¸c˜ao: distributed.config
O arquivo de configura¸c˜ao ´e um arquivo de texto com extens˜ao .config, passado como
argumento de linha de comando quando a execu¸c˜ao ´e iniciada, como ser´a mostrada na
parte de execu¸c˜ao. Ele ´e usado para se passar informa¸c˜oes ao Neko durante a inicializa¸c˜ao
da aplica¸c˜ao e que podem ser usadas durante toda ela. Portanto, pode ser uma ferramenta
muito ´util na implementa¸c˜ao de um algoritmo ou aplica¸c˜ao no Neko.
simulation = false
process.num = 4
slave = lsd96, lsd97, lsd98
process.initializer = lamport.MEInitializer
network = lse.neko.networks.comm.TCPNetwork
log = home/usu´ario/Desktop/log.txt
Esse come¸co define se a execu¸c˜ao ser´a uma simula¸c˜ao em m´aquina ´unica ou uma execu¸c˜ao
distribu´ıda entre v´arios computadores, na palavra-chave ’simulation’. Aqui foi feita uma
execu¸c˜ao distribu´ıda real. Depois, define-se o n´umero de processos em ’process.num’; neste
caso s˜ao 4 processos, rodando em 4 m´aquinas diferentes, cada um em uma m´aquina. O
endere¸co das m´aquinas que ser˜ao tratadas como slaves ´e passado na palavra-chave ’slave’.
Esses slaves, na verdade, s˜ao apenas as m´aquinas que ficar˜ao esperando a execu¸c˜ao
come¸car, aguardando que uma das m´aquinas, que det´em este arquivo de configura¸c˜ao, se
conecte a elas, envie o arquivo de configura¸c˜ao e dˆe o sinal para o in´ıcio da execu¸c˜ao. Quando
a execu¸c˜ao come¸ca, n˜ao h´a mais nenhuma distin¸c˜ao entre os n´os: n˜ao h´a mais ’master’ ou
’slave’, e todos rodam o algoritmo de forma igualit´aria.
Em ’process.initializer’ ´e passado o nome completo da classe que trata da inicializa¸c˜ao
da execu¸c˜ao; n´os sabemos que nesta aplica¸c˜ao esta classe ´e a MEInitializer, do pacote
’lamport’.
Por ´ultimo ´e passado que tipo de rede ser´a simulada com base numa classe do Neko
que simula esta rede. Neste caso, uma rede TCP ´e usada, por meio da classe do Neko
lse.neko.networks.comm.TCPNetwork. Outras redes est˜ao dispon´ıveis para serem usadas.
Basta ver na pasta do Neko quais s˜ao elas.
application = lamport.Application
algorithm = lamport.LamportME
#algorithm = mutualexclusion.RicartAgrawalaME
#algorithm = mutualexclusion.SinghalME
Aqui ´e importante. Anteriormente foi mencionada a capacidade do Neko de pegar o
nome das classes que implementar˜ao a pilha de protocolos de cada processo em tempo de
execu¸c˜ao, atrav´es do arquivo de configura¸c˜ao aqui exposto, e para isso usar a propriedade de
Reflection do Java na classe de inicializa¸c˜ao MEInitializer. Aqui est´a a parte em que isto
´e usado. Usando a palavras-chave application e algorithm, podemos dizer quais s˜ao as classes
que funcionar˜ao como a camada de aplica¸c˜ao e a camada do algoritmo do programa. Para
17
acessar estas informa¸c˜oes, o Neko usa um objeto do tipo Configurations, que guarda todas
as informa¸c˜oes expostas no arquivo de configura¸c˜ao. Estas informa¸c˜oes podem ser acessadas
durante a execu¸c˜ao atrav´es deste objeto. Examine a classe MEInitializer para ver como
isso ´e feito e perceber como ´e simples. O m´etodo getString da classe Configurations
retorna uma string associada `a chave que ´e passada a ela como argumento. Por exemplo,
em MEInitializer, para se descobrir o nome da classe que implementar´a o algoritmo, que
aqui se chama lamport.LamportME, ´e passado ao getString a palavra-chave algorithm.
Como pode-se ver, nestas linhas do arquivo de configura¸c˜ao a palavra-chave algorithm ´e
associada com a string lamport.LamportME, que ´e exatamente o nome da classe do algoritmo
de exclus˜ao m´utua que ser´a usado. Portanto, sabendo usar isso e a Reflection do Java,
podemos fazer v´arias implementa¸c˜oes de aplica¸c˜oes, algoritmos, e passar parˆametros como
n´umeros e etc., no arquivo de configura¸c˜ao, para que depois sejam usados em tempo real
na execu¸c˜ao da nossa aplica¸c˜ao no Neko. E isso ´e bastante ´util quando o principal objetivo
de quem est´a usando o Neko ´e simplicidade no uso e rapidez na implementa¸c˜ao e teste de
algoritmos.
4 Execu¸c˜ao
Para come¸car a execu¸c˜ao distribu´ıda do algoritmo ´e necess´ario antes iniciar os n´os slaves.
Slave ´e um n´o que ficar´a escutando numa porta, passada como argumento na linha de co-
mando ou na padr˜ao 8632, se nada for passado. Ele espera receber de um n´o, ´unico entre
todos os processos chamado master, o arquivo de configura¸c˜ao e o sinal para a execu¸c˜ao
come¸car. Assim que recebe esse arquivo, ele monta a pilha de protocolos do processo corre-
spondente a ele (cada n´o roda um processo) e come¸ca a execu¸c˜ao dele. Como ´e mostrado na
Figura 2, deve ser chamada a classe java.lse.comm.Slave do Neko, com o comando java
lse.neko.comm.Slave no terminal para o slave ser iniciado e ficar escutando em alguma
porta. Se uma porta diferente da padr˜ao quiser ser passada, basta adicionar o n´umero dela
como argumento deste comando (java lse.neko.comm.Slave XXXX, com XXXX sendo o
n´umero da porta). Esse procedimento deve ser feito independentemente em cada um dos
n´os que forem come¸car a execu¸c˜ao como slaves; o n´o master executa um comando diferente.
Na Figura 3 ´e mostrado como ´e feito o in´ıcio da execu¸c˜ao atrav´es do n´o master. Usando-
se o comando neko junto com o caminho para o arquivo de configura¸c˜ao mostrado ante-
riormente passado como argumento, e ap´os todos os outros n´os terem sido iniciados como
slaves e estarem conectados entre si (usando ssh, de preferˆencia), a execu¸c˜ao ter´a in´ıcio. Na
Figura 3 o arquivo de configura¸c˜ao estava na mesma pasta em que foi chamado o comando,
portanto n˜ao foi necess´ario passar um caminho absoluto ou relativo at´e ele, apesar de isso
ainda ser poss´ıvel.
A Figura 4 mostra o momento na execu¸c˜ao em que todos os processos est˜ao tentando
conseguir a prioridade para a regi˜ao cr´ıtica, mas somente o processo 1 conseguiu. Isso se
deve a ele ter enviado o req antes do processo 0, e de ou ter enviado antes do outros processos
tamb´em ou ter enviado ao mesmo tempo, sendo que neste caso ele ganha a prioridade por
ter PID menor, da forma como o algoritmo de exclus˜ao m´utua de Lamport decide a garantia
de prioridade `a ´area cr´ıtica.
18
Figura 2: Configurando um n´o slave no Neko antes de come¸car a execu¸c˜ao
Figura 3: Come¸cando a execu¸c˜ao com o n´o master
19
Figura 4: Momento em que um processo consegue acesso `a regi˜ao cr´ıtica
Por fim, a Figura 5 mostra o momento na execu¸c˜ao em que um processo termina de
usar a regi˜ao cr´ıtica e manda em broadcast uma mensagem de release. Assim que os outros
processos recebem essa mensagem, removem o req do processo remetente que estava no
vetor de mensagens e testam novamente a prioridade. Um dos processos descobre que ele ´e
o pr´oximo a receber o acesso `a regi˜ao cr´ıtica e entra, enquanto os outros processos continuam
esperando.
5 As Classes Usadas Para Gerar o Log da Execu¸c˜ao
Foi mencionada a cria¸c˜ao de um arquivo de log durante a execu¸c˜ao do algoritmo. Essa
cria¸c˜ao do log ´e feita por um processo, que aqui foi escolhido como o processo n-1, que
fica esperando mensagens espec´ıficas de gera¸c˜ao de log enviadas pelos outros processos,
que est˜ao envolvidos na execu¸c˜ao do algoritmo. Essas mensagens espec´ıficas carregam as
mensagens que estes processos enviam ou recebem, junto com o rel´ogio l´ogico do processo
quando o evento do envio ou recebimento desta mensagem ocorreu. Isto ´e utilizado para
criar o log, que consiste de linhas que registram detalhes das mensagens. Esse arquivo de
log ´e usado na gera¸c˜ao de um gr´afico da execu¸c˜ao pelo logView, como ser´a mostrado na
pr´oxima se¸c˜ao.
5.1 A Classe Event
package lamport;
import java.io.Serializable;
20
Figura 5: Momento em que um processo libera a r.c. e outro entra
import lse.neko.NekoMessage;
public class Event
implements Serializable {
NekoMessage m;
int osn;
public Event(NekoMessage m, int osn) {
this.m = m;
this.osn = osn;
}
public NekoMessage getMessage() {
return m;
}
public int getOsn() {
return osn;
}
}
A classe Event serve para que as mensagens de log possam ser enviadas pelos processos
participantes da execu¸c˜ao com as mensagens que eles trocam entre si e o tempo l´ogico
que marca o momento em que eles enviam ou recebem estas mensagens. ´E como se as
mensagens rebidas ou enviadas fossem “empacotadas” numa outra mensagem com alguns
metadados necess´ario ao registro dessa mensagem no arquivo de log. Como estes envios ou
recebimentos de mensagens s˜ao chamados de eventos em um ambiente distribu´ıdo a classe
21
recebeu este nome.
´E necess´ario que ela implemente a interface do Java chamada Serializable, caso contr´ario
uma exce¸c˜ao ´e levantada durante a execu¸c˜ao em um ambiente distribu´ıdo. Esta interface
Serializable n˜ao apresenta nenhum m´etodo a ser implementado.
5.2 A Classe MyLogger
MyLogger ´e a classe que trata da cria¸c˜ao e gerenciamento do arquivo de log.
package lamport;
import java.io.BufferedWriter;
import java.io.FileWriter;
import lse.neko.MessageTypes;
import lse.neko.NekoMessage;
import lse.neko.NekoSystem;
import org.apache.java.util.Configurations;
public class MyLogger {
Configurations config;
int n;
boolean firstEntry;
public MyLogger(int n) {
config = NekoSystem.instance().getConfig();
this.n = n;
firstEntry = true;
}
O m´etodo construtor inicia as vari´aveis da classe. Um objeto Configurations ´e criado,
para que o arquivo de configura¸c˜ao da execu¸c˜ao do Neko seja utilizado. Uma entrada nele
guarda o diret´orio e nome do arquivo de log a ser criado. Outra vari´avel iniciada ´e a que
guarda o n´umero de processos na execu¸c˜ao atual. Por fim, uma vari´avel booleana diz se
estamos tratando da primeira entrada no arquivo de log, e por isso ele precisa ser criado do
zero, ou se apenas anexamos a pr´oxima entrada ap´os as outras j´a gravadas, quando ele j´a
foi criado e a execu¸c˜ao atual j´a est´a avan¸cada.
public void doLogging(NekoMessage logMsg) {
FileWriter fw;
config = NekoSystem.instance().getConfig();
String logPath = config.getString("log");
Event event = (Event)logMsg.getContent();
int osn = event.getOsn();
NekoMessage msg = event.getMessage();
22
int eventSource = logMsg.getSource();
int eventType = logMsg.getType();
String[] s = formatString(msg, eventSource, eventType, osn);
for(int i=0; i<s.length;i++) {
if(s[i] != null) {
try{
if(firstEntry) {
fw = new FileWriter(logPath, false);
firstEntry = false;
} else fw = new FileWriter(logPath, true);
BufferedWriter out = new BufferedWriter(fw);
out.write(s[i] + "n");
out.close();
} catch (Exception e) {
System.err.println("Error: " + e.getCause());
}
}
}
}
O m´etodo doLogging trata os eventos de envio e recebimento de mensagens que devem
ser registrados no arquivo de log.
private String[] formatString(NekoMessage m, int source, int type, int osn) {
String[] v = new String[n];
String time = String.valueOf(osn);
String eventType = MessageTypes.instance().getName(type);
int messageSource = m.getSource();
int[] messageDest = m.getDestinations();
String messageType = MessageTypes.instance().getName(m.getType());
String messageContent = String.valueOf(m.getContent());
if(eventType == "r") {
String f = time + " p" + source + " messages e " + eventType + " p" + messageSource +
" p" + source + " " + messageType + " " + messageContent;
v[0] = f;
}
else for(int i=0; i<messageDest.length; i++) {
String f = time + " p" + source + " messages e " + "s" + " p" + source +
" p" + messageDest[i] + " " + messageType + " " + messageContent;
v[i] = f;
}
return v;
}
23
}
E o m´etodo formatString cria as strings que representam cada evento e que ficar˜ao
registradas em cada linha no arquivo de log. O formato das strings pode ser conferido no
arquivo de log mostrado na pr´oxima se¸c˜ao.
6 Visualiza¸c˜ao de Execu¸c˜oes: o LogView
Uma execu¸c˜ao do algoritmo produz um arquivo de log, com o nome e diret´orio passados
no arquivo de configura¸c˜ao mostrado anteriormente, e que registra as mensagens enviadas
e recebidas. Esse registros contˆem detalhes como o processo que enviou, o que recebeu,
o tipo da mensagem, o conte´udo e etc.. Um arquivo de log produzido por uma execu¸c˜ao
distribu´ıda ´e mostrado abaixo. Cada linha mostra, na ordem: o tempo l´ogico em que um
processo enviou ou recebeu uma mensagem, qual processo foi esse, se o evento foi de envio
(“s”) ou recebimento (“r”) de mensagem , qual foi o remetente e o destinat´ario, o tipo de
mensagem e o conte´udo que ela carregou (no caso o conte´udo ´e o rel´ogio l´ogico do processo
no momento de envio da mensagem).
1 p0 messages e r p2 p0 req 0
1 p0 messages e s p0 p2 ack 1
1 p1 messages e r p2 p1 req 0
1 p1 messages e s p1 p2 ack 1
0 p2 messages e s p2 p0 req 0
0 p2 messages e s p2 p1 req 0
2 p2 messages e r p1 p2 ack 1
3 p2 messages e r p0 p2 ack 1
1 p0 messages e s p0 p1 req 1
1 p0 messages e s p0 p2 req 1
4 p2 messages e r p0 p2 req 1
2 p1 messages e r p0 p1 req 1
4 p2 messages e s p2 p0 ack 4
2 p1 messages e s p1 p0 ack 2
3 p0 messages e r p1 p0 ack 2
5 p0 messages e r p2 p0 ack 4
2 p1 messages e s p1 p0 req 2
2 p1 messages e s p1 p2 req 2
6 p0 messages e r p1 p0 req 2
5 p2 messages e r p1 p2 req 2
6 p0 messages e s p0 p1 ack 6
5 p2 messages e s p2 p1 ack 5
6 p1 messages e r p2 p1 ack 5
7 p1 messages e r p0 p1 ack 6
6 p2 messages e s p2 p0 rel 6
6 p2 messages e s p2 p1 rel 6
7 p0 messages e r p2 p0 rel 6
8 p1 messages e r p2 p1 rel 6
8 p0 messages e s p0 p1 rel 8
8 p0 messages e s p0 p2 rel 8
9 p2 messages e r p0 p2 rel 8
9 p1 messages e r p0 p1 rel 8
9 p2 messages e s p2 p0 req 9
9 p2 messages e s p2 p1 req 9
24
11 p1 messages e r p2 p1 req 9
10 p0 messages e r p2 p0 req 9
11 p1 messages e s p1 p2 ack 11
10 p0 messages e s p0 p2 ack 10
12 p2 messages e r p1 p2 ack 11
13 p2 messages e r p0 p2 ack 10
11 p1 messages e s p1 p0 rel 11
11 p1 messages e s p1 p2 rel 11
14 p2 messages e r p1 p2 rel 11
12 p0 messages e r p1 p0 rel 11
12 p0 messages e s p0 p1 req 12
12 p0 messages e s p0 p2 req 12
16 p2 messages e r p0 p2 req 12
13 p1 messages e r p0 p1 req 12
16 p2 messages e s p2 p0 ack 16
13 p1 messages e s p1 p0 ack 13
14 p0 messages e r p1 p0 ack 13
17 p0 messages e r p2 p0 ack 16
16 p2 messages e s p2 p0 rel 16
16 p2 messages e s p2 p1 rel 16
17 p1 messages e r p2 p1 rel 16
18 p0 messages e r p2 p0 rel 16
17 p1 messages e s p1 p0 req 17
17 p1 messages e s p1 p2 req 17
20 p0 messages e r p1 p0 req 17
18 p2 messages e r p1 p2 req 17
18 p2 messages e s p2 p1 ack 18
20 p0 messages e s p0 p1 ack 20
21 p1 messages e r p0 p1 ack 20
22 p1 messages e r p2 p1 ack 18
20 p0 messages e s p0 p1 rel 20
20 p0 messages e s p0 p2 rel 20
23 p1 messages e r p0 p1 rel 20
21 p2 messages e r p0 p2 rel 20
24 p1 messages e s p1 p0 rel 24
24 p1 messages e s p1 p2 rel 24
25 p0 messages e r p1 p0 rel 24
25 p2 messages e r p1 p2 rel 24
Com a ajuda de um programa chamado LogView podemos gerar um gr´afico visualizando
o fluxo dessas mensagens e assim tendo uma id´eia de como ocorre o funcionamento de um
algoritmo implementado no Neko. O LogView foi escrito tamb´em em Java e gera gr´aficos
a partir de logs do Neko com o formato de mensagens mostrado acima, e um arquivo XML
de configura¸c˜ao, em que v´arios detalhes podem ser modificados para gerar gr´aficos mais ao
gosto do usu´ario. O que foi usado aqui neste documento foi ligeiramente modificado por
mim em rela¸c˜ao ao original, que vem junto com o pr´oprio Neko. As modifica¸c˜oes incluem
menos dependˆencia deste arquivo de configura¸c˜ao, sendo necess´ario apenas que o arquivo
de log seja listado nele, e um menu de op¸c˜oes que possibilita mostrar tags sobre as setas
que mostrem conte´udo da mensagem, tipo, tempos de envio e recebimento, entre outras
modifica¸c˜oes. O arquivo de configura¸c˜ao usado nas visualiza¸c˜oes mostradas a seguir ´e este
abaixo:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logView SYSTEM "logView.dtd">
25
<logView>
<display>
<timeAxis xSize="200"/>
<!--timeAxis xSize="100"/>
<processAxis ySize="100"/>
<window xSize="1000" ySize="600"/>
<messages type="REQUEST" color="red"/>
<messages type="ACK" color="blue"/>
<messages type="SCORE" color="yellow"/>
<messages type="RELEASE" color="green"/>
<label halign="left" valign="top" distance="30" percent="0.7"/-->
</display>
<file>
<log filename="/home/alvaro/Desktop/log.txt"/>
</file>
</logView>
Pode se ver que esse arquivo ´e bem pequeno. O cabe¸calho inicial indica a vers˜ao do XML,
a codifica¸c˜ao e a segunda linha indica que este arquivo deve seguir o padr˜ao de um outro
arquivo DTD (Documente Type Definition , que diz como ele deve ser entendido pelo parser
e escrito pelo usu´ario. Este arquivo DTD ´e inclu´ıdo junto com o LogView. A segunda parte
indica detalhes do gr´afico, como espa¸camento nos eixos do tempo e de processos, intervalos
a serem representados, com que cor deve ser mostrado cada tipo de mensagem, entre outros.
Pode-se ver que a parte entre <!-- e --> est´a comentada e n˜ao foi portanto considerada
como configura¸c˜ao v´alida; essa parte ´e mostrada para exemplificar que tipo de op¸c˜oes est˜ao
dispon´ıveis neste arquivo de configura¸c˜ao para personalizar a visualiza¸c˜ao. A ´ultima parte
indica o arquivo de log a ser lido. A princ´ıpio a ´unica parte estritamente necess´aria que este
arquivo deve conter ´e o cabe¸calho e a indica¸c˜ao do arquivo de log; o resto n˜ao ´e necess´ario
e o logView consegue gerar diagramas sem estas outras configura¸c˜oes.
A Figura 6 mostra o come¸co da execu¸c˜ao. Setas azuis representam mensagens de request,
vermelhas de ack e cinzas de release. Os n´umeros sobre as setas s˜ao o conte´udo da mensagem,
que neste caso ´e o rel´ogio l´ogico do processo no momento de envio da mensagem. Nesta
figura visualiza-se as mensagens de request enviadas pelos processos 2, 1 e 0, nesta ordem,
e as mensagens de ack enviadas por outros processos quando estas s˜ao recebidas. As setas
come¸cam no tempo em que s˜ao enviadas pelo processo remetente e terminam no tempo em
que foram recebidas pelo processo destinat´ario.
A Figura 7 mostra o momento em que o processo 2 envia um release em broadcast.
Como ele tinha enviado o request antes de todos os outros processos a prioridade de entrada
na r.c. foi dada a ele primeiro. Ap´os fazer uso dela, ele faz esse broadcast aviasando todos
os outros processos de que a prioridade n˜ao ´e mais dele e que outro processo pode usar a
´area cr´ıtica. Sabendo disso, o processo 0 ganha a prioridade, por ter enviado o “req” antes
do processo 1. E, da mesma forma, ap´os terminar de usar a r.c. ele tamb´em faz broadcast
de “rel”, e as setas cinzas saindo dele e indo para os outros processos mostram isso. ´E
mostrado tamb´em o processo 2 tentando entrar na ´area cr´ıtica novamente, mandando um
broadcast de request, e as setas azuis saindo dele no lado direito da imagem ilustram isso.
Por ´ultimo, a Figura 8 mostra uma parte da execu¸c˜ao em que o Processo 0 termina de
usar a regi˜ao cr´ıtica, faz um broadcast de release e ent˜ao o Processo 1 ganha a prioridade.
26
Figura 6: O come¸co da execu¸c˜ao. Todos os processos tentam entrar na ´area cr´ıtica.
Figura 7: Processo 2 sai da r.c. e Processo 0 entra. Processo 2 tenta novamente entrar na
r.c., fazendo um broadcast de request.
27
Figura 8: Processo 0 deixa a r.c., e o Processo 1 entra. Ap´os o uso da r.c. ambos fazem
broadcast de release.
Ap´os tamb´em terminar de fazer uso desta prioridade ele tamb´em avisa aos outros processos
de que a r.c. est´a sem uso no momento. Importante deixar claro que ao entrar na regi˜ao
cr´ıtica um processo aumenta seu rel´ogio l´ogico em uma unidade, portanto o Processo 1
recebeu o “rel” do Processo 0 no tempo 23 e, ao entrar na ´area cr´ıtica, aumentou seu
rel´ogio l´ogico em 1 unidade, e por isso ele envia o seu broadcast de release no tempo 24.
7 Conclus˜ao
Neste documento, foi apresentado o Neko, uma plataforma Java simples de comunica¸c˜ao
que provˆe suporte `a simula¸c˜ao e prototipagem de algoritmos distribu´ıdos. Atrav´es dele, foi
implementado o algoritmo de exclus˜ao m´utua de Lamport, e dessa forma foi mostrado como
´e simples e pr´atico o uso do Neko para este fim. Na classe que implementava o algoritmo
propriamente, viu-se que n˜ao foi necess´ario muito mais c´odigo al´em do que descrevia a
pr´opria execu¸c˜ao dele. A troca de mensagens ´e feita com m´etodos j´a implementados em
classes do Neko, que tamb´em controla a camada de redes e a troca de mensagens entre as
camadas da pilhas que representam os processos.
Por fim, a execu¸c˜ao desta implementa¸c˜ao gerou um arquivo de log que tornou poss´ıvel a
gera¸c˜ao de um gr´afico, representando a execu¸c˜ao do algoritmo e a troca de mensagens entre
os processos. Cada mensagem foi representada por uma seta entre os processos remetente
e destinat´ario, mostrando tamb´em uma etiqueta com o conte´udo dessas mensagens. Os
tempos de chegada e sa´ıda destas setas eram os pr´oprios tempos l´ogicos em cada processo,
28
tornando poss´ıvel a visualiza¸c˜ao dos eventos na execu¸c˜ao segundo uma ordena¸c˜ao do tipo
happened-before entre todas a mensagens. Sendo assim, a visualiza¸c˜ao gerada se torna um
instrumento muito ´util para se enxergar como um algoritmo distribu´ıdo funciona.
O Neko e o LogView podem ser encontrados nas p´aginas listadas na bibliografia.
Referˆencias
[1] L Lamport. “Time, Clocks, and the Ordering of Events in a Distributed System”. In:
Communications of the ACM 21 (1978), pp. 558–565.
[2] J. Muller, M. Galanthay e P. Urb´an. Rapport de Projet de Semestre Visualisation des
Fichiers de Traces de Neko : LogView. 2002. url: http://ddsg.jaist.ac.jp/neko/
logView/rapport/rapport.pdf.
[3] P. ´Urban, X. D´efago e A. Schiper. “Neko: A Single Environment to Simulate and Pro-
totype Distributed Algotithms”. In: Journal of Information Science and Engineering
18 (2002), pp. 981–997. url: http://ddsg.jaist.ac.jp/pub/UDS02.pdf.
29

Weitere ähnliche Inhalte

Was ist angesagt?

Protocolos TCP IP UDP
Protocolos TCP IP UDPProtocolos TCP IP UDP
Protocolos TCP IP UDPAndré Nobre
 
Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...
Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...
Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...Felipe Alex
 
2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos
2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos
2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicosComputação Depressão
 
Evolução protocolo rdt
Evolução protocolo rdtEvolução protocolo rdt
Evolução protocolo rdtMarllus Lustosa
 
Aula 02-processos-e-threads-tanenbaum-parte-2
Aula 02-processos-e-threads-tanenbaum-parte-2Aula 02-processos-e-threads-tanenbaum-parte-2
Aula 02-processos-e-threads-tanenbaum-parte-2Cristiano Pires Martins
 
Sistemas Operativos - Processos e Threads
Sistemas Operativos - Processos e ThreadsSistemas Operativos - Processos e Threads
Sistemas Operativos - Processos e ThreadsPedro De Almeida
 
Redes de computadores II - 4.Camada de Transporte TCP e UDP
Redes de computadores II - 4.Camada de Transporte TCP e UDPRedes de computadores II - 4.Camada de Transporte TCP e UDP
Redes de computadores II - 4.Camada de Transporte TCP e UDPMauro Tapajós
 
(ACH2044) Sistemas Operacionais - Aula 06
(ACH2044) Sistemas Operacionais - Aula 06(ACH2044) Sistemas Operacionais - Aula 06
(ACH2044) Sistemas Operacionais - Aula 06Norton Trevisan Roman
 
Processos e threads
Processos e threadsProcessos e threads
Processos e threadsSilvino Neto
 
Capítulo 23 comunicação entre processos
Capítulo 23   comunicação entre processosCapítulo 23   comunicação entre processos
Capítulo 23 comunicação entre processosFaculdade Mater Christi
 
Sistemas Operacionais Processos e Threads - Wellington Pinto de Oliveira
Sistemas Operacionais Processos e Threads - Wellington Pinto de OliveiraSistemas Operacionais Processos e Threads - Wellington Pinto de Oliveira
Sistemas Operacionais Processos e Threads - Wellington Pinto de OliveiraWellington Oliveira
 
Redes I -7.Introdução ao TCP/IP
Redes I -7.Introdução ao TCP/IPRedes I -7.Introdução ao TCP/IP
Redes I -7.Introdução ao TCP/IPMauro Tapajós
 
Redes de computadores II - 1.Arquitetura TCP/IP
Redes de computadores II - 1.Arquitetura TCP/IPRedes de computadores II - 1.Arquitetura TCP/IP
Redes de computadores II - 1.Arquitetura TCP/IPMauro Tapajós
 
Arquitetura de Computadores: Processos e Threads
Arquitetura de Computadores: Processos e ThreadsArquitetura de Computadores: Processos e Threads
Arquitetura de Computadores: Processos e ThreadsEvandro Júnior
 
Redes - Camada Enlace
Redes - Camada EnlaceRedes - Camada Enlace
Redes - Camada EnlaceLuiz Arthur
 

Was ist angesagt? (20)

Protocolos TCP IP UDP
Protocolos TCP IP UDPProtocolos TCP IP UDP
Protocolos TCP IP UDP
 
Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...
Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...
Análise de Desempenho de Algoritmos de Controle de Congestionamento TCP utili...
 
2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos
2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos
2009 1 - sistemas operacionais - aula 5 - semaforos e problemas classicos
 
Evolução protocolo rdt
Evolução protocolo rdtEvolução protocolo rdt
Evolução protocolo rdt
 
Tcp udp
Tcp udpTcp udp
Tcp udp
 
Aula 02-processos-e-threads-tanenbaum-parte-2
Aula 02-processos-e-threads-tanenbaum-parte-2Aula 02-processos-e-threads-tanenbaum-parte-2
Aula 02-processos-e-threads-tanenbaum-parte-2
 
Sistemas Operativos - Processos e Threads
Sistemas Operativos - Processos e ThreadsSistemas Operativos - Processos e Threads
Sistemas Operativos - Processos e Threads
 
Redes de computadores II - 4.Camada de Transporte TCP e UDP
Redes de computadores II - 4.Camada de Transporte TCP e UDPRedes de computadores II - 4.Camada de Transporte TCP e UDP
Redes de computadores II - 4.Camada de Transporte TCP e UDP
 
(ACH2044) Sistemas Operacionais - Aula 06
(ACH2044) Sistemas Operacionais - Aula 06(ACH2044) Sistemas Operacionais - Aula 06
(ACH2044) Sistemas Operacionais - Aula 06
 
Processos e threads
Processos e threadsProcessos e threads
Processos e threads
 
Capítulo 23 comunicação entre processos
Capítulo 23   comunicação entre processosCapítulo 23   comunicação entre processos
Capítulo 23 comunicação entre processos
 
Cluster
ClusterCluster
Cluster
 
Sistemas Operacionais Processos e Threads - Wellington Pinto de Oliveira
Sistemas Operacionais Processos e Threads - Wellington Pinto de OliveiraSistemas Operacionais Processos e Threads - Wellington Pinto de Oliveira
Sistemas Operacionais Processos e Threads - Wellington Pinto de Oliveira
 
Camada de enlace parte1
Camada de enlace   parte1Camada de enlace   parte1
Camada de enlace parte1
 
Redes I -7.Introdução ao TCP/IP
Redes I -7.Introdução ao TCP/IPRedes I -7.Introdução ao TCP/IP
Redes I -7.Introdução ao TCP/IP
 
Redes de computadores II - 1.Arquitetura TCP/IP
Redes de computadores II - 1.Arquitetura TCP/IPRedes de computadores II - 1.Arquitetura TCP/IP
Redes de computadores II - 1.Arquitetura TCP/IP
 
Arquitetura de Computadores: Processos e Threads
Arquitetura de Computadores: Processos e ThreadsArquitetura de Computadores: Processos e Threads
Arquitetura de Computadores: Processos e Threads
 
Redes - Camada Enlace
Redes - Camada EnlaceRedes - Camada Enlace
Redes - Camada Enlace
 
Camada de enlace parte2
Camada de enlace   parte2Camada de enlace   parte2
Camada de enlace parte2
 
Pvm
PvmPvm
Pvm
 

Ähnlich wie Algoritmo de Lamport no Neko

Artigo Wireshark Luiz Felipe
Artigo Wireshark Luiz FelipeArtigo Wireshark Luiz Felipe
Artigo Wireshark Luiz Felipeluizfelipemz
 
1º Artigo De GerêNcia De Redes
1º Artigo De GerêNcia De Redes1º Artigo De GerêNcia De Redes
1º Artigo De GerêNcia De Redesmanustm
 
Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...
Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...
Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...Dalton Valadares
 
Aula Simulação por Eventos Discretos
Aula Simulação por Eventos DiscretosAula Simulação por Eventos Discretos
Aula Simulação por Eventos DiscretosAntonio Marcos Alberti
 
Analisando O Caminho Dos Pacotes No Wireshark Stalin
Analisando O Caminho Dos Pacotes No Wireshark   StalinAnalisando O Caminho Dos Pacotes No Wireshark   Stalin
Analisando O Caminho Dos Pacotes No Wireshark Stalinstalinstm
 
Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...
Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...
Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...Ricardo Brasil
 
Segurança na Interoperabilidade de Redes TCP IP
Segurança na  Interoperabilidade de Redes TCP IPSegurança na  Interoperabilidade de Redes TCP IP
Segurança na Interoperabilidade de Redes TCP IPBruno Milani
 
Análise de Tráfego da Rede Utilizando o Wireshark
Análise de Tráfego da Rede Utilizando o WiresharkAnálise de Tráfego da Rede Utilizando o Wireshark
Análise de Tráfego da Rede Utilizando o WiresharkIgor Bruno
 
Artigo Analise De Redes Pelo Wireshark Igor
Artigo   Analise De Redes Pelo Wireshark   IgorArtigo   Analise De Redes Pelo Wireshark   Igor
Artigo Analise De Redes Pelo Wireshark IgorIgor Bruno
 
Proposta lucas simon-rodrigues-magalhaes
Proposta lucas simon-rodrigues-magalhaesProposta lucas simon-rodrigues-magalhaes
Proposta lucas simon-rodrigues-magalhaeslucassrod
 
Distributed Systems - Exercises
Distributed Systems - ExercisesDistributed Systems - Exercises
Distributed Systems - ExercisesMichel Alves
 
Artigo Atividade 1 Gredes
Artigo Atividade 1 GredesArtigo Atividade 1 Gredes
Artigo Atividade 1 GredesAlbarado Junior
 

Ähnlich wie Algoritmo de Lamport no Neko (20)

Algoritmo lamport
Algoritmo lamportAlgoritmo lamport
Algoritmo lamport
 
Artigo Wireshark
Artigo WiresharkArtigo Wireshark
Artigo Wireshark
 
Artigo Wireshark Luiz Felipe
Artigo Wireshark Luiz FelipeArtigo Wireshark Luiz Felipe
Artigo Wireshark Luiz Felipe
 
Protocolos logicos de_comunicacao
Protocolos logicos de_comunicacaoProtocolos logicos de_comunicacao
Protocolos logicos de_comunicacao
 
1º Artigo De GerêNcia De Redes
1º Artigo De GerêNcia De Redes1º Artigo De GerêNcia De Redes
1º Artigo De GerêNcia De Redes
 
Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...
Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...
Um Injetor de Falhas para a Avaliação de Aplicações Distribuídas Baseadas no ...
 
Aula Simulação por Eventos Discretos
Aula Simulação por Eventos DiscretosAula Simulação por Eventos Discretos
Aula Simulação por Eventos Discretos
 
Analisando O Caminho Dos Pacotes No Wireshark Stalin
Analisando O Caminho Dos Pacotes No Wireshark   StalinAnalisando O Caminho Dos Pacotes No Wireshark   Stalin
Analisando O Caminho Dos Pacotes No Wireshark Stalin
 
Artigo Redes Jonnes
Artigo Redes JonnesArtigo Redes Jonnes
Artigo Redes Jonnes
 
Artigo Redes Jonnes
Artigo Redes JonnesArtigo Redes Jonnes
Artigo Redes Jonnes
 
Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...
Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...
Aplicação das Redes Neuronais Artificiais do software STATISTICA 7.0: O caso ...
 
APM Model in .NET - PT-pt
APM Model in .NET - PT-ptAPM Model in .NET - PT-pt
APM Model in .NET - PT-pt
 
Segurança na Interoperabilidade de Redes TCP IP
Segurança na  Interoperabilidade de Redes TCP IPSegurança na  Interoperabilidade de Redes TCP IP
Segurança na Interoperabilidade de Redes TCP IP
 
Análise de Tráfego da Rede Utilizando o Wireshark
Análise de Tráfego da Rede Utilizando o WiresharkAnálise de Tráfego da Rede Utilizando o Wireshark
Análise de Tráfego da Rede Utilizando o Wireshark
 
Artigo Analise De Redes Pelo Wireshark Igor
Artigo   Analise De Redes Pelo Wireshark   IgorArtigo   Analise De Redes Pelo Wireshark   Igor
Artigo Analise De Redes Pelo Wireshark Igor
 
Proposta lucas simon-rodrigues-magalhaes
Proposta lucas simon-rodrigues-magalhaesProposta lucas simon-rodrigues-magalhaes
Proposta lucas simon-rodrigues-magalhaes
 
Protocolos TCP/IP
Protocolos TCP/IPProtocolos TCP/IP
Protocolos TCP/IP
 
Distributed Systems - Exercises
Distributed Systems - ExercisesDistributed Systems - Exercises
Distributed Systems - Exercises
 
Artigo Atividade 1 Gredes
Artigo Atividade 1 GredesArtigo Atividade 1 Gredes
Artigo Atividade 1 Gredes
 
Cynthia Artigo
Cynthia  ArtigoCynthia  Artigo
Cynthia Artigo
 

Algoritmo de Lamport no Neko

  • 1. Implementa¸c˜ao do algoritmo de exclus˜ao m´utua de Lamport usando um framework de implementa¸c˜ao de algoritmos distribu´ıdos Alvaro Augustho de Souza Silva Orientador: Luiz Eduardo Buzato 4 de novembro de 2011 Resumo Neste documento ser´a apresentada uma implementa¸c˜ao em Java do algoritmo de ex- clus˜ao m´utua de Lamport [1], baseado em rel´ogio l´ogicos, com o uso do framework Neko. O Neko [3] ´e um framework desenvolvido para facilitar o desenvolvimento e im- plementa¸c˜ao de algoritmos distribu´ıdos. O escolha do algoritmo de Lamport foi com o objetivo de apresentar o funcionamento deste em um ambiente distribu´ıdo real (no caso um cluster de 5 computadores) e os poss´ıveis usos dele e do pr´oprio Neko. Foi im- plementado um mecanismo de gera¸c˜ao de log da troca de mensagens entre os processos em um arquivo de texto simples, e com este log pˆode-se gerar um diagrama visual da execu¸c˜ao correspondente a ele, em que um gr´afico representava a troca de mensagens entre os n´os por meio de setas coloridas e etiquetas mostrando o tipos de mensagens sendo enviadas. O programa usado para gerar este diagrama chama-se LogView e foi feito em um projeto de semestre num instituto de tecnologia na Sui¸ca, com supervis˜ao de um dos autores do Neko. O LogView [2] usado aqui foi modificado por mim, de forma que ele pudesse exibir mais detalhes da troca de mensagens que a vers˜ao original n˜ao exibia, como os tempos de envio e recebimento da mensagem, o tipo dela, algumas mudan¸cas na interface de forma a tornar a visualiza¸c˜ao um pouco mais clara, entre outras modifica¸c˜oes. 1 Introdu¸c˜ao O problema da exclus˜ao m´utua ´e um ponto crucial de preocupa¸c˜ao em sistemas que com- partilham recursos entre processos em v´arios computadores, ou mesmo threads de execu¸c˜ao de um processo sendo executado em um ´unico computador. A quest˜ao ´e evitar que dois ou mais processos ou threads tenham acesso simultaneamente a este recurso compartilhado, comumente chamado de regi˜ao cr´ıtica (r.c.). A solu¸c˜ao apresentada para este problema por Leslie Lamport em seu artigo Time, clocks, and the ordering of events in a distributed system de 1978 se baseia na id´eia de timestamps. Timestamps s˜ao contadores monotonica- mente crescentes mantidos unicamente por cada processo, e que seguem as trˆes seguintes regras: 1
  • 2. 1. Um processo incrementa seu contador antes de cada evento neste processo. 2. Quando um processo envia uma mensagem, ele inclui seu pr´oprio contador na men- sagem enviada. 3. Ao receber uma mensagem, o processo recipiente atualiza seu pr´oprio contador para que ele seja maior que o m´aximo entre seu valor atual e o valor recebido na mensagem, antes que ele considere a mensagem como recebida. Com este contador e estas regras simples ´e poss´ıvel estabelecer uma rela¸c˜ao de an- tecedˆencia entre eventos numa execu¸c˜ao distribu´ıda. A partir da id´eia de timestamps o algoritmo de exclus˜ao m´utua de Lamport se desenvolve. Antes de mostr´a-lo ´e necess´ario mencionar um elemento crucial em seu funcionamento: cada processo mant´em um vetor de mensagens com tamanho igual ao n´umero de processos presentes no ambiente distribu´ıdo, incluindo ele pr´oprio. Cada posi¸c˜ao deste vetor guarda a ´ultima mensagem recebida pelo processo corresponte a esta posi¸c˜ao (todos os processos tem identificadores, que v˜ao de 0 a n-1, sendo n o n´umero de processos; este identificador se chama PID). Assim, cada processo consegue saber o timestamp da ´ultima mensagem recebida de cada outro processo, e isso ´e necess´ario para o funcionamente do algoritmo. Quando este texto se referir ao vetor de mensagens ou simplesmente ao vetor ele estar´a se referindo a este elemento. O algoritmo se baseia nas seguintes regras: • Envio de requisi¸c˜ao para entrada na r.c.: um processo envia uma mensagem de req- uisi¸c˜ao req para todos os outros processos com um timestamp (valor do seu rel´ogio l´ogico atual), e adiciona a requisi¸c˜ao rotulada com o timestamp ao vetor de mensagens, na posi¸c˜ao correspondente a ele mesmo. • Recep¸c˜ao de requisi¸c˜ao: a mensagem de requisi¸c˜ao, com o timestamp gravado nela, ´e colocada no vetor na posi¸c˜ao correspondente ao processo remetente, e um reconheci- mento ack ´e enviado a ele. • Libera¸c˜ao da r.c.: um processo envia uma mensagem de libera¸c˜ao rel para todos os outros processos. • Recep¸c˜ao de rel: a requisi¸c˜ao correspondente `a libera¸c˜ao ´e removida do vetor de mensagens. • Entrada na r.c. (guarda): um processo determina que pode entrar na r.c. se e somente se: 1. ele tem uma requisi¸c˜ao no vetor com timestamp t 2. t ´e o menor timestamp no vetor 3. se t for igual a outro timestamp no vetor, o acesso ser´a concedido ao processo que tiver o menor identificador de processo (PID) 2
  • 3. A entrada na r.c. ´e determinada pela ordena¸c˜ao total gerada pelos rel´ogios l´ogicos. Quando a guarda de de Pi torna-se verdadeira (o processo ´e autorizado a entrar na r.c.) n˜ao existe outra requisi¸c˜ao no sistema (no vetor de mensagens ou em trˆansito) que tenha timestamp menor que o da requisi¸c˜ao de Pi. Para que ele entre ´e necess´ario que o estado das demais posi¸c˜oes do vetor de mensagens tenha sido atualizado com timestamps maiores que o da requisi¸c˜ao de Pi; essa atualiza¸c˜ao ´e garantida pelo ack e pelo fato dos canais de comunica¸c˜ao serem FIFO. Portanto, ´e garantida a justi¸ca e os deadlocks devido `a ordena¸c˜ao total. J´a a exclus˜ao m´utua ´e garantida porque o processo que entra na regi˜ao cr´ıtica somente remover´a a sua requisi¸c˜ao do vetor dos demais processos depois que a tiver deixado e enviado uma mensagem de release com rel´ogio l´ogico maior para todos eles. Novamente o mecanismo de rel´ogio l´ogico garante que o algoritmo est´a correto. Este texto trata da implementa¸c˜ao deste algoritmo para a execu¸c˜ao em um cluster de cinco computadores, sendo cada computador um processo na execu¸c˜ao, atrav´es do uso de um framework para a constru¸c˜ao de algoritmos distribu´ıdos chamado Neko. O Neko ´e um framework desenvolvido em Java, e dispon´ıvel na internet para download e uso (ver bibliografia). Ele foi escrito no Distributed Systems Laboratory do Swiss Federal Institute of Technology in Lausanne (EPFL), por X´avier D´efago e P´eter Urb´an, em conjunto com outros estudantes. Seu desenvolvimento continuou no Japan Advanced Institute of Technology (JAIST), grupo DDSG, e na Universid´ad Polit´ecnica de Madrid, laborat´orio LSD. Ele ´e mantido principalmente por P´eter Urb´an, e veio sendo desenvolvido desde 2000. Esta pequena introdu¸c˜ao explicou brevemente o algoritmo de exclus˜ao m´utua aqui im- plementado e deu uma id´eia de o que ´e o Neko. O restante do texto est´a estruturado da seguinte forma. A se¸c˜ao 2 explicar´a em mais profundidade o que ´e e como funciona o framework Neko, e como algoritmos distribu´ıdos s˜ao implementados e executados quando se usa ele. Em seguida, na se¸c˜ao 3 ´e apresentada a implementa¸c˜ao do algoritmo em si, explicando cada classe em detalhes, a forma como elas se ligam, como a pilha de protocolos em cada processo ´e montada antes da execu¸c˜ao, e como ela se comporta durante a execu¸c˜ao, seja ela simulada (que n˜ao ser´a mostrada aqui) ou distribu´ıda num ambiente de rede real. Em seguida, na se¸c˜ao 4 finalmente ´e apresentado como essa implementa¸c˜ao ´e colocada em funcionamento, mostrando como inicializar cada processo e a execu¸c˜ao em si, e o que ´e mostrado indicando que o algoritmo est´a realmente sendo executado. Ap´os isso, na se¸c˜ao 5 s˜ao mostradas as classes respons´aveis pela gera¸c˜ao do log da execu¸c˜ao. O log gerado numa execu¸c˜ao e o uso dele para criar o diagrama de execu¸c˜ao no LogView ´e mostrado na se¸c˜ao 6. Por fim, uma conclus˜ao ´e apresentada na se¸c˜ao 7, listando os pr´os e os contras do Neko e comentando a vantagem de se poder visualizar o funcionamento de um algoritmo executando de forma distribu´ıda de verdade num diagrama visual e intuitivo que mostra a troca de mensagens que est´a ocorrendo. 2 Neko: Arquitetura e Funcionamento O Neko ´e uma ´e uma plataforma de comunica¸c˜ao que permite tanto simular um algoritmo distribu´ıdo numa ´unica m´aquina quanto execu¸c˜oes verdadeiramente distribu´ıdas em v´arias 3
  • 4. Figura 1: Estrutura em camadas de uma aplica¸c˜ao usando o Neko m´aquinas, numa rede de computadores, usando a mesma implementa¸c˜ao para um algoritmo. Ele foi feito em Java, com o objetivo de simplificar o desenvolvimento de algoritmos dis- tribu´ıdos, diminuindo o tempo de implementa¸c˜ao e testes e facilitando a execu¸c˜ao e estudo dos resultados. Como ´e mostrado na Figura 1, a arquitetura do Neko consiste de duas partes principais: aplica¸c˜ao (aplication) e rede (networks). No n´ıvel da aplica¸c˜ao, uma cole¸c˜ao de processos (numerados de 0 a n-1) comunicam-se atrav´es de uma interface simples de troca de men- sagens: um processo sender coloca sua mensagem na rede com a primitiva ass´ıncrona send e a rede ent˜ao entrega essa mensagem ao processo destinat´ario com a primitiva deliver. Processo s˜ao implementados como programas em v´arias camadas. A comunica¸c˜ao n˜ao ´e uma caixa-preta: a infrastrutura de troca de mensagens pode ser controlada de v´arias maneiras. Primeiro, uma rede pode ser instanciada de uma cole¸c˜ao de redes pr´e-definidas, como uma rede real usando TCP/IP ou uma Ethernet simulada. Segundo, o Neko consegue controlar v´arias redes em paralelo. Terceiro, redes que extendem e modificam as existentes s˜ao facilmente implement´aveis. O fato de o c´odigo ser aberto e a linguagem de implementa¸c˜ao ser Java facilitam muito a moldagem da plataforma ao gosto do usu´ario. Mas, em geral, n˜ao ´e necess´aria a modifica¸c˜ao do c´odigo-fonte do Neko; ele j´a oferece uma plataforma pronta, simples e completa para o teste e implementa¸c˜ao de muitos algoritmos. O algoritmo de exclus˜ao m´utua de Lamport foi implementado apenas usando as ferramentas e classes de Java disponibilizadas por ele na sua vers˜ao atual (Neko 1.0 beta 1, colocada `a disposi¸c˜ao para download e uso no site oficial do projeto Neko em 16 de junho de 2009). Para a execu¸c˜ao do algoritmo um arquivo simples de configura¸c˜ao ´e necess´ario. O que foi usado aqui ser´a apresentado e explicado para maior clareza de sua fun¸c˜ao e para mostrar melhor um detalhe importante da plataforma Neko. Os c´odigos das classes representando o algoritmo de Lamport tamb´em ser˜ao explicados detalhadamente, e assim tanto ele quanto 4
  • 5. a plataforma devem ser bem apresentados. Vamos `a exposi¸c˜ao do c´odigo implementando o algoritmo e a exclus˜ao m´utua distribu´ıda. 3 A Implementa¸c˜ao do Algoritmo 3.1 A Interface MEAlgorithm A exposi¸c˜ao da implementa¸c˜ao da exclus˜ao m´utua ser´a come¸cada mostrando uma interface simples, criada com o objetivo de simplificar o programa: package lamport; public interface MEAlgorithm { public void enterCriticalSection(); public void exitCriticalSection(); } Os dois m´etodos da interface s˜ao enterCriticalSection e exitCriticalSection. Como os pr´oprios nomes dizem, o primeiro ´e chamado quando uma classe que use um objeto que faz uso desta interface deseja entrar numa se¸c˜ao cr´ıtica do c´odigo, que usa dados compartilhados entre processos, e para isso quer garantir acesso exclusivo e sem risco de encontrar ou criar incosistˆencias. Quando a aplica¸c˜ao terminar de usar essa regi˜ao cr´ıtica ela deve chamar exitCriticalSection, que faz o que for necess´ario para liberar o acesso `a essa regi˜ao e deixar os outros processos cientes de que ningu´em mais tem a prioridade. 3.2 A Classe Que Faz Uso da Exclus˜ao M´utua: Application.java Antes de mostrar a implementa¸c˜ao do algoritmo propriamente dita ser´a apresentada a classe que chama os m´etodos da interface acima para entrar e sair da regi˜ao cr´ıtica sem o perigo de gerar incosistˆencias de dados: package lamport; // java imports: import java.util.Random; //lse.neko imports: import lse.neko.ActiveReceiver; import lse.neko.NekoProcess; import lse.neko.SenderInterface; public class Application extends ActiveReceiver { private SenderInterface sender; public void setSender(SenderInterface sender) { this.sender = sender; } /* O algoritmo de exclus~ao m´utua deste processo. * Este m´etodo ´e usado pela classe MEInitializer no momento da cria¸c~ao dos processos. */ private MEAlgorithm algorithm; 5
  • 6. public void setMEAlgorithm(MEAlgorithm algorithm) { this.algorithm = algorithm; } /* o PID deste processo */ private int me; /* o n´umero de processos private int n; /* objeto gerador de n´umeros aleat´orios */ private Random random; public Application(NekoProcess process) { super(process, "[Aplica¸c~ao] Thread-p" + process.getID()); me = process.getID(); n = process.getN(); } A primeira parte do c´odigo faz as importa¸c˜oes de classes necess´arias. Esta classe estende ActiveReceiver, uma classe do Neko que cria objetos capazes de receber ativamente men- sagens que forem enviadas a eles, ao inv´es de simplesmente esperar elas serem entregues. Seu funcionamento ser´a melhor entendido quando o algoritmo for apresentado; ele foi im- plementado usando o funcionamento desta classe. Neste come¸co tamb´em ´e criado o objeto Sender que tratar´a de enviar as mensagens que porventura devam ser enviadas a outros processos, e por fim o algoritmo de exclus˜ao m´utua a ser usado. Se eu tivesse implementado outro algoritmo em outra classe, bastava que ele implementasse a mesma interface. Para escolher um e n˜ao outro, o arquivo de configura¸c˜ao mencionado anteriormente seria usado. Por fim, o construtor desta classe ´e mostrado. Ele chama o construtor da classe NekoThread, a que ActiveReceiver estende, usando super. Esse construtor recebe um objeto NekoProcess, que representa o processo que ser´a criado, e um nome ´e passado para nomear a thread deste novo processo. ´E inicializada tamb´em uma vari´avel que guarda o pr´oprio PID, me, e outra que guarda o n´umero de processos no ambiente de execu¸c˜ao. Ambos ser˜ao usados `a frente. public void run() { if(me == (n-1)) System.out.println("Processo " + me + " fazendo logging da execu¸c~ao..."); else { System.out.println("Processo " + me + " tentando entrar na r.c.."); littleSleep(-1); algorithm.enterCriticalSection(); 6
  • 7. System.out.println("nn--------------- Processo " + me + " entrou na r.c. ---------------n"); littleSleep(8000); System.out.println("Processo " + me + " saindo da r.c.."); algorithm.exitCriticalSection(); System.out.println("n----------------- Processo " + me + " saiu da r.c.-----------------nn"); littleSleep(8000); } } Este ´e o m´etodo run que ficar´a executando em loop na thread do processo at´e que o usu´ario termine a execu¸c˜ao. Basicamente, o que ele faz ´e ficar entrando e saindo da regi˜ao cr´ıtica, e dormindo v´arias vezes para que a execu¸c˜ao n˜ao fique muito r´apida e possa ser acompanhada por quem estiver monitorando. Caso o processo atual n˜ao tenha a prioridade para entrar na r.c. no momento, mensagens ser˜ao impressas na tela esperando que a prior- idade seja passada a ele. Mas isso ´e tratado na classe que implementa o algoritmo e ser´a mostrado daqui a pouco. O m´etodo littleSleep coloca o processo em sleep e ´e mostrado abaixo. O teste inicial ´e feito porque o processo n´umero n-1 ficar´a respons´avel pelo logging da execu¸c˜ao, e n˜ao executar´a normalmente um n´o do algoritmo. private void littleSleep(int k) { int sleepTime; random = new Random(); if(k != -1) sleepTime = k; else sleepTime = random.nextInt(8000); try { sleep(sleepTime); } catch (InterruptedException e) { System.out.println("Erro ao tentar dormir na aplica¸c~ao."); } } } Esse m´etodo coloca o processo em sleep por tempos aleat´orios de at´e 8 segundos, exceto quando a thread est´a dentro da r.c., quando ela dorme exatamente por 8 segundos, para depois acordar e sair da regi˜ao. Como d´a para perceber, seria poss´ıvel implementar algo ´util durante o tempo que o processo ganha a prioridade para a regi˜ao cr´ıtica. Basta retirar o sleep de 8 segundos e colocar algo nessa parte. Aqui o sleep foi usado para demonstrar que a exclus˜ao m´utua estava funcionando. 7
  • 8. 3.3 A Classe de Inicializa¸c˜ao: MEInitializer.java Para montar a estrutura de processos necess´aria para a execu¸c˜ao de um algoritmo no Neko package lamport; // lse.neko imports: import lse.neko.NekoProcess; import lse.neko.NekoProcessInitializer; import lse.neko.ReceiverInterface; import lse.neko.SenderInterface; // other imports: import org.apache.java.util.Configurations; public class MEInitializer implements NekoProcessInitializer { public MEInitializer() { } public void init(NekoProcess process, Configurations config) throws Exception { /* Primeiro, constr´oi a pilha de protocolos e configura a rede. */ /* Camada da aplica¸c~ao */ Class applicationClass = Class.forName(config.getString("application")); Class[] appConstructorParamClasses = { NekoProcess.class }; Object[] appConstructorParams = { process }; ReceiverInterface application = (ReceiverInterface) applicationClass .getConstructor(appConstructorParamClasses) .newInstance(appConstructorParams); application.setId("app"); /* Camada do algoritmo */ Class algorithmClass = Class.forName(config.getString("algorithm")); Class[] algConstructorParamClasses = { NekoProcess.class }; Object[] algConstructorParams = { process }; ReceiverInterface algorithm = (ReceiverInterface) algorithmClass .getConstructor(algConstructorParamClasses) .newInstance(algConstructorParams); algorithm.setId("alg"); /* Configura a rede. */ SenderInterface net = process.getDefaultNetwork(); Na primeira parte s˜ao criados os objetos usados na pilha de protocolos. Neste caso s˜ao trˆes: a camada da aplica¸c˜ao, mostrada anteriormente, a camada do algoritmo, usando uma 8
  • 9. classe que ser´a mostrada a seguir, e o objeto ’net’, que implementa a camada de rede e tratar´a da troca de mensagens, envio e entrega, entre os processos. Como pode-se perceber, foi usado uma propriedade do Java chamada Reflection. Isso ´e usado para podermos saber em tempo de execu¸c˜ao qual a classe da aplica¸c˜ao e a do algoritmo, que dever˜ao estar no arquivo de configura¸c˜ao. Fazendo assim, podemos usar uma classe de inicializa¸c˜ao apenas (esta mesma) e escrever v´arias aplica¸c˜oes e algoritmos, bastando mudar uma linha no arquivo de configura¸c˜ao para trocar entre elas. Se n˜ao quis´essemos isso, bastava criar os objetos fazendo ReceiverInterface application = new Application(process) para a camada de aplica¸c˜ao, e ReceiverInterface algorithm = new LamportME(process), mas a flexibilidade e modularidade da nossa implementa¸c˜ao ficaria prejudicada. Essa decis˜ao fica a cargo do programador. ReceiverInterface ´e uma interface do Neko que s´o tem um m´etodo: deliver. Ele tem o papel de gerenciar mensagens recebidas. ActiveReceiver implementa esta interface e sobrescreve este m´etodo de modo que ele joga numa queue as mensagens recebidas. O m´etodo receive que ele tamb´em implementa, por sua vez, retira mensagens da fila e entrega ao processo que o chama, o destinat´ario original delas. A classe da aplica¸c˜ao estende ActiveReceiver, mas n˜ao faz uso deste m´etodo; a classe que implementa o algoritmo far´a amplo uso dele, como ser´a mostrado em breve. /* Segundo, liga as camadas. */ /* Configura o objeto Sender que a aplica¸c~ao pode usar para mandar mensagens. */ applicationClass .getMethod("setSender", new Class[] { SenderInterface.class }) .invoke(application, new Object[] { net }); /* Configura a camada do algoritmo criada anteriormente para ser usada * pela camada de aplica¸c~ao quando ela quiser garantir exclus~ao m´utua * no acesso `a regi~ao cr´ıtica. */ applicationClass .getMethod("setMEAlgorithm", new Class[] { MEAlgorithm.class }) .invoke(application, new Object[] { algorithm }); /* Configura o objeto Sender que a camada do algoritmo usar´a para mandar mensagens. */ algorithmClass .getMethod("setSender", new Class[] { SenderInterface.class }) .invoke(algorithm, new Object[] { net }); Continuando a usar Java Reflection, esta segunda parte liga as camadas da pilha de protocolos do processo que est´a sendo constru´ıdo. Usa m´etodos pr´oprios das classes Appli- cation, que usa setMEAlgorithm para dizer que o objeto ’algorithm’ criado anteriormente ´e o que deve ser usado como gerenciador da exclus˜ao m´utua, e setSender em ambos Appli- cation e LamportME (a classe do algoritmo) para dizer que ’net’ ´e o objeto que gerenciar´a a troca de mensagens na rede entre os processos. // Terceira, inicia a execu¸c~ao dos protocolos. application.launch(); algorithm.launch(); 9
  • 10. } } Por ´ultimo, as camadas s˜ao lan¸cadas, e por conseguinte o processo. O m´etodo launch come¸ca a execu¸c˜ao das threads e deve ser usado para este fim. 3.4 A Classe que Implementa o Algoritmo: LamportME.java Ser´a apresentada finalmente a classe que implementa a execu¸c˜ao do algoritmo de exclus˜ao m´utua de Lamport. O tamanho dela ´e relativamente extenso, mas cada m´etodo ´e bastante simples de entender, apenas seguindo `a risca a id´eia original do algoritmo e procurando n˜ao usar artif´ıcios que escapem do escopo dela. Come¸carei mostrando a vari´aveis usadas e o m´etodo construtor. package lamport; // lse.neko imports: import lse.neko.ActiveReceiver; import lse.neko.MessageTypes; import lse.neko.NekoMessage; import lse.neko.NekoProcess; import lse.neko.SenderInterface; /** * Lamport’s mutual exclusion algorithm. * See the paper * <blockquote>Lam78 <br> * L.~Lamport. <br> * Time, clocks, and the ordering of events in a distributed system. <br> * Commun. ACM, 21(7):558--565, July 1978.</blockquote> * for details of the algorithm. */ public class LamportME extends ActiveReceiver implements MEAlgorithm { // message types used by this algorithm, also used as states private static final int req = 9032; private static final int ack = 9033; private static final int rel = 9034; private static final int s = 9042; private static final int r = 9043; // registering the message types and associating names with the types. static { MessageTypes.instance().register(req, "req"); MessageTypes.instance().register(ack, "ack"); MessageTypes.instance().register(rel, "rel"); MessageTypes.instance().register(s, "s"); MessageTypes.instance().register(r, "r"); } private SenderInterface sender; 10
  • 11. public void setSender(SenderInterface sender) { this.sender = sender; } /* objeto respons´avel pelo logging. */ /* Somente o processo n-1 ir´a tratar do logging, portanto somente ele usar´a este objeto */ MyLogger logger; //algorithm variables /* the id of this process */ private int me; /* the number of processes */ private int n; /* list of addresses that includes all processes but this one */ private int[] allButMe; /* internal Lamport’s scalar clock */ int osn; /* array of requests */ private NekoMessage[] q; public LamportME(NekoProcess process) { super(process, "LamportME-p" + process.getID()); n = process.getN(); me = process.getID(); allButMe = new int[n - 1]; for (int i = 0; i < n - 1; i++) { allButMe[i] = (i < me) ? i : i + 1; } q = new NekoMessage[n]; osn = 0; logger = new MyLogger(n); } Como dito anteriormente, esta classe estende ActiveReceiver e implementa a interface MEAlgorithm, mostrada primeiro. Os tipos de menagens usados na execu¸c˜ao do algoritmo s˜ao expostos no come¸co da classe: s˜ao associados n´umeros inteiros com as strings que nomeiam os tipos da mensagens, e depois esses tipos s˜ao “registrados” no Neko com o uso do m´etodo register da classe MessageTypes do Neko. Tudo que este m´etodo faz ´e associar o nome do tipo da mensagem com o inteiro que a representar´a, e para isso usa um objeto do tipo HashMap. Ap´os isso s˜ao listadas as vari´aveis. Sender, como sabemos, ser´a o objeto que simular´a a rede e gerenciar´a o envio de mensagens, e ´e inicializado usando o m´etodo setSender pela classe de inicializa¸c˜ao, como mostrado anteriormente. Os inteiro me e n guardam o PID do processo e o n´umero de processos no ambiente de execu¸c˜ao atual, respectivamente. O vetor de inteiros allButMe serve para guardar o PID de todos os processos menos o pr´oprio 11
  • 12. PID, e ele ser´a fundamental quando quisermos enviar mensagens em broadcast. O inteiro osn guardar´a o rel´ogio l´ogico de Lamport do processo atual, e o vetor de NekoMessages q guardar´a as mensagens que chegarem de outros processos, usando uma l´ogica pr´opria do algoritmo que ser´a mostrada. O construtor chama o m´etodo super para criar a thread de execu¸c˜ao e nome´a-la. Tamb´em inicializa as vari´aveis me e n, os vetores allButMe e q, e o rel´ogio l´ogico osn ´e setado inicialmente em zero, para mostrar que a execu¸c˜ao ainda n˜ao come¸cou. Cria tamb´em o objeto respons´avel pelo logging da execu¸c˜ao. Somente o processo n-1 ´e respons´avel pelo logging, portanto s´o ele usar´a este objeto. public void run() { if(me == (n-1)) { while(true) { NekoMessage m = receive(); logger.doLogging(m); } } else { while(true) { NekoMessage m = receive(); switch(m.getType()) { case req: requestMessageHandling(m); break; case ack: ackMessageHandling(m); break; case rel: releaseMessageHandling(m); break; default: throw new RuntimeException("Unknown message received"); } } // while(true) }//method Aqui est´a o m´etodo run que deve ser executado pela thread que controla o algoritmo. Se o processo em quest˜ao n˜ao ´e o n-1, respons´avel pelo logging, o que ele faz ´e ficar num loop infinito esperando mensagens e escolhendo como tratar elas, com base no seu tipo. Como explicado antes, receive ´e um m´etodo da classe ActiveReceiver que retira mensagens da fila de mensagens e entrega-as `a classe que a chamou. As mensagens s˜ao objetos NekoMessage, e um dos campos que as definem ´e o seu tipo (type), que ´e obtido pelo m´etodo p´ublico getType. Esses tipos de mensagens s˜ao os registrados anteriormente e associados a inteiros, 12
  • 13. portanto este m´etodo retorna um inteiro. Vemos que, para cada tipo de mensagem, um m´eodo foi escrito especialmente para tratar dela. Esses m´etodos ser˜ao mostrados abaixo. /** * M´etodo que gerencia mensagens do tipo "req" * @param m: a mensagem do tipo "req" recebida */ private void requestMessageHandling(NekoMessage m) { // obtem o valor do rel´ogio l´ogico enviado na mensagem int k = ((Integer)m.getContent()).intValue(); // atualiza o pr´oprio rel´ogio l´ogico update(k); // coloca a mensagem no vetor de mensagens int j = m.getSource(); q[j] = m; // envia um "ack" de volta ao remetente, carregando o valor do pr´oprio rel´ogio NekoMessage Ack = new NekoMessage(me, new int[] {j}, getId(), new Integer(osn), ack); sender.send(Ack); this.LogMessage(m, osn, r); this.LogMessage(Ack, osn, s); } O tratamento de uma mensagem do tipo “req”, ou request ´e feito neste m´etodo. Primeiro o rel´ogio l´ogico pr´oprio ´e atualizado com base no rel´ogio l´ogico recebido na mensagem envi- ado de outro processo. Essa atualiza¸c˜ao ´e feita no m´etodo update. Ap´os isso, a mensagem ´e colocada na fila q na posi¸c˜ao destinada a mensagens recebidas pelo processo remetente dela. Por fim, uma mensagem de resposta ack ´e enviada em unicast de volta ao processo remetente, levando consigo o valor do rel´ogio l´ogico do processo atual. O m´etodo send do objeto sender tem como ´unico argumento a mensagem a ser enviada. Essa mensagem, encapsulada num objeto do tipo NekoMessage, ´e constru´ıda passando-se ao m´etodo construtor como argumentos o PID do processo remetente, um vetor representando os v´arios processos destinat´arios dela (neste caso o vetor s´o contem o remetente da mensagem “req” recebida), um identificador do protocolo de destino (basta usar o m´etodo getId), o conte´udo a ser transmitido (neste caso o valor do pr´oprio rel´ogio l´ogico, mas pode ser qualquer objeto Java) e o tipo da mensagem, nesta ordem. send enviar´a a mensagem sem que o usu´ario precise se preocupar com mais detalhes. /** * M´etodo que trata mensagens "rel" * @param m: a mensagem do tipo "rel" recebida */ private void releaseMessageHandling(NekoMessage m) { int k = ((Integer)m.getContent()).intValue(); // obtem o valor do rel´ogio l´ogico enviado na mensagem update(k); // atualiza o pr´oprio rel´ogio l´ogico int j = m.getSource(); 13
  • 14. q[j] = m; this.LogMessage(m, osn, r); } Este ´e o m´etodo que trata mensagens do tipo “rel”, ou release. S˜ao feitas duas coisas: o rel´ogio l´ogico pr´oprio ´e atualizado (ele sempre ´e atualiado quando uma mensagem ´e recebido, como o algoritmo pede para ser feito) e a mensagem ´e colocada no vetor de mensagens, na posi¸c˜ao destinada a mensagens do remetente dela. /** * Method that handles ack messages. * It also counts the number of acks received since a request was sent. * @param m: the message received */ private void ackMessageHandling(NekoMessage m) { int k = ((Integer)m.getContent()).intValue(); // obtem o valor do rel´ogio l´ogico enviado na mensagem update(k); // atualiza o pr´oprio rel´ogio l´ogico int j = m.getSource(); if(q[j] != null && q[j].getType() != req) q[j] = m; this.LogMessage(m, osn, r); } Por fim, mensagens do tipo “ack” s˜ao tratadas por este m´etodo. Como nas mensagens do tipo release, nenhuma resposta precisa ser enviada de volta ao remetente. Portanto, apenas a atualiza¸c˜ao do rel´ogio l´ogico pr´oprio e o coloca¸c˜ao da mensagem no vetor de mensagens ´e feito. Por´em, essa coloca¸c˜ao do “ack” no vetor deve ser feita com um cuidado extra: o “ack” n˜ao pode sobrescrever request que por acaso tenham sido enviados pelo processo remetente deste “ack” anteriormente; somente releases podem fazˆe-lo. Caso contr´ario, o processo atual poderia julgar que a prioridade para entrar na ´area cr´ıtica pudesse ser sua, quando na verdade era do processo que teve sua mensagem de request apagada pelo “ack”. Os dois processos entrariam na r.c. e a exclus˜ao m´utua n˜ao aconteceria. Portanto, esse cuidado ´e tomado no teste do if, antes de atribuir q[j] a m. public void enterCriticalSection() { // multicast de uma mensagem do tipo ’req’ requisitando a entrada na r.c. NekoMessage Req = new NekoMessage(me, allButMe, getId(), new Integer(osn), req); sender.send(Req); this.LogMessage(Req, osn, s); // adiciona pr´opria requisi¸c~ao `a queue de mensagens e atualiza rel´ogio l´ogico q[me] = Req; osn = osn + 1; while(!testPriority()) { try { 14
  • 15. sleep(500); } catch (InterruptedException e) { System.out.println("Erro ao testar prioridade da thread."); } } } Este ´e m´etodo que coloca o algoritmo em funcionamento. ´E a implementa¸c˜ao do enterCriticalSection presente na interface MEAlgorithm e, como sabemos, ´e o que deve ser chamado quando a aplica¸c˜ao deseja entrar na regi˜ao cr´ıtica. Como a defini¸c˜ao do al- goritmo pede, o que ´e feito ´e o seguinte: ´e feito um broadcast de uma mensagem “req”, carregando o pr´oprio PID e o pr´oprio valor do rel´ogio l´ogico do processo atual antes do envio da mensagem. Essa mensagem “req” tamb´em ´e guardada no pr´oprio vetor de mensagens, na posi¸c˜ao destinada `as pr´oprias mensagens, e o rel´ogio l´ogico ´e atualizado. Entao, um loop de teste fica rodando em ciclos de 500ms, testando se o processo atual tem a prioridade para entrar na regi˜ao cr´ıtica. Quando esta prioridade ´e ganha, o loop termina, e o m´etodo retorna. A aplica¸c˜ao continua sua execu¸c˜ao, agora com o processo atual utilizando-se da regi˜ao cr´ıtica. public void exitCriticalSection() { // multicast de release NekoMessage Rel = new NekoMessage(me, allButMe, getId(), new Integer(osn), rel); sender.send(Rel); this.LogMessage(Req, osn, s); q[me] = Rel; osn = osn + 1; } Quando a aplica¸c˜ao termina de usar a regi˜ao cr´ıtica, o m´etodo exitCriticalSection, tamb´em presente na interface MEAlgorithm, ´e executado. O que ele faz ´e enviar em broad- cast uma mensagem de release, “rel”, mostrando que terminou de usar sua prioridade e que outro processo requisitante pode fazer uso da regi˜ao. Essa mensagem de release ´e colocada no vetor de mensagens, na posi¸c˜ao destinada `as pr´oprias mensagens, e o rel´ogio l´ogico ´e incrementado de uma unidade de tempo. private void update(int k) { if(osn < k) osn = k; osn = osn + 1; } Este m´etodo simples trata da atualiza¸c˜ao do rel´ogio l´ogico como deve ser feita na especi- fica¸c˜ao do algoritmo de exclus˜ao m´utua de Lamport. Um inteiro k ´e recebido; se este k for maior que o rel´ogio l´ogico atual, osn ´e atualizado com esse valor (acontece no caso que uma mensagem recebida tinha um rel´ogio l´ogico maior que o do processo atual, significando que 15
  • 16. ele estava atrasado em rela¸c˜ao ao processo remetente). Para terminar, osn ´e aumentado em mais uma unidade. public boolean testPriority() { int myReqClock = ((Integer)q[me].getContent()).intValue(); for(int i = 0; i < n; i++) { if(q[i] == null) return false; if(q[i].getType() == req) { int otherReqClock = ((Integer)q[i].getContent()).intValue(); if((otherReqClock < myReqClock) || (otherReqClock == myReqClock && i < me)) { System.out.println(me + ": Processo " + i + " tem prioridade para entrar na r.c.!" + " O rel´ogio da minha req ´e " + myReqClock + " e o da dele ´e " + otherReqClock + "."); return false; } } } osn = osn + 1; return true; } } // class Para terminar a descri¸c˜ao desta classe, o ´ultimo m´etodo ´e o que controla o processo da exclus˜ao m´utua, e portanto ´e o mais importante. O teste para saber se o processo em quest˜ao vai ganhar ou n˜ao a prioridade de entrada na ´area cr´ıtica ´e feito da seguinte maneira, como especificado na defini¸c˜ao do algoritmo: o rel´ogio da mensagem de request do pr´oprio processo presente na fila de mensagens ´e comparado com todas as outras mensagens de request de outros processos presentes na fila. Se nenhuma delas tiver um rel´ogio de valor menor que o da “req” pr´opria, ou tiver um valor igual, por´em o PID do outro processo for maior que o PID do processo que est´a testando a prioridade, ent˜ao esse processo ganha a prioridade e pode entrar na regi˜ao cr´ıtica. Caso contr´ario, ele n˜ao ganha a prioridade, e o teste ser´a executado novamente no loop mostrado no m´etodo enterCriticalSection, at´e que ele ganhe a prioridade e possa usufruir da ´area cr´ıtica. Dessa forma, ´e assegurado que somente um processo por vez tem acesso a essa ´area, e a exclus˜ao m´utua funciona durante a execu¸c˜ao da aplica¸c˜ao. Quando o processo entra na ´area cr´ıtica, o rel´ogio dele aumenta em uma unidade, por isto ser tratado como um evento na execu¸c˜ao. Isso tamb´em facilita um pouco a visualiza¸c˜ao das mensagens de release no gr´afico gerado pelo logView, mostrado mais `a frente. 16
  • 17. 3.5 O Arquivo de Configura¸c˜ao: distributed.config O arquivo de configura¸c˜ao ´e um arquivo de texto com extens˜ao .config, passado como argumento de linha de comando quando a execu¸c˜ao ´e iniciada, como ser´a mostrada na parte de execu¸c˜ao. Ele ´e usado para se passar informa¸c˜oes ao Neko durante a inicializa¸c˜ao da aplica¸c˜ao e que podem ser usadas durante toda ela. Portanto, pode ser uma ferramenta muito ´util na implementa¸c˜ao de um algoritmo ou aplica¸c˜ao no Neko. simulation = false process.num = 4 slave = lsd96, lsd97, lsd98 process.initializer = lamport.MEInitializer network = lse.neko.networks.comm.TCPNetwork log = home/usu´ario/Desktop/log.txt Esse come¸co define se a execu¸c˜ao ser´a uma simula¸c˜ao em m´aquina ´unica ou uma execu¸c˜ao distribu´ıda entre v´arios computadores, na palavra-chave ’simulation’. Aqui foi feita uma execu¸c˜ao distribu´ıda real. Depois, define-se o n´umero de processos em ’process.num’; neste caso s˜ao 4 processos, rodando em 4 m´aquinas diferentes, cada um em uma m´aquina. O endere¸co das m´aquinas que ser˜ao tratadas como slaves ´e passado na palavra-chave ’slave’. Esses slaves, na verdade, s˜ao apenas as m´aquinas que ficar˜ao esperando a execu¸c˜ao come¸car, aguardando que uma das m´aquinas, que det´em este arquivo de configura¸c˜ao, se conecte a elas, envie o arquivo de configura¸c˜ao e dˆe o sinal para o in´ıcio da execu¸c˜ao. Quando a execu¸c˜ao come¸ca, n˜ao h´a mais nenhuma distin¸c˜ao entre os n´os: n˜ao h´a mais ’master’ ou ’slave’, e todos rodam o algoritmo de forma igualit´aria. Em ’process.initializer’ ´e passado o nome completo da classe que trata da inicializa¸c˜ao da execu¸c˜ao; n´os sabemos que nesta aplica¸c˜ao esta classe ´e a MEInitializer, do pacote ’lamport’. Por ´ultimo ´e passado que tipo de rede ser´a simulada com base numa classe do Neko que simula esta rede. Neste caso, uma rede TCP ´e usada, por meio da classe do Neko lse.neko.networks.comm.TCPNetwork. Outras redes est˜ao dispon´ıveis para serem usadas. Basta ver na pasta do Neko quais s˜ao elas. application = lamport.Application algorithm = lamport.LamportME #algorithm = mutualexclusion.RicartAgrawalaME #algorithm = mutualexclusion.SinghalME Aqui ´e importante. Anteriormente foi mencionada a capacidade do Neko de pegar o nome das classes que implementar˜ao a pilha de protocolos de cada processo em tempo de execu¸c˜ao, atrav´es do arquivo de configura¸c˜ao aqui exposto, e para isso usar a propriedade de Reflection do Java na classe de inicializa¸c˜ao MEInitializer. Aqui est´a a parte em que isto ´e usado. Usando a palavras-chave application e algorithm, podemos dizer quais s˜ao as classes que funcionar˜ao como a camada de aplica¸c˜ao e a camada do algoritmo do programa. Para 17
  • 18. acessar estas informa¸c˜oes, o Neko usa um objeto do tipo Configurations, que guarda todas as informa¸c˜oes expostas no arquivo de configura¸c˜ao. Estas informa¸c˜oes podem ser acessadas durante a execu¸c˜ao atrav´es deste objeto. Examine a classe MEInitializer para ver como isso ´e feito e perceber como ´e simples. O m´etodo getString da classe Configurations retorna uma string associada `a chave que ´e passada a ela como argumento. Por exemplo, em MEInitializer, para se descobrir o nome da classe que implementar´a o algoritmo, que aqui se chama lamport.LamportME, ´e passado ao getString a palavra-chave algorithm. Como pode-se ver, nestas linhas do arquivo de configura¸c˜ao a palavra-chave algorithm ´e associada com a string lamport.LamportME, que ´e exatamente o nome da classe do algoritmo de exclus˜ao m´utua que ser´a usado. Portanto, sabendo usar isso e a Reflection do Java, podemos fazer v´arias implementa¸c˜oes de aplica¸c˜oes, algoritmos, e passar parˆametros como n´umeros e etc., no arquivo de configura¸c˜ao, para que depois sejam usados em tempo real na execu¸c˜ao da nossa aplica¸c˜ao no Neko. E isso ´e bastante ´util quando o principal objetivo de quem est´a usando o Neko ´e simplicidade no uso e rapidez na implementa¸c˜ao e teste de algoritmos. 4 Execu¸c˜ao Para come¸car a execu¸c˜ao distribu´ıda do algoritmo ´e necess´ario antes iniciar os n´os slaves. Slave ´e um n´o que ficar´a escutando numa porta, passada como argumento na linha de co- mando ou na padr˜ao 8632, se nada for passado. Ele espera receber de um n´o, ´unico entre todos os processos chamado master, o arquivo de configura¸c˜ao e o sinal para a execu¸c˜ao come¸car. Assim que recebe esse arquivo, ele monta a pilha de protocolos do processo corre- spondente a ele (cada n´o roda um processo) e come¸ca a execu¸c˜ao dele. Como ´e mostrado na Figura 2, deve ser chamada a classe java.lse.comm.Slave do Neko, com o comando java lse.neko.comm.Slave no terminal para o slave ser iniciado e ficar escutando em alguma porta. Se uma porta diferente da padr˜ao quiser ser passada, basta adicionar o n´umero dela como argumento deste comando (java lse.neko.comm.Slave XXXX, com XXXX sendo o n´umero da porta). Esse procedimento deve ser feito independentemente em cada um dos n´os que forem come¸car a execu¸c˜ao como slaves; o n´o master executa um comando diferente. Na Figura 3 ´e mostrado como ´e feito o in´ıcio da execu¸c˜ao atrav´es do n´o master. Usando- se o comando neko junto com o caminho para o arquivo de configura¸c˜ao mostrado ante- riormente passado como argumento, e ap´os todos os outros n´os terem sido iniciados como slaves e estarem conectados entre si (usando ssh, de preferˆencia), a execu¸c˜ao ter´a in´ıcio. Na Figura 3 o arquivo de configura¸c˜ao estava na mesma pasta em que foi chamado o comando, portanto n˜ao foi necess´ario passar um caminho absoluto ou relativo at´e ele, apesar de isso ainda ser poss´ıvel. A Figura 4 mostra o momento na execu¸c˜ao em que todos os processos est˜ao tentando conseguir a prioridade para a regi˜ao cr´ıtica, mas somente o processo 1 conseguiu. Isso se deve a ele ter enviado o req antes do processo 0, e de ou ter enviado antes do outros processos tamb´em ou ter enviado ao mesmo tempo, sendo que neste caso ele ganha a prioridade por ter PID menor, da forma como o algoritmo de exclus˜ao m´utua de Lamport decide a garantia de prioridade `a ´area cr´ıtica. 18
  • 19. Figura 2: Configurando um n´o slave no Neko antes de come¸car a execu¸c˜ao Figura 3: Come¸cando a execu¸c˜ao com o n´o master 19
  • 20. Figura 4: Momento em que um processo consegue acesso `a regi˜ao cr´ıtica Por fim, a Figura 5 mostra o momento na execu¸c˜ao em que um processo termina de usar a regi˜ao cr´ıtica e manda em broadcast uma mensagem de release. Assim que os outros processos recebem essa mensagem, removem o req do processo remetente que estava no vetor de mensagens e testam novamente a prioridade. Um dos processos descobre que ele ´e o pr´oximo a receber o acesso `a regi˜ao cr´ıtica e entra, enquanto os outros processos continuam esperando. 5 As Classes Usadas Para Gerar o Log da Execu¸c˜ao Foi mencionada a cria¸c˜ao de um arquivo de log durante a execu¸c˜ao do algoritmo. Essa cria¸c˜ao do log ´e feita por um processo, que aqui foi escolhido como o processo n-1, que fica esperando mensagens espec´ıficas de gera¸c˜ao de log enviadas pelos outros processos, que est˜ao envolvidos na execu¸c˜ao do algoritmo. Essas mensagens espec´ıficas carregam as mensagens que estes processos enviam ou recebem, junto com o rel´ogio l´ogico do processo quando o evento do envio ou recebimento desta mensagem ocorreu. Isto ´e utilizado para criar o log, que consiste de linhas que registram detalhes das mensagens. Esse arquivo de log ´e usado na gera¸c˜ao de um gr´afico da execu¸c˜ao pelo logView, como ser´a mostrado na pr´oxima se¸c˜ao. 5.1 A Classe Event package lamport; import java.io.Serializable; 20
  • 21. Figura 5: Momento em que um processo libera a r.c. e outro entra import lse.neko.NekoMessage; public class Event implements Serializable { NekoMessage m; int osn; public Event(NekoMessage m, int osn) { this.m = m; this.osn = osn; } public NekoMessage getMessage() { return m; } public int getOsn() { return osn; } } A classe Event serve para que as mensagens de log possam ser enviadas pelos processos participantes da execu¸c˜ao com as mensagens que eles trocam entre si e o tempo l´ogico que marca o momento em que eles enviam ou recebem estas mensagens. ´E como se as mensagens rebidas ou enviadas fossem “empacotadas” numa outra mensagem com alguns metadados necess´ario ao registro dessa mensagem no arquivo de log. Como estes envios ou recebimentos de mensagens s˜ao chamados de eventos em um ambiente distribu´ıdo a classe 21
  • 22. recebeu este nome. ´E necess´ario que ela implemente a interface do Java chamada Serializable, caso contr´ario uma exce¸c˜ao ´e levantada durante a execu¸c˜ao em um ambiente distribu´ıdo. Esta interface Serializable n˜ao apresenta nenhum m´etodo a ser implementado. 5.2 A Classe MyLogger MyLogger ´e a classe que trata da cria¸c˜ao e gerenciamento do arquivo de log. package lamport; import java.io.BufferedWriter; import java.io.FileWriter; import lse.neko.MessageTypes; import lse.neko.NekoMessage; import lse.neko.NekoSystem; import org.apache.java.util.Configurations; public class MyLogger { Configurations config; int n; boolean firstEntry; public MyLogger(int n) { config = NekoSystem.instance().getConfig(); this.n = n; firstEntry = true; } O m´etodo construtor inicia as vari´aveis da classe. Um objeto Configurations ´e criado, para que o arquivo de configura¸c˜ao da execu¸c˜ao do Neko seja utilizado. Uma entrada nele guarda o diret´orio e nome do arquivo de log a ser criado. Outra vari´avel iniciada ´e a que guarda o n´umero de processos na execu¸c˜ao atual. Por fim, uma vari´avel booleana diz se estamos tratando da primeira entrada no arquivo de log, e por isso ele precisa ser criado do zero, ou se apenas anexamos a pr´oxima entrada ap´os as outras j´a gravadas, quando ele j´a foi criado e a execu¸c˜ao atual j´a est´a avan¸cada. public void doLogging(NekoMessage logMsg) { FileWriter fw; config = NekoSystem.instance().getConfig(); String logPath = config.getString("log"); Event event = (Event)logMsg.getContent(); int osn = event.getOsn(); NekoMessage msg = event.getMessage(); 22
  • 23. int eventSource = logMsg.getSource(); int eventType = logMsg.getType(); String[] s = formatString(msg, eventSource, eventType, osn); for(int i=0; i<s.length;i++) { if(s[i] != null) { try{ if(firstEntry) { fw = new FileWriter(logPath, false); firstEntry = false; } else fw = new FileWriter(logPath, true); BufferedWriter out = new BufferedWriter(fw); out.write(s[i] + "n"); out.close(); } catch (Exception e) { System.err.println("Error: " + e.getCause()); } } } } O m´etodo doLogging trata os eventos de envio e recebimento de mensagens que devem ser registrados no arquivo de log. private String[] formatString(NekoMessage m, int source, int type, int osn) { String[] v = new String[n]; String time = String.valueOf(osn); String eventType = MessageTypes.instance().getName(type); int messageSource = m.getSource(); int[] messageDest = m.getDestinations(); String messageType = MessageTypes.instance().getName(m.getType()); String messageContent = String.valueOf(m.getContent()); if(eventType == "r") { String f = time + " p" + source + " messages e " + eventType + " p" + messageSource + " p" + source + " " + messageType + " " + messageContent; v[0] = f; } else for(int i=0; i<messageDest.length; i++) { String f = time + " p" + source + " messages e " + "s" + " p" + source + " p" + messageDest[i] + " " + messageType + " " + messageContent; v[i] = f; } return v; } 23
  • 24. } E o m´etodo formatString cria as strings que representam cada evento e que ficar˜ao registradas em cada linha no arquivo de log. O formato das strings pode ser conferido no arquivo de log mostrado na pr´oxima se¸c˜ao. 6 Visualiza¸c˜ao de Execu¸c˜oes: o LogView Uma execu¸c˜ao do algoritmo produz um arquivo de log, com o nome e diret´orio passados no arquivo de configura¸c˜ao mostrado anteriormente, e que registra as mensagens enviadas e recebidas. Esse registros contˆem detalhes como o processo que enviou, o que recebeu, o tipo da mensagem, o conte´udo e etc.. Um arquivo de log produzido por uma execu¸c˜ao distribu´ıda ´e mostrado abaixo. Cada linha mostra, na ordem: o tempo l´ogico em que um processo enviou ou recebeu uma mensagem, qual processo foi esse, se o evento foi de envio (“s”) ou recebimento (“r”) de mensagem , qual foi o remetente e o destinat´ario, o tipo de mensagem e o conte´udo que ela carregou (no caso o conte´udo ´e o rel´ogio l´ogico do processo no momento de envio da mensagem). 1 p0 messages e r p2 p0 req 0 1 p0 messages e s p0 p2 ack 1 1 p1 messages e r p2 p1 req 0 1 p1 messages e s p1 p2 ack 1 0 p2 messages e s p2 p0 req 0 0 p2 messages e s p2 p1 req 0 2 p2 messages e r p1 p2 ack 1 3 p2 messages e r p0 p2 ack 1 1 p0 messages e s p0 p1 req 1 1 p0 messages e s p0 p2 req 1 4 p2 messages e r p0 p2 req 1 2 p1 messages e r p0 p1 req 1 4 p2 messages e s p2 p0 ack 4 2 p1 messages e s p1 p0 ack 2 3 p0 messages e r p1 p0 ack 2 5 p0 messages e r p2 p0 ack 4 2 p1 messages e s p1 p0 req 2 2 p1 messages e s p1 p2 req 2 6 p0 messages e r p1 p0 req 2 5 p2 messages e r p1 p2 req 2 6 p0 messages e s p0 p1 ack 6 5 p2 messages e s p2 p1 ack 5 6 p1 messages e r p2 p1 ack 5 7 p1 messages e r p0 p1 ack 6 6 p2 messages e s p2 p0 rel 6 6 p2 messages e s p2 p1 rel 6 7 p0 messages e r p2 p0 rel 6 8 p1 messages e r p2 p1 rel 6 8 p0 messages e s p0 p1 rel 8 8 p0 messages e s p0 p2 rel 8 9 p2 messages e r p0 p2 rel 8 9 p1 messages e r p0 p1 rel 8 9 p2 messages e s p2 p0 req 9 9 p2 messages e s p2 p1 req 9 24
  • 25. 11 p1 messages e r p2 p1 req 9 10 p0 messages e r p2 p0 req 9 11 p1 messages e s p1 p2 ack 11 10 p0 messages e s p0 p2 ack 10 12 p2 messages e r p1 p2 ack 11 13 p2 messages e r p0 p2 ack 10 11 p1 messages e s p1 p0 rel 11 11 p1 messages e s p1 p2 rel 11 14 p2 messages e r p1 p2 rel 11 12 p0 messages e r p1 p0 rel 11 12 p0 messages e s p0 p1 req 12 12 p0 messages e s p0 p2 req 12 16 p2 messages e r p0 p2 req 12 13 p1 messages e r p0 p1 req 12 16 p2 messages e s p2 p0 ack 16 13 p1 messages e s p1 p0 ack 13 14 p0 messages e r p1 p0 ack 13 17 p0 messages e r p2 p0 ack 16 16 p2 messages e s p2 p0 rel 16 16 p2 messages e s p2 p1 rel 16 17 p1 messages e r p2 p1 rel 16 18 p0 messages e r p2 p0 rel 16 17 p1 messages e s p1 p0 req 17 17 p1 messages e s p1 p2 req 17 20 p0 messages e r p1 p0 req 17 18 p2 messages e r p1 p2 req 17 18 p2 messages e s p2 p1 ack 18 20 p0 messages e s p0 p1 ack 20 21 p1 messages e r p0 p1 ack 20 22 p1 messages e r p2 p1 ack 18 20 p0 messages e s p0 p1 rel 20 20 p0 messages e s p0 p2 rel 20 23 p1 messages e r p0 p1 rel 20 21 p2 messages e r p0 p2 rel 20 24 p1 messages e s p1 p0 rel 24 24 p1 messages e s p1 p2 rel 24 25 p0 messages e r p1 p0 rel 24 25 p2 messages e r p1 p2 rel 24 Com a ajuda de um programa chamado LogView podemos gerar um gr´afico visualizando o fluxo dessas mensagens e assim tendo uma id´eia de como ocorre o funcionamento de um algoritmo implementado no Neko. O LogView foi escrito tamb´em em Java e gera gr´aficos a partir de logs do Neko com o formato de mensagens mostrado acima, e um arquivo XML de configura¸c˜ao, em que v´arios detalhes podem ser modificados para gerar gr´aficos mais ao gosto do usu´ario. O que foi usado aqui neste documento foi ligeiramente modificado por mim em rela¸c˜ao ao original, que vem junto com o pr´oprio Neko. As modifica¸c˜oes incluem menos dependˆencia deste arquivo de configura¸c˜ao, sendo necess´ario apenas que o arquivo de log seja listado nele, e um menu de op¸c˜oes que possibilita mostrar tags sobre as setas que mostrem conte´udo da mensagem, tipo, tempos de envio e recebimento, entre outras modifica¸c˜oes. O arquivo de configura¸c˜ao usado nas visualiza¸c˜oes mostradas a seguir ´e este abaixo: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE logView SYSTEM "logView.dtd"> 25
  • 26. <logView> <display> <timeAxis xSize="200"/> <!--timeAxis xSize="100"/> <processAxis ySize="100"/> <window xSize="1000" ySize="600"/> <messages type="REQUEST" color="red"/> <messages type="ACK" color="blue"/> <messages type="SCORE" color="yellow"/> <messages type="RELEASE" color="green"/> <label halign="left" valign="top" distance="30" percent="0.7"/--> </display> <file> <log filename="/home/alvaro/Desktop/log.txt"/> </file> </logView> Pode se ver que esse arquivo ´e bem pequeno. O cabe¸calho inicial indica a vers˜ao do XML, a codifica¸c˜ao e a segunda linha indica que este arquivo deve seguir o padr˜ao de um outro arquivo DTD (Documente Type Definition , que diz como ele deve ser entendido pelo parser e escrito pelo usu´ario. Este arquivo DTD ´e inclu´ıdo junto com o LogView. A segunda parte indica detalhes do gr´afico, como espa¸camento nos eixos do tempo e de processos, intervalos a serem representados, com que cor deve ser mostrado cada tipo de mensagem, entre outros. Pode-se ver que a parte entre <!-- e --> est´a comentada e n˜ao foi portanto considerada como configura¸c˜ao v´alida; essa parte ´e mostrada para exemplificar que tipo de op¸c˜oes est˜ao dispon´ıveis neste arquivo de configura¸c˜ao para personalizar a visualiza¸c˜ao. A ´ultima parte indica o arquivo de log a ser lido. A princ´ıpio a ´unica parte estritamente necess´aria que este arquivo deve conter ´e o cabe¸calho e a indica¸c˜ao do arquivo de log; o resto n˜ao ´e necess´ario e o logView consegue gerar diagramas sem estas outras configura¸c˜oes. A Figura 6 mostra o come¸co da execu¸c˜ao. Setas azuis representam mensagens de request, vermelhas de ack e cinzas de release. Os n´umeros sobre as setas s˜ao o conte´udo da mensagem, que neste caso ´e o rel´ogio l´ogico do processo no momento de envio da mensagem. Nesta figura visualiza-se as mensagens de request enviadas pelos processos 2, 1 e 0, nesta ordem, e as mensagens de ack enviadas por outros processos quando estas s˜ao recebidas. As setas come¸cam no tempo em que s˜ao enviadas pelo processo remetente e terminam no tempo em que foram recebidas pelo processo destinat´ario. A Figura 7 mostra o momento em que o processo 2 envia um release em broadcast. Como ele tinha enviado o request antes de todos os outros processos a prioridade de entrada na r.c. foi dada a ele primeiro. Ap´os fazer uso dela, ele faz esse broadcast aviasando todos os outros processos de que a prioridade n˜ao ´e mais dele e que outro processo pode usar a ´area cr´ıtica. Sabendo disso, o processo 0 ganha a prioridade, por ter enviado o “req” antes do processo 1. E, da mesma forma, ap´os terminar de usar a r.c. ele tamb´em faz broadcast de “rel”, e as setas cinzas saindo dele e indo para os outros processos mostram isso. ´E mostrado tamb´em o processo 2 tentando entrar na ´area cr´ıtica novamente, mandando um broadcast de request, e as setas azuis saindo dele no lado direito da imagem ilustram isso. Por ´ultimo, a Figura 8 mostra uma parte da execu¸c˜ao em que o Processo 0 termina de usar a regi˜ao cr´ıtica, faz um broadcast de release e ent˜ao o Processo 1 ganha a prioridade. 26
  • 27. Figura 6: O come¸co da execu¸c˜ao. Todos os processos tentam entrar na ´area cr´ıtica. Figura 7: Processo 2 sai da r.c. e Processo 0 entra. Processo 2 tenta novamente entrar na r.c., fazendo um broadcast de request. 27
  • 28. Figura 8: Processo 0 deixa a r.c., e o Processo 1 entra. Ap´os o uso da r.c. ambos fazem broadcast de release. Ap´os tamb´em terminar de fazer uso desta prioridade ele tamb´em avisa aos outros processos de que a r.c. est´a sem uso no momento. Importante deixar claro que ao entrar na regi˜ao cr´ıtica um processo aumenta seu rel´ogio l´ogico em uma unidade, portanto o Processo 1 recebeu o “rel” do Processo 0 no tempo 23 e, ao entrar na ´area cr´ıtica, aumentou seu rel´ogio l´ogico em 1 unidade, e por isso ele envia o seu broadcast de release no tempo 24. 7 Conclus˜ao Neste documento, foi apresentado o Neko, uma plataforma Java simples de comunica¸c˜ao que provˆe suporte `a simula¸c˜ao e prototipagem de algoritmos distribu´ıdos. Atrav´es dele, foi implementado o algoritmo de exclus˜ao m´utua de Lamport, e dessa forma foi mostrado como ´e simples e pr´atico o uso do Neko para este fim. Na classe que implementava o algoritmo propriamente, viu-se que n˜ao foi necess´ario muito mais c´odigo al´em do que descrevia a pr´opria execu¸c˜ao dele. A troca de mensagens ´e feita com m´etodos j´a implementados em classes do Neko, que tamb´em controla a camada de redes e a troca de mensagens entre as camadas da pilhas que representam os processos. Por fim, a execu¸c˜ao desta implementa¸c˜ao gerou um arquivo de log que tornou poss´ıvel a gera¸c˜ao de um gr´afico, representando a execu¸c˜ao do algoritmo e a troca de mensagens entre os processos. Cada mensagem foi representada por uma seta entre os processos remetente e destinat´ario, mostrando tamb´em uma etiqueta com o conte´udo dessas mensagens. Os tempos de chegada e sa´ıda destas setas eram os pr´oprios tempos l´ogicos em cada processo, 28
  • 29. tornando poss´ıvel a visualiza¸c˜ao dos eventos na execu¸c˜ao segundo uma ordena¸c˜ao do tipo happened-before entre todas a mensagens. Sendo assim, a visualiza¸c˜ao gerada se torna um instrumento muito ´util para se enxergar como um algoritmo distribu´ıdo funciona. O Neko e o LogView podem ser encontrados nas p´aginas listadas na bibliografia. Referˆencias [1] L Lamport. “Time, Clocks, and the Ordering of Events in a Distributed System”. In: Communications of the ACM 21 (1978), pp. 558–565. [2] J. Muller, M. Galanthay e P. Urb´an. Rapport de Projet de Semestre Visualisation des Fichiers de Traces de Neko : LogView. 2002. url: http://ddsg.jaist.ac.jp/neko/ logView/rapport/rapport.pdf. [3] P. ´Urban, X. D´efago e A. Schiper. “Neko: A Single Environment to Simulate and Pro- totype Distributed Algotithms”. In: Journal of Information Science and Engineering 18 (2002), pp. 981–997. url: http://ddsg.jaist.ac.jp/pub/UDS02.pdf. 29