UNIVERSIDADE LUTERANA DO BRASIL CENTRO DE CIÊNCIAS NATURAIS E EXATAS DEPARTAMENTO DE INFORMÁTICA AMBIENTE DE MULTIPROCESSAMENTO PARA UMA REDE DE MÁQUINAS UNIX: MANUAL DE IMPLEMENTAÇÃO por ELGIO SCHLEMER Prof. Roland Teodorowitsch Orientador Canoas, dezembro de 1996 Agradeço à meu s pais, Ern o e Eroni, pelo apoio incondicion al em tod os os momentos; aos meus professo res, em esp ecial a Roland T eodorowitsch , pela dedicada orient ação; a Miguel Roberto Bor ges, por sua incansável col aboração e cessão de recursos; e acim a de tudo , agradeço à Deu s, por ter colocado essas pessoas em meu caminho. “Senhor, deste−me tan to! Dá−m e uma coisa a mais: um coração agradecido”. (Geor ge Herbert ) SUMÁRIO LISTA DE FIGURAS RESUMO Este trabalho descreve a implementação de um ambiente que transforma uma rede UNI X em um mult iprocessador virtu al, assim co mo faz o PVM. Basi cament e, o ambiente é compost o de uma bi bliot eca, l incada com programas dos usuários e de um processo que control a a execução e comunicação entre processo s no sistema. ABSTRACT Thi s wor k describes th e implementation of a environ ment that turn s a UNIX network into a vir tual m ultipr ocessor, li ke PVM does. Basi cally , the environm ent is i ntegrated by a l ibrar y linked with th e users’ progr ams and a pr ocess that controls the execution and communicatio ns between process in the system . 1INTRODUÇÃO Ambientes mul tiprocessado s baseados em UNIX são, do ponto de vista didático, difí ceis de ent ender em função de sua complexidade e da escassa lit eratura em relação ao seu f uncionamento interno. O Ambiente impl ementado f ornece fer rament as de estudo bastante práticas, facilitando o aprendizado. O AMRU (Ambi ente de Multipro cessamento para uma Rede de máquinas Uni x) ut iliza, basicam ente, chamadas de RPC ( Remo te Procedure Call) disp onívei s n o UNIX e rotinas básicas do C. Fornece todas as rotinas necessári as à criação de um a máqui na paral ela virt ual, como, por exemplo, serv iços de “sen d” e “receive” (envio e recep ção de mensagens ent re processos) e manipulação de pro cessos. Um ambi ente paralelo virtual é aquele que perm ite progr amar como se est ivesse uti lizando uma máquina paral ela, quando na verdade estamos sim ulando−a. O Ambiente de Multiprocessament o para uma Rede d e máqu inas Uni x (AMRU) , faz esta simul ação em cima de uma rede ro dando UNIX ou compatível . Este sistem a fornece r otinas de criação e comunicação de processos, possibilit ando rodar vár ios processos ao m esmo tempo, dividido s entre as máquinas da r ede, e controlá−los com o se esti vessem r odando em um processador local . Par a isso ser possível, faz−se uso de chamadas RPC ( Remote Procedure Call ) [SUN 90], pacote de comunicação exi stent e no UNIX, à um processo 9 ant eriorm ente inicializado nas máqui nas da rede (chamado de daemon), e, através dele, executa−se os serviços necessários. Estes servi ços podem ser: in serir uma nova máq uina no sistema, tor nando−a um “pr ocessador virtual”; criar um novo processo em uma máquina especif icada; enviar um m ensag em para um processo do sistem a ou receber uma mensag em de um pr ocesso do si stema. Par a tanto, o capítulo 2 fornece um a visão geral do ambi ente. Em seguida o capítulo 3 descreve com det alhes a implementação de cada função do ambi ente. Duas aplicações são discut idas no capít ulo 4. Por fim, o capít ulo 5 apresenta as conclusões. 2PROGRAMAÇÃO PARALELA Máq uinas paralel as são aquelas que possuem dois ou mais processador es e são capazes de execut ar pelo menos doi s p rocessos simult aneamen te. Ou sej a, em determinado momento, duas instruções estão sendo execut adas ao mesmo tem po. Essas máquinas são caras, e portanto, difícei s de serem encontradas. Em função di sto, alternativas mais econô micas for am desenvolvidas. Uma destas alter nativas é a util ização de uma r ede de comput adores com o se fosse uma única máquina com vári os pro cessad ores (figu ra 2. 1). Um sistema rodando nesta rede seria capaz de mascara−l a, possibilit ando aos processos “enxergá−la” como um a máqu ina paralela. Figura 2.1− Ilustração de uma rede Neste caso, cada máquina é vista como se fosse um p rocessador de uma máq uina paralela virt ual, e pode−se, portan to, util izá−la como t al. Este é o pri ncipal objetivo deste trabal ho d e conclusão, desenvolver um ambi ente que 11 exp lore os recursos de uma r ede, no qual pode−se inici ar a execução d e múltipl os pro cessos e atr avés do qual estes processos possam se com unicar . Um exemplo das possibilidades do sist ema proposto neste trabalho é mostrado na figura 2.2. Através de uma série de cham adas à uma bibliot eca de fun ções, o usuário pode: agregar uma máqui na da rede ao seu mult iprocessador virtual ( am_addhost()), iniciar um processo em al guma máq uina da red e ( am_ startproc()) e enviar (am_send() ) ou receber ( am_receive( )) mensagens. 12 #include "am_lib.c" main() { int aux; long int tid; char msg[100]; int m1,m2, p1,p2; m1=am_init(); /* inicializa o sistema parelelo virtual */ if (m1==−1) exit(0); m1=am_addhost("verdi"); /* insere a maquina verdi no sistema */ if (m1==−1) exit(0); m2=am_addhost("vivaldi"); /* insere a maquina vivaldi no sistema */ if (m2==−1) exit(0); p1=am_startproc(m1,"exemplo2"); /* inicia o processo exemplo2 na maquina verdi */ if (p1==−1) exit(0); p2=am_startproc(m2,"exemplo2"); /* inicia o processo exemplo2 na maquina vivaldi */ if (p1==−1) exit(0); am_send(p1,"Querida Verdi..."); /*envia msg para processo na am_send(p2,"Querida Vivaldi..."); /*envia msg para processo na tid=am_receive(−1,msg); /*recebe uma msg de qualquer tid=am_receive(−1,msg); /*recebe uma msg de qualquer } maq 1*/ maq 2*/ processo*/ processo*/ Figura 2.2− Programa exemplo1.c Neste exemplo são criados dois processos “exem plo2”, um executará na máquina verdi e o outr o na máqu ina vivaldi. En via−se, em seguida, uma mensagem para cada um e espera−se pel as respostas. #include "am_lib.c" main() { long int tid; char msg[100],nome[30]; gethostname(nome,64); /*obtem o nome desta máquina*/ tid=am_receive(−1,msg); /*recebe mensagem de qualquer processo */ fprintf(stderr,"Recebi mensagem ’%s’ do processo %d\n",msg,tid); /*imprime a mensagem na tela*/ sprintf(msg,"Eu sou a maquina %s",nome); am_send(tid,msg); /*envia mensagem*/ } Figura 2.3− Programa exemplo2.c Na fi gura 2. 3 t emos o có digo do program a exemplo2. Sua função é receber mensagem d e qual quer processo e enviar uma resposta. 3IMPLEMENTAÇÃO O Ambien te Mul tipro cessad o para uma Rede de máquin as Unix (AMRU), foi total mente desenvolvido em C sobre o sistem a op eracion al LINUX (com patível com UNIX) versão 2.2. Foram utilizadas, para tant o, as máquinas verd i e vi valdi do Laboratóri o de In formát ica da ULBRA. Foi implem entad a uma biblioteca contendo as f unções necessárias à util ização do “AMRU” (am_l ib.c) e um programa daemon (amrud.c). Um pro grama daemon é um processo residente qu e permanece em execução continua. 3.1Biblioteca am_lib.c Esta biblioteca contém as fun ções necessárias à manipulação d o Amb iente Multiprogramado. Deve ser i ncluída na imp lementação qu e use as caract erísti cas deste si stema. Estruturas e Variáveis Globais A bibl ioteca cri a duas estrut uras par a manipular pr ocessos e máquinas instaladas. A estr utura AM_L ISTHOS T manipula a lista de máquinas que est ão disponí veis no sist ema. S ua def inição pode ser vi sta na figur a 2.1. 14 struct AM_LISTHOST{ char nome[20]; int numero; char status; }; /* Lista das hosts participantes do sistema nome= nome da host (ex. verdi) numero= numero pelo qual sera reconhecida no sistema status= 0 se campo disponivel, 1 se ocupado por host ativa */ Figura 2.1− Estrutura AM_LISTHOST O registro nome cor responde ao nome da máquina, através da qual ela é identificada e referenciada (por exemplo: verdi, vivaldi, mozart, etc). O regi stro numero é o número pel o qual ela é conhecida no si stema. Este número é incrementado a cada cham ada de am_ addhost(), sendo armazenad o na variável gl obal am_nummaq. Desta f orma a prim eira máquina a ser inserida receberá o número zero , a segunda um, e assim sucessivam ente. Por últi mo, o regi stro stat us fornece informações sobre o reg istro dentro da est rutura. Se stat us está em zero, si gnifica q ue aquel a posição está di sponível para ser u sada por uma nova m áquina, se for um, indica a presença de uma máq uina ativa. Esta estru tura é usada pela v ariável global am_hostli st, que é um vetor de AM_MAXMA Q posições (d efinido como const ante) . A estr utura AM_L ISTPROC manipula informações sobre os processos instalados pelo sistema. Sua definição pode ser vi sta na figur a 2.2. struct AM_LISTPROC{ long int nproc; int nmaq; char status; char nome[20]; }; /* Lista das processos inicializados por startproc nproc= numero do processo dado pelo sistema (uma composicao do numero da host e o id do processo nmaq= numero da host onde encontra−se este processo nome= nome do processo. */ Figura 3.2− Estrutura AM_LISTPROC O registro npro c g uarda o número do pro cesso criado. Este número é gerado pelo próprio sist ema e difere do núm ero de processo (id) usado 15 internamente pelo sistema operacional. O r egistr o nma q co ntém a inf ormação do n úmero da máquina à qual pertence o r eferi do processo, número este que coi ncide com o regi stro numero em am_ hostli st. Stat us po de ser zer o ou um. Zer o indi ca que este registro não está ocupado por nenhu m pr ocesso e um que exi ste um processo ativo e pronto para ser mani pulado . E, por fim, o campo nome contém uma str ing designando o nome do pro cesso. A variável global am_listproc ut iliza esta estrut ura, p ara declarar um v etor de AM_M AXPROC posiçõ es (definid a como const ante) . A vari ável am_ servidor indi ca se este processo é o u não o processo serv idor, setada para zer o caso afirmativo e para um caso contrár io. Torna−se servid or o p rocesso que executar a função am_init( ) . Função am_init() Util izada para inici alizar o p rocesso corren te como ser vidor, in iciali za as li stas de máqui nas e de processos, seta a variável indicadora de servi dor para zero e já insere a própria m áquina no sistema se registran do com o apl icação servido ra junto ao amrud . Se a apl icação n ão executar esta função, ela será considerada como uma aplicação cliente, não controlará os pr ocesso s (man ipulação das listas) e não poder á inserir máquinas no sist ema e nem inicializar p rocessos, pois são caracter ísticas ex clusiv as do processo servido r. O código dest a função pode ser visto n a figura 3.3. 16 int am_init() { char nome[50],envia[100],recebe[25]; int conta; char *env,*precebe; int stat; for (conta=0;conta<AM_MAXMAQ;conta++) am_listmaq[conta].status=0; for (conta=0;conta<AM_MAXPROC;conta++) am_listproc[conta].status=0; gethostname(nome,64); am_servidor=0; conta=am_addhost(nome); sprintf(envia,"8#%d#",getpid()); env=envia; precebe=recebe; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); am_listproc[am_numproc].status=1; am_listproc[am_numproc].nmaq=am_myhost(); am_listproc[am_numproc].nproc=am_mytid(); return(conta); } Figura 3.3− Função am_init() Função am_hostname() Obt ém o nome da m áquina cuj o número é maq. Recebe u m inteiro com o número da máquin a, e uma stri ng para armazenar o n ome obtido. O códig o desta função pode ser vi sto na figur a 3.4 . É necessário, às vezes conhecer o no me de determi nada máqui na ( para usar este no me em uma chamada RPC, por exempl o). A list a de máquinas é controlada e conhecida apenas pelo servidor . Deve−se, portanto, r equisi tar ao serv idor que co nsult e sua base de dados e retor ne o número desta máquina. Ent retanto, para se fazer uma chamada de RP C, preci sa−se informar o no me da máqu ina, e uma vez que a aplicação cliente não tem est a informação (apenas seu daemon possu i), deve−se deixar que o daemon local execute est a tarefa que é reali zada da seg uinte forma: − obtém −se atr avés da função gethostname o nom e da m áquina local ; 17 − prepara−se a string de envio composta de duas partes: o núm ero 6 indi cando que o amrud (daemon) deve execu tar o ser viço 6, seguido do dado referente ao númer o da máquina para qual desej a−se o bter o nome; − faz−se uma chamada RPC ao amrud local , passando a string e recebendo outra string; − o amr ud retornará um entre três padrões de resposta. S e a string retornada possuir o caracter ‘1’ em sua prim eira posição, i ndica que o amru d retornou o nome da máquina requi sitada, nome este que segue após o caracter ‘#’ na stri ng. Se, porém, a stri ng retornada começar com ‘0’, o am rud local não teve co ndições de lo calizar a máqu ina porque el a não está inseri da n o sistema, ou seja, não existe. O caracter ‘2’ na prim eira posição ind ica que o amru d local não teve co ndições de l ocalizar a máquina porque ele não é o serv idor e não conhece a lista d e máquinas. Nest e caso, segue−se após o car acter ‘# ’ o nome da máqui na que possui o amr ud servi dor, devendo a fun ção f azer uma n ova chamada de RPC, desta vez, porém, destinada ao amru d ser vidor. Nest a chamada, se a st ring retornada não inici ar com ‘1’, certam ente esta m áquina não existe. − retor na−se 1, caso a máquina tenha sid o encontrada, ou −1 caso contrário . É int eressante sal ientar o fato de t odas as chamadas de RPC serem através da troca de string s. Optou−se por stri ngs pelo fat o d as chamadas envolverem inteir os e str ings sim ultaneamente, e intei ros podem facilm ente ser co nverti dos para str ings e vice−versa. 18 int am_hostname(int maq,char *nome) /* Esta funcao retorna o nome da maquina cujo numero e maq */ { char nome2[20],*env,envia[20],recebe[20],*precebe,stat; /* nome=usada para guardar o nome da host, conta=contador for*/ gethostname(nome,64); /* obtem o nome da host no laco a seguir, procurara pela ocorrencia do nome da host na lista de hosts do sistema retornando o numero desta*/ precebe=recebe; sprintf(envia,"6#%d#",maq); env=envia; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if (recebe[0]==’0’) return(−1); if (recebe[0]==’1’) {sprintf(nome,"%s",recebe+2); return(1); } if (recebe[0]==’2’) {sprintf(envia,"6#%d#",maq); env=envia; stat = callrpc ( recebe+2, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if (recebe[0]==’1’) {sprintf(nome,"%s",recebe+2); return(1); } } return(−1); } Figura 3.4− Função am_hostname() Função am_myhost() Utilizada para obt er o número da máquina local . Reto rna o número dest a ou −1 se ocorreu erro (máquina não está no sistema). Para obter o número da máquina local, deve−se r equisitar o ser viço 5 ao daem on de sua própria máquina ( env recebe ‘5’ na sua declaração), pois ela “sabe” o seu número, qu e lhe é passad o quando o am_addhost() é executado . A função am_strint () é executada para ret irar um i nteir o da stri ng. Na f igura 3 .5 temos o código da função am_myhost( ). 19 int am_myhost() /* Esta funcao retorna o numero da maquina que o requisitou ou −1 caso haja algum erro (esta maquina nao tem processo daemon, por exemplo */ { char nome[20],*env="5",recebe[50],*precebe,stat; /* nome=usada para guardar o nome da host, conta=contador for*/ int maq=0; gethostname(nome,64); /* obtem o nome da host no laco a seguir, procurara pela ocorrencia do nome da host na lista de hosts do sistema retornando o numero desta*/ precebe=recebe; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if (recebe[0]==’1’) {am_strint(recebe); maq=am_strint(recebe); return(maq); } return(−1); } Figura 3.5− Função am_myhost() Função am_mytid() Esta função retorna o n úmero pelo qual este processo é co nheci do no sistema (am _pid) podendo ser vista na fi gura 3.6 . Uma vez q ue este número é uma composição d o númer o da máquina com o i dentif icador do pro cesso, basta obter estas duas i nformações para gerar o am_pid do si stema. O número da máquina pode ser obtid o pela fu nção am_ myhost () e o identificador d o processo pela função do UNIX get pid() . Um deslocam ento à esq uerda de 12 po sições n o número da máquina, adicionado através de “OU” lógi co ao i denti ficador retor nado pela get pid() com põe o número do processo no sist ema (am_pid) . 20 long int am_mytid() /* Obtem o numero pelo qual o processo e conhecido no sistema Este numero e uma comnposicao binaria descrita em am_startproc */ {long int maq; pid_t pid; maq=am_myhost(); /*otem o numero da host */ maq=maq<<12; /*compoe o numero da maquina */ pid=getpid(); /*obtem o numero do processo (no SO)*/ maq=maq|pid; return(maq); } Figura 3.6− Função am_mytid() Função am_converte() Esta fun ção t em como objetiv o receber o número de uma máquin a e o número de um pr ocesso (seu identifi cador) e retor nar o número am_pid deste processo. Pode ser vista na figura 3.7. Par a ger ar o am_ pid ( número pel o qual este processo será conheci do no sistema) deve−se fazer um deslocamento à esquerda d e 12 posições do número da máq uina. Par a um númer o de m áquina i gual a dois, um deslocamento a esq uerda de 12 posições resultará em 8192. Este número deverá ser adi cionado ( através d a operação ou lógico ) ao i dentif icador de pr ocesso n o sistema para formar o número am_pi d do processo, que é retor nado pela função. long int converte (int maq,long int tid) {long int aux; aux=maq<<12; return(aux|tid); } Figura 3.7− Função am_converte() 21 Função am_strint() Esta função r etira uma parte num érica da string data , conver te−a para inteiro e devolve à v ariável data o que sobrou . O número i nteiro gerado é retorn ado à função que o cham ou. Na figur a 3.8 temos seu código . long int am_strint (char *am_data) {char *am_aux; long int am_total=0; int am_sinal=1; for (am_aux=am_data;(*am_aux<’0’)||(*am_aux>’9’);am_aux++) {if (am_aux==NULL) return(−1); if (*am_aux==’−’) am_sinal=−1; } for (;(*am_aux>=’0’)&&(*am_aux<=’9’);am_aux++) am_total=(am_total*10) + (*am_aux−’0’); sprintf(am_data,"%s",am_aux); return(am_total*am_sinal); } Figura 3.8− Função am_strint() Função am_addhost() Tem o objeti vo de inserir uma nov a máqui na no si stema, r etornando o seu número (apenas a apl icação servido ra pode executar est a tarefa). Esta inserção é f eita enviando−se uma mensagem de teste e algumas infor mações ao amrud já instalado na máquina (a instal ação dest e amr ud deve ser feita explicit amente) . Ini cialmen te é feita uma verificação para determ inar se est a é a apl icação servidor a ou não. Isto é feito at ravés da verificação da var iável am_ servidor, que deve ser zero (esta variável é reset ada quando am_init() fo r executado ). Se não f or zer o, si gnifica que esta aplicação n ão é a ser vidora, por tanto não pod e i nserir máquinas no sistema e a f unção am_ addhost() retornará −1 sinalizando u m erro. Caso sej a o servidor, f az−se uma verifi cação na l ista de máquinas para ver se já não existe esta máquina no sistema, se existi r apenas retorna seu 22 número. Caso não exi sta, prepara−se a mensagem que será enviada ao amru d. Esta mensag em é composta do número 1, indicando tratar−se do servi ço 1, seg uido do número que esta máqui na receberá e o nome da máquina servidora. Desta forma o amrud destino saberá a q uem repo rtar as chamadas de RPC. Env ia−se então esta stri ng ao amrud devendo este ret ornar 1, caso não haja err o. Procede−se en tão ao cadastramento dest a nova máqui na na list a de máquinas do sistema. Após alt erar esta list a, deve−se co municar ao amrud serv idor est a nova adesão, para q ue ele possa responder à pedidos como am_ hostname. Isto é realizado sinal izando o serviço n úmero 7, enviando−se o número e o nome da nova m áquina ao amrud servi dor q ue n o caso encontra−se nesta máqui na, o que indica q ue a cham ada de RP C deve ser feita à m áquina reto rnada pela função gethost name(). Estas informaçõ es são em pacotadas em uma string. Apó s isto, f inalm ente ret orna−se o número da máq uina in serida, ao mesmo t empo em que increm enta−se o contador de máquinas. O códi go com pleto desta função pode ser vi sto na figur a 3.9. 23 int am_addhost(char *maquina) { /* Este programa faz uma chamada RPC para o programa AMRUD em execucao na maquina remota. Se este programa estiver ativado ele respondera conforme solicitado. Retorna o numero da host ou −1 se houve erro */ int stat,conta; char recebe[2],buf[30],nome[20]; char *envia,*precebe; if (am_servidor) return(−1); /* Verifica se a maquina ja nao esta inserida no sistema */ for(conta=0;conta<am_nummaq;conta++) if (strcmp(maquina,am_listmaq[conta].nome)==0) {am_listmaq[conta].status=1; return(am_listmaq[conta].numero); } precebe=recebe; envia=buf; gethostname(nome,64); sprintf(buf,"1#%d#%s",am_nummaq,nome); /* envia o nome e o numero de sua host para a host inserida saber quem a criou */ stat = callrpc ( maquina, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &envia, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n",maquina); return(−1); } else if (strcmp("1",recebe)==0) /*procura por uma posicao disponivel na lista de hosts, a posicao e considerada disponivel se status for 0 */ for (conta=0;conta<AM_MAXMAQ;conta++) if (!am_listmaq[conta].status) {strcpy(am_listmaq[am_nummaq].nome,maquina); /* cadastra a nova host inserida na lista de hosts */ am_listmaq[conta].numero=am_nummaq; am_listmaq[conta].status=1; sprintf(buf,"7#%d#%s",am_nummaq,maquina); gethostname(nome,64); envia=buf; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &envia, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n",nome); return(−1); } return(am_nummaq++); } return(−1); } Figura 3.9− Função am_addhost() 24 Função am_startproc() Ini cia um pr ocesso na máquina especifi cada e r etorna o am_pid dele (figura 3 .10). Somente a aplicação servid ora po de executar esta função. Por tanto, assi m com o na função am_ addhost(), deve−se pri meiramente testar se trat a−se da aplicação servidor a através da verif icação da vari ável am_servid or. Após isto , se trat ar−se da aplicação servi dora, ver ifica−se a exi stênci a da máquina especi ficada consu ltando −se a l ista de máqu inas. No caso de não existi r, retorna−se uma mensagem de erro (−1) . Deve−se então empacot ar a string a ser env iada ao amr ud da máquina destino. Esta string é composta de duas part es: o caracter ‘4’ ( indicando trat ar−se d o servi ço 4) e o nome do pr ocesso, separados p elo caracter ‘# ’. Executa−se então uma chamada RPC à máquina especif icada, enviando−se a string empacotada. Apó s a chamada RPC, verifi ca−se o val or reto rnado. Se este for “−1”, sign ifica qu e não foi possível iniciar o pr ocesso (por motiv os que ser ão visto s na ex plicação deste serviço) , caso con trário a st ring terá então o núm ero do pro cesso criado , que no caso é o id entifi cador gerado pelo SO na máquina local. Faz−se então a conversão para am_pi d através da função am_con verte( ). Com o p rocesso criado, seu am_ pid ger ado, faz−se então o cadastram ento deste processo na l ista de processos existente, retornando−se o número (am_pi d) dest e à função qu e o executou. 25 long int am_startproc(int maq,char *nomep) {/* Inicia o processo de nome nomep na maquina maq, retorna o numero do processo criado ou −1 se houve erro. O numero do processo e uma composicao feita a partir do numero da host com o ip do processo da seguinte forma: faz−se um shift left do numero da host em 12 posicoes como exemplo, se for maquina 1, expresso como 0000 0000 0000 0001b, ao fazer esse shift, fica 0001 0000 0000 0000b, ou seja, 4096 em decimal, soma−se entao a este numero o id do processo, por exemplo, processo 240, expresso como 0000 0000 1111 0000b, resoltando em 4336 (0001 0000 1111 0000b). Assim sendo, como o long int e expresso em 4 bytes, usamos o primeiro byte para numero da host (total de 256) e os outros tres para processos (4096). Verificou−se que os ips criados pelo Linux nao ultrapassam este limite. */ int stat,numero=−1,conta; char envia[20],recebe[5],*precebe,*env; long int proc; if (am_servidor) return(−1); /* procura pela host na list de hosts */ for(conta=0;conta<AM_MAXMAQ;conta++) if ((maq==am_listmaq[conta].numero)&&(am_listmaq[conta].status)) numero=conta; if (numero==−1) {fprintf(stderr,"numero de maquina nao existe\n"); return(−1); } sprintf(envia,"4#%s",nomep); /* Gera string a ser enviada ao Deamon na host, 4 indica servico de am_startproc seguido do nome do processo que deve sr criado */ precebe=recebe; env=envia; stat = callrpc(am_listmaq[numero].nome,RUSERSPROG,RUSERSVERS, RUSERSPROC_NUM,xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n", am_listmaq[numero].nome); return(−1);} if (strcmp(recebe,"−1")==0) {printf("Nao foi possivel iniciar processo %s\n",nomep); return(−1);} proc=am_strint(recebe); proc=am_converte(maq,proc); /*em recebe existe o numero id (do SO) do processo criado, obtem−se agora o numero do processo no sistema */ for (conta=0;conta<AM_MAXPROC;conta++) /* Cadastra o processo criado na lista de processos */ if (!am_listproc[conta].status) {am_listproc[conta].status=1; strcpy(am_listproc[conta].nome,nomep); am_listproc[conta].nproc=proc; am_listproc[conta].nmaq=maq; return(proc); } return(−1); } Figura 3.10− Função am_startproc() 26 Função am_send() Envia um a mensagem msg ao pr ocesso proc, ambos passados como parâmetros. Seu có digo pode ser visto na figura 3.11 . Com o este pro cesso pode estar em q ualquer máqui na participant e do sistema, não é possí vel usar as rot inas padr ões de troca de mensagens ent re pro cessos já existentes no UNIX. Portanto, cada amru d é responsável pela buf erização das mensagens de seus filhos, dev endo as mensagens ser endereçadas ao amrud da máquina à qual o pr ocesso dest ino é filho. Com eça−se descobrindo qual é a máquina à qual p ertence este processo. Um deslocament o à d ireita de 12 posições na variável proc fornece o número da máqu ina e um “E” lógico com 4 095 o identif icador do pr ocesso. Prepara−se então a string que deverá ser env iada ao amrud. Ela é com posta d o caracter ‘2 ’, indicando o serviço de send, mai s o am_p id do pro cesso que envia a mensagem, obtido pel a função am_mytid() o identificador d o pro cesso destino, e pela mensagem p ropriamente dita. Todos est es p arâmetros são empacotados na string envia, e separ ados um do outr o pelo caracter ’#’. Procede−se então à cham ada de RPC à m áquina onde se encontra o pro cesso d estino, cujo nom e foi ant eriorm ente ob tido pel a função am_hostname( ) e ver ifica−se o valor r etornado. S e este for “−1”, signifi ca que não existe o processo destino, caso contrário a mensagem foi enviada com sucesso e a função retor nará 1. 27 int am_send(long int proc,char *msg) { /* Envia uma mensagem para o processo especificado */ int stat,conta,numero=−1; char envia[100],nome[30]; char recebe[20],*precebe,*env; numero=proc>>12; stat=am_hostname(numero,nome); if (stat==−1) return(−1); proc=proc&4095; /* Obtem o real numero do processo, seu id, o numero pelo qual ele e conhecido pelo SO de sua maquina */ sprintf(envia,"2#%d#%d#%s",am_mytid(),proc,msg); /* Gera a string a ser enviada ao Daemon da maquina que possue este processo. O numero 2 indica que trata−se do servico 2. Segue−se o numero do processo que executou o servico e o numero do processo para quem deseja−se enviar a mensagem. */ precebe=recebe; env=envia; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n", nome); return(−1);} if (strcmp(recebe,"−1")==0) {printf("Nao foi possivel achar processo %s\n",proc); return(−1); } return(1); } Figura 3.11− Função am_send() Função am_receive() Recebe uma m ensagem do processo esp ecificado (proc), retornando seu número. S e o parâmet ro passado em proc for −1, am_receive receberá mensagem de qualquer processo. Veja có digo na figur a 3.12 . Em pri meiro lu gar gera−se a stri ng que será enviada ao amrud local (isto porque cada am rud gerencia as mensagens dos pr ocessos da sua máquina). E sta strin g começa com o identi ficad or do servi ço, que no caso é ‘3’ ( receive), segui da d o número ident ificad or d o pro cesso que desej a receber a mensagem e do am _pid do processo de q uem se deseja receber a mensagem. 28 Env ia−se esta st ring ao amrud local continuamente at é q ue exista uma mensagem para este processo (espera ocupada) . Depo is que amrud retorn ar a st ring, procede−se à conversão dos parâmetros, pois esta st ring contém o nú mero do processo que enviou a mensagem seg uido d a mensagem enviada. 29 int am_receive(int proc,char *msg) { /* Recebe uma mensagem do processo especificado, Se proc for −1, indica que a mensagem pode ser de qualquer processo */ int stat,conta,numero=−1; char envia[20],recebe[100],nome[20],*precebe,*env; sprintf(envia,"3#%d#%d#",getpid(),proc); /* Note que aqui, ao inves de enviar o numero do processo no sistema, envia−se o seu id apenas. Isso porque quem fara o tratamento de mensagens e o Daemon desta maquina (esta propria que executou o am_receive), e este daemon conhece seus "filhos" pelo seu id. */ precebe=recebe; env=envia; gethostname(nome,64); /* O buffer de msgs e controlado pelo Daemon de cada host, cada Daemon controla as msgs de seus filhos. Por isso esta chamada de RPC e para a propria host que executou esta funcao. */ for (;recebe[0]!=’1’;) { stat = callrpc (nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao nesta maquina \n"); return(−1); } } am_strint(recebe); numero=am_strint(recebe); sprintf(msg,"%s",recebe+1); return(numero); } Figura 3.12− Função am_receive() 3.2Programa Daemon (amrud.c) Este pr ograma deve ser ant eriorm ente inst alado em cada máquina que part icipará do si stema. Atribui−se o nome d e amr ud a todo o daemon do sistema rodan do nas m áquinas i nseridas por am_ addhost() e chama−se de amru d ser vidor o daem on da máqui na ser vidora, ou seja, aquela responsável pela in stalação do sistem a e qu e executou a função am_init( ). Cada amrud é r esponsável em atender as chamad as RP C destinadas à sua máquina, bem como m anter e gerenciar as mensagens destinadas à seus filhos. 30 Estruturas e variáveis globais Amr ud cria as m esmas duas estr uturas par a manipular processos e máquinas inst aladas exi stente na bib lioteca AM_ LIB, por ém co m algumas inform ações diferentes. Sua definição encontra−se na figura 3.13. Na est rutura AM_LISTHOST não exi ste o campo st atus e na AM_LISTPROC não existe o campo nom e e nmaq, e existem outros três campos: msg, orig em e espera. O campo msg é um vetor de caracteres bi−direcional de AM_MSGMAX posi ções, cada uma com AM_TAMMSG caracteres. Este campo serve como buf fer de mensagens recebidas, podendo, portanto, arm azenar até AM_MSGMAX mensagens para cada processo, cada mensagem poden do conter AM_T AMMSG caracteres. O camp o or igem é um vetor de int eiros de AM_MSGMAX posições. Nel e é ar mazenado o númer o am_pid de cada processo que env iou um a mensagem. É mant ida um a relação ent re a p osição do vetor or igem, com a posi ção do vetor msg, ou seja, o processo de am_pi d origem[0] gravou sua mensagem no campo msg[0] . O campo esp era armazena o am_ pid do processo de q uem se esper a uma men sagem, e também serve para testar a disponibilidade do registro, isto é, se esp era tiver −3, indica que todo aquel e regist ro não pertence ainda à nen hum pro cesso, podendo ser usado quand o um serviço de startproc for r equisi tado; se po ssuir −2 , este registro j á pertence à um pro cesso e este no mom ento não aguarda mensagens. Se, no entanto possuir −1, o processo espera qualquer men sagem. Outro v alor i ndica o am_pi d do pr ocesso de quem se ag uarda mensagem . 31 O campo status tem uma função d iferent e que na AM_LI B. Aq ui el e serv e apenas para contar o núm ero de men sagen s existen tes no mo mento para aquel e pro cesso. 32 struct AM_LISTHOST{ char nome[20]; int numero; }am_listmaq[AM_MAXPROC]; /* Lista das hosts participantes do sistema nome= nome da host (ex. verdi), −1 registro disponivel numero= numero pelo qual sera reconhecida no sistema */ struct AM_LISTPROC { long int nproc; char status; char msg[AM_MSGMAX][AM_TAMMSG]; int origem[AM_MSGMAX]; int espera; }am_listproc[AM_MAXPROC]; /* Lista de processos. Difere−se um pouco da lista existente na biblioteca, pelo fato de aqui nao existir o nome do processo, nproc e o id dele do SO e ter campos para buferizar as msg. nproc=numero do processo msg= buffers de mensagem pendentes, cada processo pode ter ate AM_MSGMAX mensagens esperando, cada uma de ate AM_TAMMSG caracteres. status= 0= sem msg para ele, >0=numero de msg recebidas espera= −3 campo disponivel, −2 ocupado−processo nao espera msg, −1 ocupado, processo espera msg de qualquer pro− cesso, >=0 = numero do processo de quem esta esperando uma mensagem. origem=processos que enviaram mensagem, −1=disponivel. */ char am_minhahost=0; /* se =0, este e o servidor, >0 designa o numero desta host */ char am_servername[30]; /* nome da host servidora */ Figura 3.13− Estruturas e Variáveis do amrud Existem apenas quatro variáveis gl obais: am_minhahost, que armazena o número da sua pr ópria m áquina; a st ring am_servername, que guar da o no me da máquina q ue possui o amr ud servidor; am_ listmaq, uma estrutura que armazena informações sobre as máquinas inseridas e a variável am_listproc que ar mazena infor mações dos processos inseridos no si stema. Função am_msgrec() A fun ção am_ msgrec( ) é responsável pelo trat amento interno de men sagens. Verif ica inicialmente se existe no buffer alguma mensagem do pid_ msg para o pid_ req. Se existi r, empacota−a na variável msg passada por parâmetro e retorna 1. Senão, retor na −1. 33 Prim eiram ente percorre−se a l ista de processos à procura do processo número pid_req. Tão logo a mesma seja l ocalizada, faz−se um t este co m a vari ável stat us, po is se esta for zero, não existem m ensag ens par a este processo e já se pod e retor nar −1. De posse da po sição no regist ro pert encent e ao processo que r equisitou ao pedido, procura−se pela mensagem vin da do processo pid_msg no buf fer. Se pid_msg f or −1 , então pega−se a prim eira mensagem buferizada. Após gerar a string de r etorno (já empacotada com o identificador d e sucesso, 1, o identificador do processo que en viou a m ensagem e a mensagem enviada) dev e−se ajustar os dad os no buffer, ou seja, decrementar a var iável status (contadora d e mensagens) e lim par o campo de ond e ela foi extraída. Observe que o n úmero do processo que enviou a mensagem é retornad o ao processo que a requisit ou, isto porque caso pid_msg tenha sido −1, o processo pid_req t erá meios de saber de q uem realment e veio a men sagem . 34 int am_msgrec(int am_pidreq,int am_pidmsg,char *am_msg) {/*Servico de recebimento de mensagens, se existir mensagem de am_pidmsg para am_pidreq, esta sera armazenada em am_msg e a funcao retorna 1, senao a funcao retorna −1 (o servico de send e receive e sincrono, mas a decisao de por o processo para dormir e do am_receive e nao do Daemon este ultimo apenas informa ao am_receive pelo retorno do RPC que nao existe a am_msg requisitada). Se am_pidmsg for −1, indica receber msg de qualquer processo. */ int conta,conta2; /* procura a posicao do buffer pertencente ao processo que requisitou o pedido de receive */ for (conta=0;conta<AM_MAXPROC;conta++) if (am_listproc[conta].nproc==am_pidreq) {if (am_listproc[conta].status==0) /* se status e zero, nao ha msg para este processo */ return(−1); if (am_pidmsg==−1) /* −1 indica receber de qualquer processo */ for (conta2=0;conta2<AM_MSGMAX;conta2++) if (am_listproc[conta].origem[conta2]!=−1) {sprintf(am_msg,"1#%d#%s",am_listproc[conta]. origem[conta2],am_listproc[conta].msg[conta2]); /*monta a string de retorno, 1 indica sucesso, segue o numero do processo qeu enviou a msg (util caso am_pidmsg fosse −1) e em seguida a mensagem */ am_listproc[conta].origem[conta2]=−1; /*disponibiliza a posicao do buffer */ am_listproc[conta].status−−; return(1); } for (conta2=0;conta2<AM_MSGMAX;conta2++) if (am_listproc[conta].origem[conta2]==am_pidmsg) { sprintf(am_msg,"1#%d#%s",am_pidmsg,am_listproc[conta]. msg[conta2]); am_listproc[conta].origem[conta2]=−1; am_listproc[conta].status−−; return(1); } } return(−1); } Figura 3.14− Função am_msgrec() Função am_msgsend() Esta função armazena no buf fer u ma mensagem msg do processo origem para o processo destino. P ara processo origem deve−se f ornecer o v alor de seu am_pid e para o d estino apenas o valor de seu identi ficador. Isto porque a manipul ação de mensagens é feit a por cada am rud, sendo q ue ele conhece seus filhos pelo i dentif icador de processo gerado pelo sistema operacional . 35 Prim eiram ente faz−se uma busca na l ista de máquinas à procu ra do regi stro pertencente ao processo destino. Após achar este registro, procur a−se uma posição liv re no buffer ( campo origem e msg) . E cadastr a−se a men sagem, bufer izando−a em msg, alterando o campo origem para que conten ha o am_pid do processo origem e incrementando a vari ável status. int am_msgsend(int origem,int destino,char *am_msg) { /* Coloca mensagem no buffer retorna 1 se sucesso, ou −1 se erro (buffer cheio, por exemplo */ int conta,conta2; for (conta=0;conta<AM_MAXPROC;conta++) /* procura posicao de buffer do processo destino */ if (am_listproc[conta].nproc==destino) /* procura no buffer espaco disponivel para armazenar a msg */ for (conta2=0;conta2<AM_MSGMAX;conta2++) { if (am_listproc[conta].origem[conta2]==−1) {/* buferiza a mensagem */ am_listproc[conta].origem[conta2]=origem; am_listproc[conta].status=am_listproc[conta].status++; strcpy(am_listproc[conta].msg[conta2],am_msg); return(1); } } return(−1); } Figura 3.15− Função am_msgsend() Função am_strint() Função id êntica à am_ strint () da bibl ioteca am_li b. Ret ira u m número inteir o de uma string. Função **am_server() A função am_ server( ) é a função mais impor tante do sistema. É ela que aten de as chamadas de RPC e executa os serviços requisi tados r etornando um apontador de caracteres. Oferece oit o serviços b ásicos que serão vistos indi vidual mente. E les são sel ecionados através de uma sel eção (do t ipo 36 swit ch−case) na qual é testado o primei ro caracter do parâmetro passado n a variável indat a. char **am_server (char **indata) {/* Programa responsavel pelo atendimento das chamadas de RPC, atraves das strings enviadas pelas chamadas, determina o tipo de servico requizitado e exetua−o */ static char data[AM_TAMMSG]; static char *pd,*rd; int stat,pid,am_pidreq,am_pidmsg; rd=*indata; switch (**indata) { case ... } } Figura 3.16− Função **am_server() Serviço am_addhost Este ser viço só pode ser requi sitado pela apli cação servid ora, e ser ve para testar se o am rud está devi damente in stalado nesta máqui na, e para inf ormá−l o que está partici pando do sistem a, bem como passar−lhe o seu número e o nom e da máquina ser vidora. Se o amru d est iver i nstalado, deve apenas retorn ar a string “1” à aplicação ser vidora. case ’1’: /* Teste, usado pelo addhost */ am_strint(rd); am_minhahost=(char)am_strint(rd); sprintf(am_servername,"%s",rd+1); data[0]=’1’; data[1]=’\0’; break; Figura 3.17− Serviço am_addhost Serviço am_send Este serviço obtém, através de duas cham adas à função am_strint (), o número dos processos envolvi dos no serviço, e os envia à função am_msgsend(). Pr epara a string data com a inf ormação “1” para ret orná−la ao processo ser vidor . 37 case ’2’: /* Send */ data[0]=’1’; data[1]=’\0’; am_strint(rd); am_pidreq=am_strint(rd); am_pidmsg=am_strint(rd); am_msgsend(am_pidreq,am_pidmsg,rd+1); reak; Figura 3.18− Serviço am_send Serviço am_receive Este serviço, da mesma form a que o send, apenas obtém o n úmero dos pro cessos envolvi dos e os passa à f unção am_ msgrec( ). Se est a função achar a mensagem solicitada, a string data já estará format ada para envi o ao pro cesso serv idor, caso cont rário, será preci so format á−la sinalizando o erro, o que é feito pela função sprint f . case ’3’: /* Receive */ am_strint(rd); am_pidreq=am_strint(rd); am_pidmsg=am_strint(rd); stat=am_msgrec(am_pidreq,am_pidmsg,data); if (stat==−1) {data[0]=’0’; data[1]=’\0’; } break; Figura 3.19− Serviço am_receive Serviço am_startproc Este serviço é responsável pela i nicial ização de pr ocessos. P ara isto, faz−se u so de d uas funções d o UNIX. A primeira é a função fork(), q ue cria uma n ova cópia do pro cesso qu e a execu tou. Assim, ao executar−se fork(), tem os duas cópias do processo am rud, uma consid erada pai e a outra fi lho. As duas cópias segui rão sua execução nor malmen te a partir deste ponto, uma independente da out ra (se o pai morrer, o f ilho t ambém morre) . A função fork() retorna o núm ero do pr ocesso filho ao pai e zer o ao fil ho. Com ist o é possível realizar um t este para d etermi nar q uem é o fi lho e 38 quem é o pai. Se pi d for 0, a negação em pid far á com que a sentença seja verd adeira, portanto apenas o filho execut ará o comando após o if e apenas o pai execu tará os com andos do else. O filho executará a cham ada d e sistema execv( ) . A função execv() executa o programa cujo nom e é passado por parâmetro e subst itui a imagem do pr ocesso que o executou pela im agem do pro cesso que deseja−se executar. Assim sendo, o amrud filho não conti nuará a executar depois de chamar a fu nção execv() term inado qu ando o processo criado ter minar. O segundo parâm etro de execv() é os parâmet ros passados à fun ção que se deseja criar, como se t ivessem sido passados na li nha de comando. Apó s ser inicializado o pr ocesso desejado, o mesmo é cadastrado (pelo amru d pai, j á q ue o fil ho anterio rmente cri ado não chegar á a executar esta part e do códi go) na lista de processos do amr ud. Esta l ista, como já f oi dit o ant eriorm ente, guarda apenas dados dos processos filhos de cada amrud. Em seguida, uma string de retorno é formatada para sinal izar ao pr ocesso servidor seu sucesso n a i niciali zação. Est a sinalização é o número iden tificador do processo criado ou ‘−1’ se houve erro. 39 case ’4’: /* Startproc */ pid=fork(); if (!pid) { execv(&rd[2],NULL);exit(0); } sprintf(data,"%d",pid); for (stat=0;stat<AM_MAXPROC;stat++) if (am_listproc[stat].espera==−3) { am_listproc[stat].espera=−2; am_listproc[stat].nproc=pid; for (pid=0;pid<AM_MSGMAX;pid++) am_listproc[stat].origem[pid]=−1; stat=AM_MAXPROC+1; } if (stat==AM_MAXPROC) {sprintf(data,"%d",−1); break; } break; Figura 3.20− Serviço am_startproc Serviço am_myhost Este ser viço é bastant e simp les e út il, ele apenas reto rna o número da máquina local . case ’5’: /* Myhost */ sprintf(data,"1#%d",am_minhahost); break; Figura 3.21− Serviço am_myhost Serviço am_hostname Este ser viço fornece, à quem o requi sitou, o nom e da máqui na cuj o número é p assado por par âmetro . E ntretanto, apenas o am rud servid or m antém o cadast ro das m áquinas participantes do sistem a, e se o amrud em q uestão não for o servidor, el e não será capaz de determ inar o nome da máqu ina q ue se pede. Deve−se, por tando, fazer um teste para saber se este amrud é o ser vidor , ist o é feit o pela var iável am_minhahost que será zer o se este for o servidor. Caso sej a, fornece−se o nome da máquina requisitada ou “0” se esta não existir . 40 Por ém, se este não for o amrud servi dor, ele não ter á condições de efetuar a tar efa p edida, mas também não pode sinalizar que não existe tal máq uina. Neste caso, então, o amrud sinalizar á com o caracter ‘2’ seguido d o nome da máquina que contém o am rud ser vidor , d evendo o processo que requisitou este serviço voltar a fazê−lo, só que dest a vez ao amr ud ser vidor . case ’6’: /* am_hostname */ am_strint(rd); pid=am_strint(rd); sprintf(data,"0"); if (am_minhahost==0) {for (stat=0;stat<AM_MAXPROC;stat++) if (am_listmaq[stat].numero==pid) sprintf(data,"1#%s",am_listmaq[stat].nome); } else sprintf(data,"2#%s",am_servername); break; Figura 3.22− Serviço am_hostname Serviço cadastra Este ser viço é requisi tado apenas pelo pr ocesso servidor toda vez que uma função am_addhost() é execut ada. T em como obj etivo infor mar ao amrud serv idor que uma nova máqui na foi inserida, bem como o número e o no me desta. Se este cadastramento não fosse feito, o amrud ser vidor não teria condições de resp onder aos servi ços de am_hostname porque o registro das máq uinas existentes est aria apenas no pr ocesso ser vidor, inatingí vel por chamadas de RPC. case ’7’: /* cadastra nova host */ am_strint(rd); pid=am_strint(rd); for (stat=0;stat<AM_MAXPROC;stat++) if (am_listmaq[stat].numero==−1) { am_listmaq[stat].numero=pid; sprintf(am_listmaq[stat].nome,"%s",rd+1); stat=AM_MAXPROC; sprintf(data,"%s","1"); } break; Figura 3.23− Serviço cadastra 41 Serviço registra Este serviço é requisi tado apenas pel o processo servi dor uma única vez, dur ante o am_ init() . Tem como o bjetivo in formar ao amrud servi dor o identificador do processo servido r, já que o amrud não o conhece por não trat ar−se de seu fi lho. Se este cadastr amento não fosse feito, o processo serv idor não poderia receber mensagens, poi s o amrud servidor não teri a n em ao menos espaço n o buf fer r eservado para el e e respo nderia com erro à qualquer pedi do de envio de mensagem destinado ao processo servidor . case ’8’: /* registra o processo servidor*/ am_strint(rd); pid=am_strint(rd); am_listproc[0].espera=−2; am_listproc[0].nproc=pid; for (pid=0;pid<AM_MSGMAX;pid++) am_listproc[0].origem[pid]=−1; break; Figura 3.24− Serviço registra Apó s este servi ço, termi na o comand o switch−case. Por tanto , a st ring gerada em algum ponto ant erior da execu ção cont ém os valores que devem ser reto rnados ao pr ocesso que execut ou a chamada. Reto rna−se, por fim, esta string. Função main() A fu nção pri ncipal do amru d faz a iniciali zação das variáveis usadas pel o amr ud, o regist ro da função am_ server( ) no RPC e a instalação do RP C. Seu có digo p ode ser visto na figura 3.25. A função reg isterrpc() [ SCH 96] recebe seis parâmetro s, sendo que três del es foram man tidos em default por não serem rel evantes neste trab alho (RUSERSPROG, RUSERSVERS , RUSERSP ROC_NUM). O quarto é o 42 endereço da f unção que at enderá as ch amadas de RP C, que no caso é a função am_server( ). O quint o e o sexto parâmet ros indicam os tipo s de dados que o processo receber á e qu e envi ará, que no caso é wrapstring para os dois. Em seguida é chamada a função svc_ run() do pacote RPC. Esta f unção inst ala o processo am_ server( ) como responsável pelo atendimento das chamad as e não ret orna mais. Se est a função retornar houve algum erro. main() {int aux=0; for (aux=0;aux<AM_MAXPROC;aux++) /* inicializa campos da lista de am_listproc */ {am_listproc[aux].espera=−3; am_listproc[aux].status=0; am_listmaq[aux].numero=−1; } registerrpc(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, am_server, xdr_wrapstring, xdr_wrapstring); svc_run(); fprintf(stderr,"Erro: svc_run retornou!\n"); exit(1); } Figura 3.25− Função main() 4EXEMPLOS DE UTILIZAÇÃO São v ários os exempl os que podem ser u tilizados neste sist ema. Muit os dos i mplementados em PVM pod em, com facilidade, ser mi grados para est e amb iente. Não todos, por que cer tamente o PVM é bem mai s completo e, em alguns pontos, m ais efi ciente que este amb iente. Substi tuí−lo nunca foi a pret ensão deste t rabalho. Neste capí tulo ser ão ap resent ados dois exemplos: desempenh o e o clássico filósof os jantadores. 4.1Desempenho de troca de mensagens Este exemplo consiste de dois processo s cu ja f inalid ade é t estar o temp o necessári o ao en vio e recebimento de m ensagens no sistema. Chamaremos o primei ro processo de “desempenho” e o segu ndo de “responde”. A função do processo desem penho é criar no ambiente o processo resp onde, envi ar um a m ensagem qualquer a este e receber uma outra de volta, contabili zando o tempo levado. Isto será f eito n vezes, e, ao final das iterações, ser á calculado o tem po médio, mínimo e m áximo do envio das mensagens. Já o processo “r esponde” terá a ú nica função de resp onder à todas as men sagens recebidas do d esempenho. O código do responde po de ser vist o n a figur a 4.1. Not e que não existe com plexidade algum a neste processo, ele apenas recebe um a mensagem qualquer e a envia de vol ta ao mesmo processo que a enviou, faz isso até qu e receba a m ensagem ‘0’. 44 /* Este processo recebe uma mensagem e a envia de volta */ #include "am_lib.c" main() { long int tid; char msg[100]; tid=am_receive(−1,msg); for(;msg[1]!=’0’;) { fprintf(stderr,"Recebeu msg %s do processo %d\n",msg,tid); am_send(tid,msg); tid=am_receive(−1,msg); } } Figura 4.1− Exemplo responde O processo desempenho é u m pouco mais compl exo e seu código pode ser vi sto na figur a 4.2 . Primeir amente inici aliza−se o ambient e através de função am_init( ), que retornará o númer o da máquina servi dora ou −1 se houve erro (o amrud deve ser instal ado pr eviamente pel o usuár io). I nicializa−se então o processo “responde” na máquin a ser vidor a, cujo núm ero será g uardado na vari ável p1. Dent ro de um laço de NMAX posições (defin ido com constante), faz−se vári os envios e recebimen tos, sempre guar dando o tem po lev ado ( deve−se ter cui dado ao defi nir a constante NMAX, pois um número excessivo de i terações pode causar sobrecarga n a vari ável t otal) . Par a obter o t empo, faz−se uso da função get timeof day(), declarada na bibl ioteca time.h. E la alter a duas estruturas t ambém bibl ioteca, um a é u sada p ara armazenar o tempo atual defini das nesta (struct timeval) e a outr a não será util izada pela aplicação ( struct timezone). Na estrutu ra tim eval, exi stem do is registros: tv_sec e tv_usec, que armazenam, resp ectiv amente o tempo em segundos e microssegundos. F oram usadas duas vari áveis do tipo desta estrut ura para guardar o t empo. Com a prim eira, tpi, guarda−se o t empo i nicial e com a segunda, tpf, o final . 45 Apó s o término da iteração, dimi nuí−se o valor fi nal em segun dos (tpf.u_sec) do val or ini cial em segu ndos ( tpi.tv _sec) armazenado este valor no pró prio campo tvf.tv _sec. Segue−se o mesmo procedimento com os microsseg undos, armazenado o r esultado em tpf .tv_usec. Convert e−se, em seguida, o valor em segundos para micr ossegu ndos multiplicand o−o por 1.00 0.000 e o somando este valor ao val or em microssegundos tem−se o val or total d a it eração , tempo de ida e vol ta da mensagem, por isso é necessári o dividir este valor por dois par a obter o tempo da transmissão. Acum ula−se o valor obtido na variável total , e verifica−se este t empo obti do para sab er se corresponde ao tempo mínimo ou máximo até então. Proced e−se então à uma n ova it eração. 46 /* Processo desempenho */ #include "am_lib.c" #include <sys/time.h> #define NMAX 10 main() {char msg[100]; unsigned char n; struct timeval tpi, tpf; struct timezone tzpi, tzpf; unsigned long tempo, tmin=0,tmax=0, total; int m1,m2,p1,p2,tid; m1=am_init(); if (m1==−1) exit(0); m1=am_addhost("verdi"); if (m1==−1) exit(0); m2=am_addhost("vivaldi"); if (m2==−1) exit(0); p1=am_startproc(m2,"responde"); if (p1==−1) exit(0); for (n=0;n<NMAX;n++) {sprintf(msg,"%s","Ola"); gettimeofday(&tpi, &tzpi); am_send(p1,msg); am_receive(p1,msg); gettimeofday(&tpf, &tzpf); tpf.tv_sec= tpf.tv_sec−tpi.tv_sec; tpf.tv_usec= tpf.tv_usec−tpi.tv_usec; tempo=((tpf.tv_sec*1000000)+tpf.tv_usec)/2; fprintf(stderr,"Tempo da iteracao %2d foi %u\n",n,tempo); total=total+tempo; if (tmax==0) tmax=tmin=tempo; else {if (tempo<tmin) tmin=tempo; if (tempo>tmax) tmax=tempo; } } am_send(p1,”000”); total=total−tmin−tmax; n=NMAX−2; total=total/n; fprintf(stderr,"Tempo medio foi de %u\n",total); fprintf(stderr,"Tempo minimo foi de %u\n",tmin); fprintf(stderr,"Tempo maximo foi de %u\n",tmax); } Figura 4.2− Exemplo desempenho No final das it erações, retira−se o tem po máximo e o tempo m ínimo da vari ável tot al (isso po rque os extremos p odem infl uenci ar negat ivamen te a média) e divi de−se o t otal por NMAX−2 (já que f oram retir ados o resultad o de duas iter ações) . 47 Imprim e−se então o resultado médio, míni mo e m áximo . 4.2Filósofos Jantadores Cinco fil ósofos estão reunidos em um a mesa par a jant ar, dispondo de cin co garfos. Cada um p recisa de do is garfos par a jantar. Co mo não há garfos suf icient es (seriam n ecessários 10 gar fos), eles divid em os garfos ent re si, na man eira mostrada na figu ra 4.3. Desta f orma, o Filóso fo f1 usará os garfos g 1 e g 5, o f ilósof o g2 os gar fos g1 e g2 e assim sucessivamente. Os f ilósof os or a jan tam, ora pensam e cada filósofo, assim que conseguir pegar os dois garfos, janta e os devolve à mesa. Figura 4.3− Ilustração Filósofos Jantadores Reso lver este prob lema po r paralel ismo, signifi ca criar cinco processos garf os e cinco processos fi lósofos. Quand o um garf o estiver sendo usado por um fi lósofo, não poderá ser usado por nenhum outro até qu e este o li bere, e ist o só ocor rerá quando o fil ósofo estiv er satisf eito. Impl ementar este alg oritmo requer uma série de cuidados. Um deles é refer ente à possibilidad e 48 do si stema todo entrar em deadlock. Imagine que cada filósofo, assim que for inicializado, tentará pegar o garfo da sua d ireita, e que todos consigam. Para que p ossam jan tar, devem também possu ir o garfo à sua esquer da, só que o garf o à su a esqu erda é também o garfo à direi ta do f ilóso fo anterior e, por tanto, já está reservado à este, o que fará que o f ilósof o que r equisit ou este garfo f ique aguardando. Contudo, per cebe−se que todos os fil ósofos estão agu ardando a li beração do garfo à sua esq uerda, o qu e r esulta que nenhum fil ósofo jamais conseguirá pegá−lo, caracter izando deadlock. Para resol ver est e pr oblema, é tomada uma precaução simples: o úl timo filóso fo tentará peg ar o g arfo à sua esquerda antes do da direita. Assim, o primeiro f ilósof o será o pr imeiro a jantar, por que somente ele conseguirá pegar o garfo da sua direita e o da sua esquerda. O processo que instal a os fi lósofo s e os garf os cujo código pode ser visto n a figura 4.4 será analisado a seguir . 49 #include "am_lib.c" main() {long int m1,m2,tidg[5],tidf[5],cc,aux,f; char msg[40]; m1=am_init(); if (m1==−1) exit(0); m1=am_addhost("verdi"); if (m1==−1) exit(0); m2=am_addhost("vivaldi"); if (m2==−1) exit(0); for (aux=0;aux<5;aux++) {tidg[aux]=am_startproc(m1,"garfo"); if (tidg[aux]==−1) exit(0); } for (aux=0;aux<5;aux++) {tidf[aux]=am_startproc(m2,"filosofo"); if (tidf[aux]==−1) exit(0); } fprintf(stderr,"Ids dos filosofos: "); for (aux=0;aux<5;aux++) fprintf(stderr,"%d ",tidf[aux]); fprintf(stderr,"\nIds dos garfos: "); for (aux=0;aux<5;aux++) fprintf(stderr,"%d ",tidg[aux]); fprintf(stderr,"\n"); for (f=0;f<5;f++) {sprintf(msg,"0%d",f); am_send(tidf[f],msg); if (f==4) sprintf(msg,"%d",tidg[0]); else sprintf(msg,"%d",tidg[f]); am_send(tidf[f],msg); if (f==4) sprintf(msg,"%d",tidg[f]); else sprintf(msg,"%d",tidg[f+1]); am_send(tidf[f],msg); } for (f=1;f<=5;f++) {cc=am_receive(−1,msg); fprintf(stderr,"\n**** Filosofo %d esta satisfeito\n",cc); } } Figura 4.4− Processo jantar Ini ciciali za−se o sistema pelo coman do am_init(), at ribuindo−se o valor da máquina servi dora à v ariável m 1. Verifica−se se houve er ro, saindo do pro grama em caso af irmati vo (a mensagem de erro já é gerada pelo am_ addhost()). Insere−se a máqui na verdi e a vivaldi (uma d elas já havia sido 50 inserida p elo comando am_init()) at ribuindo seus valores à vari ável m 1 e m2, respectivamente. Cria−se com a função am_ startproc() cinco processos garf o na máqu ina m1 (verdi) e armazena seus am _pids no vetor tidg, fazendo−se o mesmo com os processos fil ósofos, porém criando−os na máquina m2 (vi valdi) e armazenando seus am_p ids no veto r tidf. Im prime−se na tela o valor am_pid de tod os os processos cr iados, para verif icação do usuário. Apó s ser em criados os processos, envia−se a cada filósofo o núm ero que cada um recebeu (segundo figura 4.3, nú meros de 0−4) e o númer o identificador de seus doi s garfos, para q ue ele possa enviar−lhes mensagens. Neste ponto é necessário t omar as medi das para ev itar deadlock. Par a t odo tidf [aux] envia−se os am_pids dos garf os tidg[aux] e tid g[aux+1], exceto para o f ilósofo tidf [4] (o últim o), onde esta ordem é invertida, poi s dever ia−se enviar tidg[ 4] e t idg[0] , mas envia−se pr imeiro o tidg[0] e depoi s o ti dg[4] . Apó s enviar os am_p ids dos garfos, este pr ocesso fica esperando o retorno dos filósofos di zendo que estão satisfeitos, e ter mina sua execução. O processo fi losof o tem como objet ivo requi sitar dois gar fos à mesa, usá−los por um det erminado tempo ( defini da pela constan te T EMPO) e dev olvê−l os, v oltand o a repeti r est e pr ocesso por n v ezes (defin ido na constante N) . 51 #include "am_lib.c" #define #define N 2 TEMPO 31000 /* Numero de vezes que jantaram*/ /* Tempo que ficaram com os garfos*/ main() {int tidp,tidg1,tidg2,cc,data=0,conta1,conta2,nf; char msg[40]; tidp=am_receive(−1,msg); nf=str_int(msg); tidp=am_receive(−1,msg); tidg1=str_int(msg); tidp=am_receive(−1,msg); tidg2=str_int(msg); sprintf(msg,"%s","1"); am_send(tidp, msg); for (conta1=1;conta1<=N;conta1++) {sprintf(msg,"%s","1"); am_send(tidg1,msg); cc=am_receive(tidg1,msg); if (*msg==’0’) conta1−−; else {fprintf(stderr,"Filosofo %d tem garfo 1 (%d)\n",nf,tidg1); sprintf(msg,"%s","1"); am_send(tidg2,msg); cc=am_receive(tidg2,msg); if (*msg==’0’) {sprintf(msg,"%s","0"); am_send(tidg1,msg); fprintf(stderr,"Filosofo %d liberou garfo 1 (%d)\n" ,nf,tidg1); conta1−−; } /* Neste ponto o filosofo ja tem os dois garfos a sua disposicao */ else {fprintf(stderr,"Filosofo %d tem garfo 2 (%d)\n",nf,tidg2); for (conta2=0;conta2<TEMPO;conta2++); sprintf(msg,"%s","1"); am_send(tidg1,msg); am_send(tidg2,msg); fprintf(stderr,"Filosofo %d ja usou os garfos\n",nf); } } } sprintf(msg,"%s","1"); am_send(tidp, msg); } Figura 4.5− Exemplo filosofo.c Crio u−se uma pequena função n este processo d enomin ada str_int() qu e apenas converte uma str ing passada por par âmetro para um intei ro, de form a idêntica à fu nção am_strint () estudad a anterior mente. Esta fun ção se faz necessári a já que nosso sistema perm ite troca apenas de strings, devendo o usuário preocupar−se com p ossíveis empacotam entos e desem pacotamentos. 52 O código completo d o processo f ilosof o pode ser visto na figura 4.5 . Em primeiro l ugar, recebe−se uma mensagem de um processo q ualquer (so mente o pai i rá enviá−la). Define−se o p rocesso que a enviou como tidp . Esta mensagem r ecebi da corresponde ao am_ pid do primeiro garf o, sendo por tanto convert ida para intei ro e atr ibuída à variável tid g1, o mesmo acontece com a segu nda mensagem r ecebid a pelo processo pai, só que seu val or será at ribuído à variável tidg2. De posse dos am_pids de seu s dois garf os, o processo fil osofo responde com a string “1” ao seu pai , i nform ando− o que está pr onto para trabalhar . O fil ósofo pode, ent ão, com eçar a requisit ar seus garfos. Dentro de um laço de N vezes ( número d e vezes que i rá jant ar), ele envia uma m ensagem com o text o “1” ao pr imeiro garfo, na tentat iva de usá−lo , este, por sua vez irá retornar u ma mensagem de co nfirmação. A mensagem recebida por am_ receive() do garfo pode ser “1”, se o g arfo agor a per tence a este filósofo ou ‘0’ se o garfo não est á dispon ível para este processo. Se esta últim a sit uação o correr , o pr ocesso filoso fo decr ementa a vari ável contado ra dos laço s, p ara desconsider ar esta última iteração, e repete o processo. Mas, se o garf o1 respo ndeu “1”, ou seja, el e está, agor a, disponí vel para este fil ósofo, tenta−se ent ão peg ar o garfo2. Par a ob ter o garf o2, o procedimento é idêntico ao utilizado para obter o garf o1, com a difer ença que se desta vez el e não conseguir pegar este garfo , ele envi a um a mensagem “0” ao garfo1, dispensando−o − para evitar deadl ock − e repete t odo o laço novamente desconsiderando a últ ima it eração. Caso tenha, finalm ente, disponib ilizado os seus dois garfos, f az−se um laço de TEMPO vezes par a si mular o temp o gasto na j anta e depois de 53 term inado este laço, envia−se uma mensagem “1” p ara os doi s garfos, inform ando−o s que não m ais pr ecisa deles. Depo is que o filósofo já j antou to das as vezes def inidas na constante N, este en via um a mensagem ao seu p ai, in formand o estar sati sfeito . #include "am_lib.c" main() {long int tidf,tida,chega; char msg[20]; for(;;) {tidf=am_receive(−1,msg); if (*msg==’1’) {am_send(tidf, msg); chega=0; for (;chega!=1;) {tida=am_receive(−1,msg); if (tida!=tidf) am_send(tida,"0"); else chega=1; } } } } Figura 4.6− Processo garfo O últim o pr ocesso envolvido na sol ução deste problema é o p rocesso garf o. A função dele é aceitar o pedido de requisição do p rimei ro filósofo, e rejeitar qualquer outro pedido que venha até que o prim eiro o di spense. O código compl eto deste pr ocesso pode ser vi sto na figur a 4.6. O processo inicia aceitando m ensagem de qualquer out ro processo, sab e−se que, pel a maneira co mo este probl ema foi impl ementado, o único que env iará esta mensagem é o filósofo que o usará. Armazena−se o am_pid do pro cesso q ue enviou esta mensagem na variável tidf e verifi ca−se se a men sagem tem em sua p rimeir a posição o caracter ‘1’, indicando um fi lósofo querendo usar este garfo. O processo gar fo responde, então com a st ring “1”, env iando a mesma msg recebida, para indicar ao filósofo que agora pertence a ele. 54 Processo garfo entra agora em um laço até que a vari ável achei seja dif erente de 1. Dent ro deste laço, ele recebe m ensagem de qualquer pro cesso, com para o am_pid dest e com o am_pi d d o seu fil ósofo. Se for em i guais, é sinal que trata−se do fi lósof o, à qual pert ence atualmente, querendo infor mar que n ão mais o está usando. Deve−se, portanto, setar a variável achei para 1, indi cando q ue o processo filosofo dispensou este garf o, podendo o mesmo ser requ isitado por outro filosof o. Mas se não for o mesm o filosofo, garfo en tão envia a str ing “0” para indicar ao processo que tenta requi sitá−l o que este garfo já per tence à um o utro processo e não pode, no m omento, atendê−lo . 5CONCLUSÃO A gama de uso dest e ambient e não é, obviamen te, tão ampla quanto a do PVM, mas muitos exemplos cri ados em PVM podem, com extr ema faci lidade, ser mi grados para esta in terface. No entanto, por ser mais sim ples, este sistema é bem mais fácil de usar que o PVM, p ois não necessita d e algu ns com andos bur ocráticos usados no PVM ( pvm_initsend(), pvm_pkint (), pvm_upkint (), por exempl o). Seu estudo é extrem ament e simpl es, e facilment e alunos poderão im plemen tar p rocessos em ambiente mul tiprogramado sem a necessi dade de se f amiliarizar com a extensa gama de com andos do PVM. Uma das m aiores v antagens do AMRU est á, no entanto, no fat o de que ele usa pr imiti vas básicas de comun icações do UNIX e é relat ivamen te pequeno. O que facilita não só a sua utilização, mas também o entendim ento da sua estru tura i nterna. Ist o o t orna, sem dúvida, uma ferr amenta ex tremam ente relevante d o ponto de vista did ático. ANEXOS ANEXO 1 AM_LIB.C 57 /*********************************************************************/ /* AM_LIB.C */ /* AMBIENTE DE MULTIPROCESSAMENTO PARA UMA REDE DE MÁQUINAS UNIX */ /* Elgio Schlemer 11/12/96 */ /* */ /* Biblioteca Principal */ /*********************************************************************/ #include #include #include #include #include <stdio.h> <utmp.h> <rpc/rpc.h> <rpcsvc/rusers.h> <string.h> #define AM_MAXMAQ #define AM_MAXPROC 4 /* Numero maximo de hosts comportadas */ 20 /* Numero maximo de processos comportados */ /*************************************************************/ /* Definicao de estruturas */ struct AM_LISTHOST{ char nome[20]; int numero; char status; }; /* Lista das hosts participantes do sistema nome= nome da host (ex. verdi) numero= numero pelo qual sera reconhecida no sistema status= 0 se campo disponivel, 1 se ocupado por host ativa */ struct AM_LISTPROC{ long int nproc; int nmaq; char status; char nome[20]; }; /* Lista das processos inicializados por startproc nproc= numero do processo dado pelo sistema (uma composicao do numero da host e o id do processo nmaq= numero da host onde encontra−se este processo nome= nome do processo. */ /*************************************************************/ int am_nummaq=0,am_numproc=0; /* am_nummaq=contador de host existentes am_numproc=contador de processos criados*/ struct AM_LISTHOST am_listmaq[AM_MAXMAQ]; struct AM_LISTPROC am_listproc[AM_MAXPROC]; char am_servidor=1; /* Prototypes das funcoes */ int am_init(); int am_hostname(int, char *); int am_myhost(); long int am_mytid(); long int am_converte (int ,long int); int am_addhost(char *); long int am_startproc(int, char *); int am_send(long int,char *); int am_receive(int,char *); long int am_strint(char *); /****************************************************************/ long int am_strint (char *am_data) {char *am_aux; long int am_total=0; int am_sinal=1; for (am_aux=am_data;(*am_aux<’0’)||(*am_aux>’9’);am_aux++) {if (am_aux==NULL) return(−1); if (*am_aux==’−’) am_sinal=−1; } for (;(*am_aux>=’0’)&&(*am_aux<=’9’);am_aux++) am_total=(am_total*10) + (*am_aux−’0’); sprintf(am_data,"%s",am_aux); return(am_total*am_sinal); } /*************************************************************/ int am_init() { char nome[50],envia[100],recebe[25]; int conta; char *env,*precebe; int stat; 58 for (conta=0;conta<AM_MAXMAQ;conta++) am_listmaq[conta].status=0; for (conta=0;conta<AM_MAXPROC;conta++) am_listproc[conta].status=0; gethostname(nome,64); am_servidor=0; conta=am_addhost(nome); sprintf(envia,"8#%d#",getpid()); env=envia; precebe=recebe; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); am_listproc[am_numproc].status=1; am_listproc[am_numproc].nmaq=am_myhost(); am_listproc[am_numproc].nproc=am_mytid(); return(conta); } /*************************************************************/ int am_hostname(int maq,char *nome) /* Esta funcao retorna o nome da host cujo numero e maq */ { char nome2[20],*env,envia[20],recebe[20],*precebe,stat; /* nome=usada para guardar o nome da host, conta=contador for*/ gethostname(nome,64); /* obtem o nome da host no laco a seguir, procurara pela ocorrencia do nome da host na lista de hosts do sistema retornando o numero desta*/ precebe=recebe; sprintf(envia,"6#%d#",maq); env=envia; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if (recebe[0]==’0’) return(−1); if (recebe[0]==’1’) { sprintf(nome,"%s",recebe+2); return(1); } if (recebe[0]==’2’) { sprintf(envia,"6#%d#",maq); env=envia; stat = callrpc ( recebe+2, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if (recebe[0]==’1’) { sprintf(nome,"%s",recebe+2); return(1); } } return(−1); } /*************************************************************/ int am_myhost() /* Esta funcao retorna o numero da host que o requisitou ou −1 caso haja algum erro (esta maquina nao tem processo daemon, por exemplo */ { char nome[20],*env="5",recebe[50],*precebe,stat; /* nome=usada para guardar o nome da host, conta=contador for*/ int maq=0; gethostname(nome,64); /* obtem o nome da host no laco a seguir, procurara pela ocorrencia do nome da host na lista de hosts do sistema retornando o numero desta*/ precebe=recebe; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if (recebe[0]==’1’) {am_strint(recebe); maq=am_strint(recebe); return(maq); } return(−1); } /*************************************************************/ long int am_mytid() /* Obtem o numero pelo qual o processo e conhecido no sistema Este numero e uma comnposicao binaria descrita em am_startproc */ {long int maq; pid_t pid; maq=am_myhost(); /*otem o numero da host */ maq=maq<<12; /*compoe o numero da maquina */ pid=getpid(); /*obtem o numero do processo (no SO)*/ maq=maq+pid; return(maq); } 59/*************************************************************/ long int am_converte (int maq,long int tid) {long int am_aux; am_aux=maq<<12; return(am_aux+tid); } /*************************************************************/ int am_addhost(char *maquina) { /* Este programa faz uma chamada RPC para o programa AMRUD em execucao na maquina remota. Se este programa estiver ativado ele respondera conforme solicitado. Retorna o numero da host ou −1 se houve erro */ int stat,conta; char recebe[2],buf[30],nome[20]; char *envia,*precebe; if (am_servidor) return(−1); /* Verifica se a maquina ja nao esta inserida no sistema */ for(conta=0;conta<am_nummaq;conta++) if (strcmp(maquina,am_listmaq[conta].nome)==0) {am_listmaq[conta].status=1; return(am_listmaq[conta].numero); } precebe=recebe; envia=buf; gethostname(nome,64); sprintf(buf,"1#%d#%s",am_nummaq,nome); /* envia o nome e o numero de sua host para a host inserida saber quem a criou */ stat = callrpc ( maquina, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &envia, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n",maquina); return(−1);} else if (strcmp("1",recebe)==0) /* procura por uma posicao disponivel na lista de hosts, a posicao e considerada disponivel se status for 0 */ for (conta=0;conta<AM_MAXMAQ;conta++) if (!am_listmaq[conta].status) {strcpy(am_listmaq[am_nummaq].nome,maquina); /* cadastra a nova host inserida na lista de hosts */ am_listmaq[conta].numero=am_nummaq; am_listmaq[conta].status=1; sprintf(buf,"7#%d#%s",am_nummaq,maquina); gethostname(nome,64); envia=buf; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &envia, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n",nome); return(−1);} return(am_nummaq++); } return(−1); } /*************************************************************/ long int am_startproc(int maq,char *nomep) { /* Inicia o processo de nome nomep na maquina maq, retorna o numero do processo criado ou −1 se houve erro. O numero do processo e uma composicao feita a partir do numero da host com o ip do processo da seguinte forma: faz−se um shift left do numero da host em 12 posicoes como exemplo, se for maquina 1, expresso como 0000 0000 0000 0001b, ao fazer esse shift, fica 0001 0000 0000 0000b, ou seja, 4096 em deci− mal, soma−se entao a este numero o id do processo, por exemplo, processo 240, expresso como 0000 0000 1111 0000b, resoltando em 4336 (0001 0000 1111 0000b). Assim sendo, como o long int e expresso em 4 bytes, usamos o primeiro byte para numero da host (total de 256) e os outros tres para processos (4096). Verificou−se que os ips criados pelo Linux nao ultrapassam este limite. */ int stat,numero=−1,conta; char envia[20],recebe[5],*precebe,*env; long int proc; 60 if (am_servidor) return(−1); /* procura pela host na list de hosts */ for(conta=0;conta<AM_MAXMAQ;conta++) if ((maq==am_listmaq[conta].numero)&&(am_listmaq[conta].status)) numero=conta; if (numero==−1) {fprintf(stderr,"numero de maquina nao existe\n"); return(−1); } sprintf(envia,"4#%s",nomep); /* Gera string a ser enviada ao Deamon na host, 4 indica servico de am_startproc seguido do nome do processo que deve sr criado */ precebe=recebe; env=envia; stat = callrpc ( am_listmaq[numero].nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n", am_listmaq[numero].nome); return(−1);} if (strcmp(recebe,"−1")==0) {printf("Nao foi possivel iniciar processo %s\n",nomep); return(−1); } proc=am_strint(recebe); proc=am_converte(maq,proc); /* em recebe existe o numero id (do SO) do processo criado, obtem−se agora o numero do processo no sistema */ for (conta=0;conta<AM_MAXPROC;conta++) /* Cadastra o processo criado na lista de processos */ if (!am_listproc[conta].status) {am_listproc[conta].status=1; strcpy(am_listproc[conta].nome,nomep); am_listproc[conta].nproc=proc; am_listproc[conta].nmaq=maq; return(proc); } return(−1); } /*************************************************************/ int am_send(long int proc,char *msg) { /* Envia uma mensagem para o processo especificado */ int stat,conta,numero=−1; char envia[100],nome[30]; char recebe[20],*precebe,*env; numero=proc>>12; stat=am_hostname(numero,nome); if (stat==−1) return(−1); proc=proc&4095; /* Obtem o real numero do processo, seu id, o numero pelo qual ele e conhecido pelo SO de sua maquina */ sprintf(envia,"2#%d#%d#%s",am_mytid(),proc,msg); /* Gera a string a ser enviada ao Daemon da maquina que possue este processo. O numero 2 indica que trata−se do servico 2. Segue−se o numero do processo que executou o servico e o numero do processo para quem deseja−se enviar a mensagem. */ precebe=recebe; env=envia; stat = callrpc ( nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao na maquina %s.\n", nome); return(−1);} if (strcmp(recebe,"−1")==0) {printf("Nao foi possivel achar processo %s\n",proc); return(−1); } return(1); } /*************************************************************/ int am_receive(int proc,char *msg) { /* Recebe uma mensagem do processo especificado, Se proc for −1, indica que a mensagem pode ser de qualquer processo */ int stat,conta,numero=−1; char envia[20],recebe[100],nome[20],*precebe,*env; sprintf(envia,"3#%d#%d#",getpid(),proc); /* Note que aqui, ao inves de enviar o numero do processo no sistema, envia−se o seu id apenas. Isso porque quem fara o tratamento de mensagens e o Daemon desta maquina (esta propria que executou o am_receive), e este daemon conhece seus "filhos" pelo seu id. */ precebe=recebe; env=envia; gethostname(nome,64); /* O buffer de msgs e controlado pelo Daemon de cada host, cada Daemon controla as msgs de seus filhos. Por isso esta chamada de RPC e para a propria host que executou esta funcao. */ for (;recebe[0]!=’1’;) { stat = callrpc (nome, RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_wrapstring, &env, xdr_wrapstring, &precebe ); if ( stat != 0 ) {fprintf(stderr,"ERRO: ’amrud’ nao esta execucao nesta maquina \n"); return(−1); } } am_strint(recebe); numero=am_strint(recebe); sprintf(msg,"%s",recebe+1); return(numero); } /*************************************************************/ 58 ANEXO 2 AMRUD.C 59 60 /*********************************************************************/ /* AMRUD.C */ /* AMBIENTE DE MULTIPROCESSAMENTO PARA UMA REDE DE MÁQUINAS UNIX */ /* Elgio Schlemer 11/12/96 */ /* */ /* Programa Daemon */ /*********************************************************************/ #include <stdio.h> #include <rpc/rpc.h> #include <rpcsvc/rusers.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define AM_TAMMSG 50 #define AM_MSGMAX 10 #define AM_MAXPROC 20 /* tamanho das strings de msgs */ /* num max de msgs no buffer para cada proc */ /* Numero maximo de processos neste host */ struct AM_LISTHOST{ char nome[20]; int numero; }am_listmaq[AM_MAXPROC]; /* Lista das hosts participantes do sistema nome= nome da host (ex. verdi), −1 registro disponivel numero= numero pelo qual sera reconhecida no sistema */ struct AM_LISTPROC { long int nproc; char status; char msg[AM_MSGMAX][AM_TAMMSG]; int origem[AM_MSGMAX]; int espera; }am_listproc[AM_MAXPROC]; /* Lista de processos. Difere−se um pouco da lista existente na biblioteca, pelo fato de aqui nao existir o nome do processo, nproc e o id dele do SO e ter campos para buferizar as msg. nproc=numero do processo msg= buffers de mensagem pendentes, cada processo pode ter ate AM_MSGMAX mensagens esperando, cada uma de ate AM_TAMMSG caracteres. status= 0= sem msg para ele, >0=numero de msg recebidas espera= −3 campo disponivel, −2 ocupado−processo nao espera msg, −1 ocupado, processo espera msg de qualquer pro− cesso, >=0 = numero do processo de quem esta esperando uma mensagem. origem=processos que enviaram mensagem, −1=disponivel. */ char am_minhahost=0; /* se =0, este e o servidor, >0 designa o numero desta host */ char am_servername[30]; /* nome da host servidora */ /* PROTOTYPES DAS FUNCOES */ int am_msgrec(int, int, char *); int am_msgsend(int, int, char *); long int am_strint (char *); char **am_server (char **); /****************************************************************/ int am_msgrec(int am_pidreq,int am_pidmsg,char *am_msg) {/* Servico de recebimento de mensagens, se existir mensagem de am_pidmsg para am_pidreq, esta sera armazenada em am_msg e a funcao retorna 1, senao a funcao retorna −1 (o servico de send e receive e sincrono, mas a de− cisao de por o processo para dormir e do am_receive e nao do Daemon este ultimo apenas informa ao am_receive pelo retorno do RPC que nao existe a am_msg requisitada). Se am_pidmsg for −1, indica receber msg de qualquer processo. */ int conta,conta2; /* procura a posicao do buffer pertencente ao processo que requisitou o pedido de receive */ for (conta=0;conta<AM_MAXPROC;conta++) if (am_listproc[conta].nproc==am_pidreq) { if (am_listproc[conta].status==0) /* se status e zero, nao ha msg para este processo */ return(−1); if (am_pidmsg==−1) /* −1 indica receber de qualquer processo */ for (conta2=0;conta2<AM_MSGMAX;conta2++) if (am_listproc[conta].origem[conta2]!=−1) {sprintf(am_msg,"1#%d#%s",am_listproc[conta].origem[conta2],am_listproc[conta].ms g[conta2]); /* monta a string de retorno, 1 indica sucesso, segue o numero do processo qeu enviou a msg (util caso am_pidmsg fosse −1) e em seguida a mensagem */ am_listproc[conta].origem[conta2]=−1; /*disponibiliza a posicao do buffer */ am_listproc[conta].status−−; return(1); } for (conta2=0;conta2<AM_MSGMAX;conta2++) if (am_listproc[conta].origem[conta2]==am_pidmsg) { sprintf(am_msg,"1#%d#%s",am_pidmsg,am_listproc[conta].msg[conta2]); am_listproc[conta].origem[conta2]=−1; am_listproc[conta].status−−; return(1); } } return(−1); } /****************************************************************/ int am_msgsend(int origem,int destino,char *am_msg) { /* Coloca mensagem no buffer retorna 1 se sucesso, ou −1 se erro (buffer cheio, por exemplo */ int conta,conta2; for (conta=0;conta<AM_MAXPROC;conta++) /* procura posicao de buffer do processo destino */ if (am_listproc[conta].nproc==destino) /* procura no buffer espaco disponivel para armazenar a msg */ for (conta2=0;conta2<AM_MSGMAX;conta2++) { if (am_listproc[conta].origem[conta2]==−1) {/* buferiza a mensagem */ am_listproc[conta].origem[conta2]=origem; am_listproc[conta].status=am_listproc[conta].status++; strcpy(am_listproc[conta].msg[conta2],am_msg); return(1); } } return(−1); } /****************************************************************/ long int am_strint (char *data) /* Retira o primeiro numero inteiro encontrado na string data, retorna−o convertido para inteiro e retira−o da string */ {char *aux; long int total=0; int sinal=1; for (aux=data;(*aux<’0’)||(*aux>’9’);aux++) {if (aux==NULL) return(−1); if (*aux==’−’) sinal=−1; } for (;(*aux>=’0’)&&(*aux<=’9’);aux++) total=(total*10) + (*aux−’0’); sprintf(data,"%s",aux); return(total*sinal); } /****************************************************************/ char **am_server (char **indata) {/* Programa responsavel pelo atendimento das chamadas de RPC, atraves das strings enviadas pelas chamadas, determina o tipo de servico requizitado e exetua−o */ static char data[AM_TAMMSG]; static char *pd,*rd; int stat,pid,am_pidreq,am_pidmsg; rd=*indata; switch (**indata) { case ’1’: /* Teste, usado pelo addhost */ am_strint(rd); am_minhahost=(char)am_strint(rd); sprintf(am_servername,"%s",rd+1); data[0]=’1’; data[1]=’\0’; break; case ’2’: /* Send */ data[0]=’1’; data[1]=’\0’; am_strint(rd); am_pidreq=am_strint(rd); am_pidmsg=am_strint(rd); am_msgsend(am_pidreq,am_pidmsg,rd+1); break; case ’3’: /* Receive */ am_strint(rd); am_pidreq=am_strint(rd); am_pidmsg=am_strint(rd); stat=am_msgrec(am_pidreq,am_pidmsg,data); if (stat==−1) {data[0]=’0’; data[1]=’\0’; } break; case ’4’: /* Startproc */ pid=fork(); if (!pid) { execv(&rd[2],NULL);exit(0); } sprintf(data,"%d",pid); for (stat=0;stat<AM_MAXPROC;stat++) if (am_listproc[stat].espera==−3) { am_listproc[stat].espera=−2; am_listproc[stat].nproc=pid; for (pid=0;pid<AM_MSGMAX;pid++) am_listproc[stat].origem[pid]=−1; stat=AM_MAXPROC+1; } if (stat==AM_MAXPROC) {sprintf(data,"%d",−1); break; } break; case ’5’: /* Myhost */ sprintf(data,"1#%d",am_minhahost); break; case ’6’: /* am_hostname */ am_strint(rd); pid=am_strint(rd); sprintf(data,"0"); if (am_minhahost==0) {for (stat=0;stat<AM_MAXPROC;stat++) if (am_listmaq[stat].numero==pid) sprintf(data,"1#%s",am_listmaq[stat].nome); } else sprintf(data,"2#%s",am_servername); break; case ’7’: /* cadastra nova host */ am_strint(rd); pid=am_strint(rd); for (stat=0;stat<AM_MAXPROC;stat++) if (am_listmaq[stat].numero==−1) { am_listmaq[stat].numero=pid; sprintf(am_listmaq[stat].nome,"%s",rd+1); stat=AM_MAXPROC; sprintf(data,"%s","1"); } break; case ’8’: /* registra o processo servidor*/ am_strint(rd); pid=am_strint(rd); am_listproc[0].espera=−2; am_listproc[0].nproc=pid; for (pid=0;pid<AM_MSGMAX;pid++) am_listproc[0].origem[pid]=−1; break; default: data[0]=’2’; data[1]=’\0’; } pd=data; return(&pd); } /****************************************************************/ main() {int aux=0; for (aux=0;aux<AM_MAXPROC;aux++) /* inicializa campos da lista de am_listproc */ {am_listproc[aux].espera=−3; am_listproc[aux].status=0; am_listmaq[aux].numero=−1; } registerrpc(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, am_server, xdr_wrapstring, xdr_wrapstring); svc_run(); fprintf(stderr,"Erro: svc_run retornou!\n"); exit(1); } BIBLIOGRAFIA [ S CH 96] S CHL E ME R, E l g i o . Am bi ent e de Mu l t i p r ocessam en t o par a um a Rede d e Máq ui n as UNI X : T r ab al h o sub m et i d o com o r equi si t o i n i ci al p ar a a o b te nçã o d o gr au de Bach ar el em I nf or m át i ca. Canoas: Depar t am ent o de I n for má t ic a d a UL BRA. 199 6 . [ S HA 87] S HAW, My r i l Cl em ent . UNI X I nt er nal s . US A: T AB bo o ks, 1987. [ S UN 90] S UN. Net wor k P r og r am m i ng Gu i d e . 1 990.