Troca de Mensagens _ Notas de Aula _ Prof. Tiago Garcia de Senna Carneiro DECOM / UFOP Num sistema distribuído não há memória compartilhada. Portanto, toda a comunicação entre processos deve ser realizada através de troca de mensagens. • Pilha de Protocolos Os processos de um sistema distribuído comunicam-se através da pilha de protocolos da rede que interliga as máquinas sobre as quais estão executando. Entretanto, este fato pode impor sérias restrições ao desempenho do sistema. Quando uma mensagem é recebida ou enviada através de uma pilha de protocolos, cada camada da pilha tende a anexar às mensagens enviada pelos processos um cabeçalho contendo informações de controle.A existência de todos esses cabeçalhos cria uma sobrecarga considerável no sistema. Nas redes de longa distância, onde a banda passante é limitada a algums kbps (tipicamente, 64kbps), esta sobrecarga não chega a ser um problema sério. O fator limitante neste caso é a capacidade das linhas de transmissão. Mesmo com toda a manipulação de cabeçalhos, os processadores são suficientemente rápidos para manter as linhas trabalhando à máxima velocidade. Então, um sistema distribuído de longa distância pode usar pilhas de protocolos, tais como o TCP/IP, sem comprometer de forma considerável o desempenho da comunicação entre processos. Entretanto, num sistema distribuído baseado em rede local a sobrecarga introduzida pela pilha de protocolos é desastrosa. Os processadores consomem um tempo muito grande processando protocolos que acabam transformando a vazão (throughput) da rede em apenas uma pequena fração da sua capacidade de transmissão nominal. Portanto, a maioria dos sistema distribuídos baseados em rede local não utilizam protocolos em camadas ou, no caso de usarem, implementam somente um subconjunto da pilha de protocolos. Além disso, para alcaçarem melhor desempenho os sistemas distribuídos tendem a evitar protocolos baseados em conexão com o TCP/IP. O modelo-cliente servidor é basedo em um protocolo muito simples, sem conexão, do tipo solicitação/resposta. O cliente envia uma mensagem ao servidor solicitando algum serviço, o servidor faz o trabalho e envia para o cliente o resultado ou um código de erro. Este esquema apresenta como vantagens a simplicidade e a eficiência. Assumindo um sistema homogêneo, este esquema de comunicação poderia ser implementado diretamente sobre a camada de enlace da pilha de protocolos TCP/IP evitando-se as demais camadas. • Endereçamento Para que um processo possa enviar mensagens a um processo remoto, ele precisa conhecer o endereço desse processo. O endereço, end, de um processo deve indentificar numa primeira instância a máquina na qual ele está executando e depois, quem é ele entre os vários processos executando naquela máquina. De posse desse endereço, qualquer processo pode invocar a primitiva send( end, &msg ) do sistema para eviar mensagens ao outro processo ou então a primitiva receive( end, &msg) quando desejar receber mensagens desse processo. Vários esquemas de endereçamento podem ser utilizados, cada um apresenta vantagens e desvantagens: a) Endereçamento máquina.processo:neste esquema o endereço de um processo é formado por duas partes, a primeira parte é o endereço da máquina onde o processo está executando e a segunda parte é o endereço do processo dentro da máquina. Supondo a existência de uma máquina cujo endereço IP seja 200.131.209.193 e a existência de um número que identifica um processo dentro daquela máquina como o processo 5291, o endereço do processo poderia ser expresso como 200.131.209.193.5291. Este esquema apresenta dois problemas. O primeiro diz respeito à transparêcia à migração que todo sistemas distribuído deve prover. O fato de usarmos o endereço da máquina como parte do endereço quebra tal transparência uma vez que o processo 5291 não poderia migrar para outra máquina sem causar problema, pois os processos que precisam comunicar-se com ele não o encontrariam mais. Além disso, para garantir a transparência á migração deveríamos garantir que nehum outro processo em todo o sistema fosse identificado pelo número 5291, ou seja, que o identificador 5291 tivesse um escopo global, pois só dessa forma o processo 5291 podera ser encontrado caso migrasse para uma outra máquina do sistema. Neste momento é que surge o segundo problema. Que método seria utilizado para gerar identificadores universais para todos os processos do sistema? Este método iria interferir na confiabilidade ou desempenho do sistema distribuído? Por exemplo, centralizar a reponsabilidade de gerar esses identificadores em um único processo do sistema, com certeza, iria criar um novo gargalo no sistema além de fazer com que todo o sistema dependa do bom funcionamento de um único processo. Grande parte do esquema de comunicação do UNIX de Berkley utiliza um esquema um pouquinho diferente desse: endereçamento máquina.local-ident. O campo máquina é o endereço IP de 32 bits da máquina e o campo local-ident é um número de 16 bits escolhido radômicamente pelo próprio processo. Um processo começa sua execução com uma chamada ao núcleo do sistema para informá-lo que seu identificador é local-ident. A probabilidade de um processo escolhar um ident-local que esteja sendo utilizado é remota, mas caso isso aconteça o processo deverá escolher outro identificador. b) Endereçamento de processo em broadcast: neste método um processo possui um endereço que não depende do endereço da máquina onde está rodando. Quando um processo A precisa comunicar-se com um processo B, o processo precisa antes de qualquer coisa enviar uma mensagem broadcast na rede perguntado "Que máquina está executando o processo B?". Todas as máquinas receberão esta mensagem, mas somente a máquina que estiver o processo B responderá enviando o seu endereço para o processo A. O problema com este esquema é que ele pode causar muita sobrecarga na rede, pois todo processo para comunicar-se com outro precisará executar um broadcast. Além disso, um processo terá sempre que esperar pelo endereço da máquina que executa o processo com quem deseja se comunicar, esta fato torna-se ainda mais grave a penalização que a comunicação entre processos impõe ao desempenho dos sistemas distribuídos. c) Endereçamento de através de servidores de nome: no esquema de endereçamento através de servidores de nome um processo extra é utilizado para mapear o nome de serviços escritos em alto nível (ASCII), para endereços de máquina. Este processo é chamado servidor de nome. Serviços são facilidades que processos servidores oferecem a outros processos do sistema. Um serviço é está sempre associado a um porto padronizado por convenção, por exemplo, o serviço de ftp é associado ao porto 21 e o serviço de e-mail é associado ao porto 25. Desta forma, quando o processo deseja solicitar um serviço, ele primeiro deve consultar o servidor de nome para saber o endereço da máquina que executa o processo que fornece o serviço e depois ele deve conectar-se a esta máquina informando o porto que corresponde ao serviço que ele deseja. O porto é um número que permite ao sistema operacional multiplexar a rede entre vários processos. Todo processo que deseja fornecer algum serviço através da rede deve associar-se a um porto, assim quando uma mensagem chegar através da rede o sistema operacional terá como saber para qual processo a mensagem deverá ser entregue. O problema deste esquema de endereçamento é que ele insere um componente centralizado no sistema: o servidor de nome. • Comunicação Síncrona versus Comunicação Assíncrona A comunicação entre processos pode ocorrer de duas maneiras diferentes. Na comunicação síncrona quando um processo invoca a primitiva send(addr, &buffer) para enviar uma mensagem a outro processo, a sua execução fica suspensa até que a mensagem realmente chegue ao processo destinatário. Quando um processo invoca a primitiva receive( addr, &buffer), a execução desse processo fica suspensa até que uma mensagem vinda do processo addr chegue. A comunicação síncrona também é conhecida como comunicação com bloqueio ou bloqueante. Na comunicação assíncrona a execução de um processo não é bloqueda quando o processo invoca a primitiva send ou receive. A comunicação assíncrona é a que apresenta um menor custo em termos de desempenho, pois permite ao sistema distribuído explorar ao máximo o paralelismo de processamento e comunicação. Entretanto, é a forma de comunição que apresenta maiores dificuldades de implementação, além de criar novos problemas para o programador. Por exemplo, suponha que um processo tenha invocado de forma assíncrona a primitiva send(addr, &buffer) e que portanto, sua execução tenha continuado a ser realizada. Quando este processo terá certeza que pode alterar o conteúdo da variável buffer sem alterar a informação a ser trocada durante a comunicação? A resposta é simples, não há como. Duas soluções podem ser dadas para o problema acima: 1) antes de ser enviada toda mensagem deve ser copiada para uma área de armazenamento temporário interna ao núcleo do sistema. Assim, qualquer alteração no conteúdo da variável buffer não influenciará a comunicação em andamento. Fatores críticos para esta estratégia é a determinação do tamanho dessa área de armazenamento temporário e o número máximo permitido de mensagens assíncronas sendo enviadas. 2) sempre que uma mensagem terminar de ser entregue ao seu destino, o processo que a gerou deverá ser interrompido e informado do fato, ou seja, uma interrupção será gerada na máquina do processo que originou a mensagem. Porém, o uso de interrupções torna o programação dos processos comunicantes mais difícil, além de quase impossibilitar a depuração dos mesmos. Um aspecto importante da comunicação síncrona é a necessidade do uso de temporizadores. Este temporizadores deverão ser utilizados para evitar que processos fiquem eternamente esperando pela confirmação do envio de uma mensagem ou pela chegada de uma mensagem gerada por outro processo. • Comunicação com Bufferização versus Comunicação SemBufferização Suponha que um processo A envie uma mensagem M para um processo B, mas que o processo B ainda não tenha executado a primitiva receive(addrA, &buffer), ou seja, que o processo B ainda não esteja esperando por uma mensagem. Quando a mensagem chegar à máquina onde o processo B está executando o sistema operacional deverá descartar a mensagem ou esperar que o processo B a solicite? O que acontecerá se o processo B nunca requisitar a mensagem M? Duas soluções podem ser adotadas para resolver este problema. A primeira e a menos desejada é simplesmente proibir que um processo invoque a primitiva send se não houver um lugar para armazená-la na máquina destino. Isto é, o processo transmissor ficará bloqueado até o processo destino invoque a primitiva receive. A segunda solução faz uso de um buffer interno ao núcleo do sistema operacional chamado mailbox e de temporizadores. A medida que as mensagens são recebidas elas vão sendo inseridas no mailbox que funciona como uma fila de mensagens e para cada mensagem um novo temporizador é iniciado com o objetivo de medir o tempo de permanência da mensagem no mailbox. Conforme os processos forem requisitando as mensagens, estas vão sendo retiradas do mailbox. Se nehum processo requisitar uma determinada mensagem e seu temporizador atingir um valor máximo permitido, a mensagem é descartada.O problema com a segunda solução é a sobrecarga gerada pela manutenção dos mailboxes. • Comunicação Confiável versus Comunicação Não-Confiável Muitas vezes o envio de uma mensagem deve ser confiável, isto é, o processo que enviou a mensagem deve receber uma confimação do processo destino informando o perfeito recebimento da mensagem. Este mecanismo evitaria que uma mensagem perdida ou corrompida atrapalhe o funcionamento do sistema. Num arquitetura cliente-servidor a confirmação pode ser necessária tanto para garantir que um servidor recebeu a solicitação de um cliente como para garantir que um cliente recebeu a resposta de um servidor. Como tanto a solicitação do cliente como a resposta do servidor são recebidas pelo núcleo do sistema operacional na forma mensagens a serem entregues aos processos servidor e cliente, respectivamente, o próprio núcleo do sistema poderia confirmar o recebimento dessas mensagens evitando-se que esses processos tivessem que fazê-lo, resultando num melhor desempenho do sistema. Para melhorar ainda mais o desempenho do sistema, quando o processamento solicitado por um cliente for muito pequeno, em termos computacionais, a própria resposta a esta solicitação poderia ser utilizada como confirmação. Desta forma, não sera necessário o envio de duas mensagens para o mesmo processo onde um intervalo de tempo muito pequeno separaria uma mensagem da outra, evitando-se sobrecargas na rede de comunicação do sistema distribuído.