Sempre foi uma das grandes preocupações de técnicos em computação executar o maior número possível de tarefas no menor período de tempo e custo necessários. Muitas descobertas contribuíram significativamente para isso, tais como processadores mais rápidos, hierarquias de memórias, discos maiores e mais eficientes etc. Temos, hoje, computadores suficientemente rápidos para realizar inúmeros cálculos em frações de segundo. E relativamente baratos a ponto de estarem disponíveis não só em centros de pesquisa e empresas, mas também em residências.
Outro fator que contribuiu e ainda está contribuindo para a popularização dos computadores são as redes de computadores e o teleprocessamento. Interligando diversos computadores através de uma rede é possível, por exemplo, compartilhar periféricos como discos e impressoras. O teleprocessamento garante, por sua vez, a comunicação entre máquinas situadas em pontos remotos.
Com o desenvolvimento das redes, surgiram ferramentas cada vez mais sofisticadas que permitem não só o compartilhamento de periféricos, mas também a comunicação entre aplicações. É possível, desta forma, transformar uma rede de computadores em um multiprocessador. A idéia básica é dividir uma aplicação em pequenos módulos que executam em diferentes máquinas da rede e que eventualmente se comunicam para trocar informações através de mensagens. Na verdade, usar uma rede para executar programas paralelos constitui uma alternativa relativamente mais econômica de processamento do que um multiprocessador. O preço de um multiprocessador é proibitivo para muitas instituições.
Considerando está possibilidade, foi implementado o Ambiente de Multiprocessamento para UNIX (AMU). Este ambiente permite o desenvolvimento de aplicações paralelas sobre uma rede UNIX como se houvesse um multiprocessador disponível. Suas funções básicas são: iniciar a execução de processos em outras máquinas da rede e controlar estes processos, enviando e recebendo mensagens dos mesmos.
O AMU foi implementado em C sobre o sistema operacional LINUX [LIN 96], compatível com o UNIX. São utilizadas chamadas de RPC (Remote Procedure Call) [SUN 90] e chamadas básicas de sistema do UNIX [SHA 87], o que garante a sua portabilidade. Pela sua simplicidade e tamanho relativamente reduzido, o AMU torna-se ainda uma ferramenta bastante didática, cobrindo todos os detalhes da implementação de primitivas de troca de mensagens em sistemas operacionais modernos. Seu objetivo não é substituir, nem mesmo competir, com bibliotecas de troca de mensagens consagradas, como o PVM (Parallel Virtual Machine) [GEI 93][CRA 94][KIT 95], mas apresentar uma forma simples de implementar a troca de mensagens, explorando os recursos disponíveis em sistemas operacionais compatíveis com UNIX.
A seguir, algumas características do AMU serão apresentadas com mais detalhe. Inicialmente, na seção 2, será apresentada uma visão geral do ambiente. Na seção 3, serão apresentadas as funções que compõem o AMU. A seção 4 apresenta um pequeno exemplo de utilização do AMU. Por fim, serão apresentadas algumas conclusões.
Na visão de seu usuário, o AMU é uma ferramenta com a qual é possível desenvolver uma aplicação paralela sobre uma rede de máquinas rodando UNIX. Para tanto, o AMU disponibiliza aos seus usuários dois componentes básicos: um processo chamado "amd" e uma biblioteca chamada "am_lib". A figura 1 mostra como estes componentes interagem transformando um conjunto de processos, rodando em diferentes máquinas da rede, em uma aplicação paralela.

O processo "amd" é um processo residente (daemon) executado automaticamente em cada máquina da rede que faz parte do ambiente. Este processo é responsável por qualquer comunicação executada com os processos da máquina.
Aplicações que desejarem utilizar os serviços do processo "amd" deverão utilizar as funções da biblioteca "am_lib". Esta biblioteca, que deverá ser incluída na aplicação, contém uma série de rotinas que o usuário utiliza para: inicializar o ambiente, cadastrar uma máquina no sistema, executar um processo em uma máquina, remover uma máquina do sistema, encerrar a execução de um processo, receber mensagens, enviar mensagens, entre outras.
A estrutura interna do AMU baseia-se na troca de mensagens entre a aplicação e os processos residentes (processos "amd") instalados em cada máquina. Para executar um novo processo em uma máquina remota, por exemplo, envia-se uma mensagem ao processo "amd" da máquina em que o processo será criado, através de uma chamada RPC (Remote Procedure Call) de nível médio [SUN 90]. Este, por sua vez, interpreta a mensagem enviada e executa a ação desejada. No caso: a criação de um novo processo. E envia em seguida uma mensagem de resposta com o resultado da solicitação.
Dez serviços principais foram implementados e estão disponíveis na biblioteca através de funções. São elas: am_init(), am_addhost(), am_hostname(), am_myhost(), am_mytid(), am_startproc(), am_deletehost(), am_killproc(), am_send(), am_receive() e am_exit(). A seguir cada uma delas será sucintamente descrita.
Esta é a primeira função que deve ser chamada pelo processo mestre. O processo mestre é o processo principal de uma aplicação, responsável pela ativação dos demais processos. A função am_init() testa inicialmente se o processo residente "amd" está sendo executado ou não. Caso o processo residente "amd" não tenha sido instalado, a função am_init() iniciará a sua execução. A função am_init() envia em seguida uma mensagem (sempre através de RPC) para o "amd" local pedindo o cadastramento do processo como processo mestre.
Caso a aplicação não execute esta função, ela será considerada como uma aplicação comum, não podendo inserir máquinas no sistema, nem iniciar a execução de processos. Estas são funções exclusivas do processo mestre.
Com a função am_addhost() é possível inserir uma nova máquina no ambiente. A função am_addhost() recebe o nome da máquina como parâmetro e retorna um identificador para máquina ou um código de erro.
Inicialmente é consultada uma tabela interna com a definição de todas as máquinas já cadastradas. Caso a máquina ainda não tenha sido cadastrada, insere-se a máquina na tabela e ativa-se o processo "amd" na máquina remota através de um comando rsh (Remote Shell). A função retornará o número da máquina para a aplicação, conforme o valor atribuído a ela durante a inserção na tabela. Este número deverá ser referenciado pelas demais funções que desejarem criar ou remover processos.
A função am_hostname() retorna o nome da máquina cujo número foi recebido como parâmetro. Este nome é obtido a partir da tabela interna atualizada pela função am_addhost().
A função am_myhost() é utilizada para obter o número da máquina local. Retorna o número desta ou -1, se ocorrer algum erro. Para obter o número da máquina local, deve-se requisitar o serviço ao processo "amd" da máquina em que está o processo mestre, pois ele armazena todos os números de máquina em uma tabela interna.
Esta função retorna o número pelo qual este processo é conhecido no sistema. Este número consiste num identificador único para cada processo do sistema, independente da máquina na qual o processo está sendo executado. Este número é gerado através de uma composição matemática do número da máquina com identificador de processo do UNIX. O número da máquina pode ser obtido pela função am_myhost() e o identificador do processo pode ser obtido através da função getpid(), do UNIX.
A função am_startproc() inicia um processo na máquina especificada e retorna o seu identificador ou -1, se ocorrer algum erro. Somente a aplicação servidora pode executar esta função. Inicialmente verifica-se a existência da máquina especificada consultando-se a lista de máquinas. Em caso afirmativo, envia-se, através de RPC, o nome do programa a ser executado para o processo "amd" da máquina remota.
Se a criação do processo obtiver êxito, um identificador de processo será retornado e o processo será cadastrado em uma tabela de processos.
A função am_deletehost() remove uma máquina do conjunto de máquinas que compõem o ambiente de multiprocessamento. Esta função encerra a execução de todos os processos iniciados na máquina, recebendo como parâmetro o número da máquina, conforme retornado pela função am_addhost().
A função am_killproc() encerra a execução de um processo iniciado com a função am_startproc(). Ela recebe como parâmetro o número do processo a ser encerrado e retorna um código indicando sucesso ou fracasso.
A função am_send() envia uma mensagem a um processo criado com am_startproc(). Como este processo pode estar em qualquer máquina participante do sistema, não é possível usar as rotinas padrões de troca de mensagens entre processos do UNIX. Em função disto, as mensagens para um determinado processo serão enviadas através de chamadas RPC ao "amd" da máquina onde o processo foi criado. Cada "amd" é responsável pela buferização das mensagens de seus processos "filhos".
Para simplificar o projeto e evitar problemas com a conversão entre formatos de diferentes máquinas, as mensagens enviadas e recebidas conterão apenas cadeias de caracteres. A função am_send() recebe, portanto, como parâmetros, o número do processo que receberá a mensagem e uma cadeia de caracteres correspondendo à mensagem.
Recebe uma mensagem de um processo específico ou de qualquer processo, retornando o número do processo que enviou a mensagem.
Esta função executa uma chamada de RPC ao "amd" local para receber a mensagem, uma vez que cada "amd" gerencia as mensagens para os processos da sua máquina.
Quando um processo mestre executa am_exit() todos os processos criados por ele também serão encerrados.
A figura 2 apresenta um exemplo de aplicação usando o AMU. Inicialmente o sistema é inicializado (am_init()). Em seguida são definidos em que máquinas serão criados novos processos. Isto é feito através da função am_addhost(). Se as máquinas foram cadastradas com sucesso, será requisitada a criação de um processo em cada máquina. A função am_startproc() inicia a execução de um processo na máquina especificada. O programa envia então uma mensagem para cada processo e aguarda a resposta, respectivamente, usando am_send() e am_receive(). Finalmente, as mensagens recebidas são impressas e a execução é encerrada.
A figura 3 apresenta o código para os processos "exemplo2" iniciados com a função am_startproc(). Sua função é receber uma mensagem de qualquer processo e enviar uma resposta.
| #include "am_lib.c"
void main() {
int tid, m1, m2, mp, p1, p2; mp = am_init(); /* Inicializa
o ambiente */
m1 = am_addhost ("maquina1"); m2 = am_addhost ("maquina2"); if ( m1==-1 || m2==-1 )
p1 = am_startproc (m1,"exemplo2"); p2 = am_startproc (m2,"exemplo2"); if ( p1==-1 || p2==-1)
am_send (p1,"Alo Máquina 1!"); am_send (p2,"Alo Máquina 2!"); /* Receber 2 mensagens de respostas
de qualquer processo */
|
| #include "am_lib.c"
void main()
char msg[100], nome[30]; gethostname(nome,64); /*obtem o nome desta máquina*/ tid = am_receive(-1,msg); /* Recebe uma mensagem */
fprintf(stderr,"Recebi mensagem '%s' do processo %d\n", msg, tid); sprintf(msg,"Eu sou a maquina %s",nome); /* Prepara resposta */ am_send(tid,msg); /* Envia
mensagem de resposta */
|
A gama de recursos da biblioteca do AMU não é, obviamente, tão ampla quanto a de PVM ou outras bibliotecas consagradas. Mesmo assim diversas aplicações clássicas podem ser implementadas com relativa facilidade usando o AMU.
O principal objetivo do AMU é criar um ambiente relativamente simples e pequeno, usando chamadas básicas de sistema do UNIX, em que diversos conceitos relacionados com sistemas distribuídos e programação paralela possam ser vivenciados.
Desta forma foi criada uma estrutura básica em cima da qual será possível desenvolver novos projetos que explorem conceitos relacionados à execução distribuída de processos e à troca de mensagens entre processos. Alunos de disciplinas como Projeto de Sistemas Operacionais ou Sistemas Distribuídos poderão modificar ou até mesmo criar novas funções para a biblioteca.
Algumas sugestões de projetos são: uma função que crie automaticamente um determinado número de processos em quaisquer máquinas cadastradas, estudo de balanceamento de carga, rotinas mais sofisticadas de troca de mensagens que permitam enviar outros tipos de dados além de cadeias de caracteres, uso padrões para troca de parâmetros entre diferentes arquiteturas etc.
[GEI 93] GEIST, Al; et all. PVM 3: User's Guide and Reference Manual. Oak Ridge National Laboratory, Oak Ridge, May, 1993.
[KIT 95] KITAJIMA, João Paulo F. W. Programação Paralela Utilizando Mensagens. XIV Jornada de Atualização em Informática, XV Congresso da Sociedade Brasileira de Computação. UFRGS, Canela, 29 de julho a 4 de agosto de 1995. 41p.
[LIN 96] LINUX SYSTEMS LABS. LINUX: The Complete Reference. Editors: John Purcell; Amanda Robinson. Linux Systems Labs. 4th edition.
[SHA 87] SHAW, Myril Clement; SHAW, Susan Soltis. UNIX Internals: A Systems Operation Handbook. TAB Books, Inc., Blue Ridge Summit, 1987. 206p.
[SUN 90] SUN MICROSYSTEMS. Network Programming Guide. Mountain View: Sun microsystems, 1990, 356p.