






















































Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Os melhores documentos à venda: Trabalhos de alunos formados
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Comunidade
Peça ajuda à comunidade e tire suas dúvidas relacionadas ao estudo
Descubra as melhores universidades em seu país de acordo com os usuários da Docsity
Guias grátis
Baixe gratuitamente nossos guias de estudo, métodos para diminuir a ansiedade, dicas de TCC preparadas pelos professores da Docsity
A comparação de implementações de sistemas de sincronização entre o modelo base e o modelo communicating sequential processes (csp). O texto inclui programas para o banquete dos filósofos, produtor-consumidor e semáforos, com análises de desempenho e discussão sobre as vantagas e desvantagas de cada modelo. O documento também menciona possíveis trabalhos futuros.
O que você vai aprender
Tipologia: Notas de aula
1 / 62
Esta página não é visível na pré-visualização
Não perca as partes importantes!
Resumo Sistemas multiprocessados têm como objetivo melhorar a performance de programas uti- lizando paralelismo. Para o funcionamento correto do sistema, é necessário que os proces- sadores se comuniquem e se coordenem. Este trabalho compara dois modelos de multipro- cessamento: memória compartilhada e sincronização com semáforos; e rede de processadores com memória distribuída e sincronização por rendezvous. Foi implementado um simulador da arquitetura MIPS que permite extrair informações dos programas nele executados para a análise e comparação da performance dos modelos. O trabalho conclui comentando os resultados dos experimentos.
Sistemas multiprocessados têm como objetivo melhorar a performance de programas utilizando paralelismo. A possibilidade do paralelismo advém da natureza concorrente dos programas exe- cutados nesse tipo de sistema. Devido à concorrência, é necessário que os processadores se comuniquem e se coordenem para o funcionamento correto do sistema. São comparados dois modelos de multiprocessamento: (i) memória compartilhada, interco- nexão entre os elementos por barramento, sincronização com semáforos (modelo base); e (ii) rede de processadores com memória distribuída e sincronização por rendezvous (modelo CSP). A com- paração parte da execução de dois problemas clássicos de sincronização (produtor-consumidor e banquete dos filósofos), e da análise dos contadores de performance para cada um dos mode- los. A principal motivação do trabalho é investigar a utilização de um modelo de programação baseado em troca de mensagens com a comunicação explícita. No modelo base, o barramento utilizado é implementado com uma estrutura de dados que garante que todos os processadores que tentem acessá-lo eventualmente consigam realizar esse acesso. Os semáforos utilizados são semáforos contadores, implementados utilizando instruções padrão do conjunto MIPS. No modelo CSP, a rede consiste em nós de processamento dispersos em uma malha retangu- lar. A troca de mensagens e sincronização entre os processadores foi implementada como uma extensão do conjunto de instruções do MIPS. A transmissão de mensagens é realizada por todos os nós de processamento. Na comparação entre os modelos, para o problema produtor-consumidor foi observado que o modelo CSP apresenta vantagens sobre o modelo base em relação a tamanho do código da solução e performance. Essa situação se inverte para o problema do banquete dos filósofos. Um equívoco foi cometido na implementação dos problemas no modelo CSP, imitar o modelo base com a sincronização por semáforos. Além de resultados contraintuitivos, até mesmo os resultados positivos são afetados pelo equívoco. Este trabalho é similar a [5], embora tenha sido desenvolvido de forma independente.
Este trabalho é organizado da seguinte forma: a Seção 2 introduz e define os conceitos utili- zados para a compreensão das descrições do simulador e dos experimentos; a Seção 3 escreve o simulador e os experimentos; a Seção 4 contém uma argumentação informal sobre a equivalência dos mecanismos de sincronização; a Seção 5 mostra os códigos utilizados no experimento; a Se- ção 6 apresenta, analisa e comenta o resultado dos experimentos; a Seção 7 faz uma comparação geral entre os modelos considerando os resultados das simulações; e a Seção 8 conclui o trabalho.
2 Definições
Esta seção define e descreve conceitos utilizados no trabalho. A seção inicia apresentando concei- tos essenciais para o entendimento da concorrência e os mecanismos necessários para a solução de problemas do tipo. Em seguida são apresentados a noção de execução paralela, o tipo de sistema computacional que utiliza dos conceitos anteriores para obter melhor performance e como a memória desses sistemas podem ser implementadas. Logo após, são introduzidas lingua- gens para a descrição e programação de problemas de concorrência. A seção finaliza com breves descrições dos processadores que inspiraram e são utilizados no trabalho.
Considere o seguinte trecho de código:
Programa 1: Implementação da Formula de Bhaskara
Bhaskara () { a = 1; /* E0 */ b = 2; /* E1 */ c = 3; /* E2 */
bb = b * b ; /* E3 */ aux = 4 * a * c ; /* E4 */
delta = bb - aux ; /* E5 */
sqrt_delta = sqrt ( delta ); /* E6 */
x0 = b + sqrt_delta ; /* E7 */ x1 = b - sqrt_delta ; /* E8 */
print ( x0 , x1 ); /* E9 */ }
Eventos são um conjunto de instruções de máquina que executam alguma computação, no caso do exemplo, os eventos (denotados En ) realizam atribuições, operações aritméticas e im- primem valores de variáveis. Tem-se então que o Programa 1 pode ser descrito como o conjunto de eventos:
Bhaskara = { E 0 , E 1 , E 2 , E 4 , E 5 , E 6 , E 7 , E 8 , E 9 }. (1) Processos são uma sequência de eventos [8]. Seja o operador “;” indicador da execução sequencial dos eventos, o operador “. ” indicador o término da execução. Executando os eventos do exemplo em ordem numérica, tem-se então o seguinte processo:
E 0 ; E 1 ; E 2 ; E 3 ; E 4 ; E 5 ; E 6 ; E 7 ; E 8 ; E 9 ;. (2) A ordem de execução dos eventos no processo não é totalmente arbitrária. Dada a definição da relação acontece antes em [8], denotada como “→”, os eventos do subconjunto { E 0 , E 1 , E 2 }
2.3.2 Região crítica
Dado um sistema computacional executando múltiplos processos, e os processos compartilham de um recurso em comum, a região crítica é o conjunto de eventos que manipulam o recurso compartilhado. Para evitar condições de corrida, as regiões críticas dos processos envolvidos devem satisfazer as seguintes condições: (i) apenas um processo executa sua região crítica ( exclusão mútua ); (ii) nenhum processo espera indefinidamente para entrar na região crítica; e (iii) nenhum processo fora da região crítica pode bloquear os demais processos.
2.3.3 Operações atômicas
Operações atômicas ou operações indivisíveis são operações que não interrompem ou interferem umas com as outras. Para que uma operação seja atômica, dois eventos a e b envolvidos na operação, devem satisfazer a seguinte condição:
a → b ⊕ b → a. (8) O operador “⊕” denota a operação ou exclusivo.
2.3.4 Semáforos
Os semáforos foram propostos por Dijkstra [4], e utilizam o princípio da exclusão mútua para sincronizar os processos concorrentes. Sejam “sem” uma variável de tipo inteiro inicializada com um valor maior ou igual a zero, e P(), V() duas funções. P(sem) decrementa o valor do semáforo em um. Se o valor resultante for maior que zero, então a execução prossegue normalmente. Caso contrário, o processo espera até que recurso seja liberado, i.e , até que o semáforo seja incrementado para um valor maior que zero. V(sem) incrementa o valor do semáforo em um. P(), V() são operações atômicas.
2.3.5 Rendezvous
Rendezvous é um mecanismo de sincronização no qual os processos envolvidos aguardam uns aos outros em um determinado ponto de suas execuções até que todos os demais processos também atinjam esse ponto. Dados dois processos A, B , dois eventos ax, ar ∈ A , dois eventos by, br ∈ B , ar, br são os eventos de sincronização, ax, by são eventos quaisquer de seus respectivos processos que executam antes de ar, br. O processo A faz rendezvous com o processo B quando:
ar → br ∧ ax → ar ∧ by → br. (9)
2.3.6 Inanição e impasse
Starvation ou inanição é a condição de um processo que necessita de um recurso para continuar sua execução, mas, o processo nunca consegue acessar o recurso. Deadlock ou impasse é um caso específico da inanição: um processo está em espera para acessar a região critica e depende de algum outro processo para liberar o acesso, mas o segundo processo nunca o libera.
Execução paralela é a execução simultânea dos eventos de um processo, ou de diversos processos. Note que concorrência e paralelismo não são a mesma coisa. Concorrência diz respeito à compo- sição dos eventos dos processos ao passo que paralelismo é a execução simultânea desses eventos. A partir desse ponto, é considerado que “execução paralela” e paralelismo são sinônimos.
Paralelismo em dados consiste em explorar as características dos dados processados de forma a permitir que os processadores operem sobre blocos de dados simultaneamente. Por exemplo, o cálculo dos elementos do vetor b , obtido pela multiplicação dos elementos do vetor a por uma constante K , pode ser definido por:
bi =
∑^ n
i =
aiK. (10)
Nota-se que não há dependências entre o cálculo dos elementos individuais de b , o que permite que múltiplos processadores calculem elementos do vetor resultado sem interferir com a computação efetuada pelos outros processadores. Paralelismo no nível de tarefa consiste em quebrar o problema computacional em diferentes processos e atribuir esses processos a diferentes processadores. Com base no exemplo da Equa- ção 10, dado um sistema com p processadores, sejam^1 n = | a |; p < n e p múltiplo de n ; i ∈ [0 ..n ); e j ∈ [0 ..p ), pode-se dividir a tarefa de multiplicação em p processos:
procj : bi =
( j +1)( ∑ n/p )
i = j ( n/p )
aiK (11)
Esse é o tipo de paralelismo normalmente explorado por sistemas multiprocessados, e é o foco deste trabalho.
Um sistema multiprocessado é definido neste trabalho como um sistema computacional que possui mais de um processador. O objetivo de um sistema multiprocessado é melhorar algum parâmetro relacionado à per- formance (tempo de execução, consumo de energia, vazão dos dados, ou alguma combinação destes) dos processos executados. Em um sistema implementado adequadamente, esse objetivo é cumprido dada a habilidade inata de paralelismo do sistema computacional e da independência dos eventos concorrentes nele executados. Sistemas multiprocessados se apresentam em várias formas e tamanhos: Desde meados da primeira década do século XXI, o público geral tem acesso a circuitos integrados com mais de um processador, e com os avanços na tecnologia de fabricação cada vez mais processadores podem ser incluídos num circuito integrado.
Este trabalho define como organização de memória o conceito de como a memória é visualizada do ponto de vista lógico pelos programadores do sistema.
2.6.1 Memória compartilhada
O modelo de organização de memória compartilhada consiste nos processadores acessando uma memória unificada do ponto de vista do programador. A comunicação entre os processadores acontece na memória por meio de operações de load e store : a operação load lê um valor da memória e a operação store armazena um valor na memória. Essas operações são o equivalente lógico das instruções load e store.
(^1) Notação: um intervalo em Z é representado por [ a..b ] se a e b pertencem ao intervalo (fechado-fechado), ou ( a..b ) se a e b não pertencem ao intervalo (aberto-aberto). Um intervalo pode ser fechado-aberto ([ a..b )) ou aberto-fechado (( a..b ]).
2.7.3 Comando de seleção de alternativas O comando de seleção de alternativas é uma estrutura de execução condicional na qual uma determinada lista de comandos só é executada quando a respectiva condição ( guarda ) é satisfeita.
Programa 4: Definição do comando de seleção de alternativas
< sele ç ão > ::= [ < comando de guarda >{# < comando de guarda >}] < comando de guarda > ::= < guarda > - > < lista de comandos > < guarda > :: = < lista de guardas >| < lista de guardas >; < comando de entrada > < lista de guardas > :: = < elemento da guarda >{; < elemento da guarda >} < elemento da guarda > ::= < express ã o booliana >| < declara ç ão >
A diferença deste comando com relação a outras estruturas de mesmo tipo como if...else ou switch é que a ordem de execução das guardas é arbitrária. Se a guarda escolhida falhar, outra é escolhida até que se esgotem as alternativas. Caso todas as guardas falhem o comando de seleção de alternativas falha. Por exemplo, para selecionar entre três alternativas:
[x >y - > PROCESSA_XY # P ?a - > PROCESSA_A # Q ?b - > PROCESSA_B ]
Os comandos de guarda são:
x > y -> PROCESSA_XY /* CG0 / P? a -> PROCESSA_A / CG1 / Q? b -> PROCESSA_B / CG2 */
O comando de seleção de alternativas arbitrariamente seleciona um comando de guarda. Por exemplo, seja CG 0 o primeiro comando a ser executado e a guarda x > y é avaliada. Supondo que falhe, o comando de seleção de alternativas escolhe CG 2 , mas o comando de entrada da guarda Q? b não está pronto. Por fim, o comando de seleção de alternativas seleciona CG 1 , cuja guarda P? a está pronta, então a lista de comandos indicada por PROCESSA_A é executada.
Por ser uma linguagem formal para a descrição de sistemas, CSP não se adéqua enquanto linguagem de programação. Em 1982 foi introduzida a linguagem de programação occam, que implementa os conceitos definidos em CSP [2]. A sintaxe da linguagem é diferente da que é usada em CSP, mas como implementa as mesmas estruturas já vistas anteriormente, essa não é discutida neste trabalho.
O Transputer é uma implementação em silício do modelo de computação da linguagem occam. O objetivo dos projetistas era explorar o decrescente custo de fabricação de circuitos integrados para implementar sistemas multiprocessados. Este trabalho faz uso da arquitetura do transputer descrita em [9]. O transputer diferencia-se das demais arquiteturas da época devido ao seu conjunto mí- nimo de instruções (16 instruções, com um número maior em versões mais recentes), memória integrada e facilidade de conectar-se a outros transputers. Os transputers se conectam por par de links, um par para cada direção da comunicação (Norte, Sul, Leste, Oeste). A comunicação ponto-a-ponto emprega um protocolo serial. Por ser uma comunicação ponto-a-ponto, múltiplos transputers podem comunicar-se concorrentemente, aumentando a quantidade de dados que o sistema pode processar em paralelo.
MIPS é uma arquitetura de processadores do tipo RISC, desenvolvido no início da década de 1980 [7]. Suas características principais são: (i) os dados são operados dentro dos registradores;
(ii) os únicos tipos de instrução que alteram a memória são as instruções load e store; e (iii) as instruções têm tamanho fixo de 32 bits.
3 Experimentos
Para efetuar esse trabalho foi implementado um simulador de sistemas multiprocessados. Dois modelos de sistemas multiprocessados são simulados, um com memória compartilhada, interco- nexão dos processadores por barramento e sincronização por semáforos, e outro com memória distribuída, interconexão dos processadores ponto-a-ponto (formando uma malha retangular) e sincronização por rendezvous. Os experimentos efetuados nesse trabalho consistem na execução de dois problemas clássicos de sincronização.
Os problemas escolhidos para a comparação de desempenho dos dois modelos são pares produtor- consumidor e o banquete dos filósofos. Esses problemas clássicos são normalmente utilizados para testar os mecanismos de sincronização [1].
3.1.1 Produtor-consumidor
O processo produtor produz algum dado que é consumido pelo processo consumidor. Após o processo produtor produzir o dado, este dado é inserido em uma área de transferência ( buffer ) na qual o processo consumidor o remove. O buffer é implementado como uma fila circular e pode armazenar até MAXELEM elementos. A inserção dos elementos do buffer ocorre no fim na fila. A remoção dos elementos dá-se no início da fila. Para garantir que os apontadores do início e fim da fila sejam consistentes, os processos não devem acessar o buffer concorrentemente. Caso o buffer esteja cheio, o processo produtor aguarda até que seja liberada uma posição para a inserção de um novo elemento. Caso o buffer esteja vazio, o processo consumidor aguarda até que algum elemento seja inserido. Este trabalho explora duas variações do problema. Na primeira há um processo produtor e C consumidores, com C ≥ 1. Na segunda versão são dois produtores e C consumidores, C ≥ 1.
3.1.2 Banquete dos filósofos
No problema do banquete dos filósofos , têm-se 5 instâncias de um processo de tipo filósofo que realizam duas ações, comer e pensar. Para comer, os filósofos sentam-se em uma mesa circular com 5 pratos, 5 garfos (cada garfo posicionado entre dois pratos) e no meio da mesa há um prato com macarrão que é (magicamente) reposto a todo momento. Para retirar e comer sua porção, o filósofo utiliza o garfo que está a sua esquerda mais o garfo que está a sua direita. Após comer (e somente após comer), o filósofo devolve os dois garfos à mesa, levanta-se, e vai realizar a ação de pensar até que sinta fome novamente e repita o processo. O problema consiste em como os filósofos vão organizar-se à mesa de forma a que todos tenham acesso aos dois garfos e que nenhum filósofo morra de fome.
O modelo base consiste em um sistema com p > 1 processadores, uma única memória compar- tilhada uniformemente entre os processadores por meio de um barramento. Apenas um proces- sador obtém acesso ao barramento por ciclo da simulação. A sincronização entre os processos utiliza semáforos.
arbitragem pelo acesso ao barramento é implementado com uma estrutura de dados FIFO ( first- in-first-out ). A implementação dessa FIFO garante que nenhum processador fique a esperar eternamente pelo acesso ao barramento. Um processador ganha acesso ao barramento caso todas requisições anteriores já tenham sido atendidas. Dado um sistema com p processadores, o processador que deseja utilizar a memória para a operação load ou store primeiro faz uma requisição ao barramento, que consiste em enfileirar o ID do processador em uma fila circular de tamanho p , caso o ID já se encontre na fila, este não é enfileirado novamente. O barramento possui um variável booliana de controle (used) que indica se o barramento foi utilizado no ciclo atual. Se used tiver valor 0, o controlador do barramento então verifica o elemento inicial da fila, caso o valor seja o mesmo valor do ID do processador requerente, este é desenfileirado e processador ganha o acesso ao barramento e executa a operação de acesso à memória. Caso used tenha valor 1, a fila está cheia ou o elemento inicial da fila tem valor diferente do ID do processador requerente, o processador não ganha acesso ao barramento e não executa a operação de acesso à memória, devendo tentar novamente no próximo ciclo (seu PC não é incrementado). A função de requisição de acesso ao barramento permite a execução de apenas um processador por vez, e o pseudocódigo do Programa 5 indica a implementação. A variável global bus_queue é a fila FIFO do barramento.
Programa 5: Implementação da requisição de acesso ao barramento
busacc ( ID ) { enqueue ( bus_queue , ID ); /* enfileira ID */
if ( used ) { /* barramento utilizado? */ return FAILURE ; /* repete op. no pr ó x. ciclo */ }
if ( head ( bus_queue ) != ID ) { /* ID é o elem. inicial da fila? */ return FAILURE ; /* caso n ão , repete opera ç ã o */ }
dequeue ( bus_queue ); /* desenfileira ID */
used = 1; /* utiliza barramento */
return SUCCESS ; /* ID ganha acesso */ }
3.4.4 Comunicação ponto-a-ponto Para a comunicação ponto-a-ponto, cada processador do sistema simulado possui quatro pares de links (Norte, Sul, Leste, Oeste) que encaminham as mensagens na rede. Os links são modelados por uma fila com as mensagens e representam a comunicação em apenas um sentido. Outra estrutura da comunicação é a caixa de mensagens , na qual são depositadas as men- sagens que o processador recebe. A caixa de mensagens contém um vetor das mensagens, cujos índices representam o remetente. O conjunto formado pelos links, processador e caixa de mensagens recebe o nome de nó da rede , cuja ilustração é a Figura 1. A cada ciclo do simulador, as mensagens saltam do nó atual para algum dos nós vizinhos. Do ponto de vista do processador, o envio ou recebimento das mensagens é realizado apenas pelos comandos CSP. O simulador implementa, para cada nó, as máquinas de estado que controlam o tráfego de mensagens pela rede.
CPU
Memória
COP 2
MBOX oeste
sul
leste
norte
Figura 1: Diagrama de blocos do nó de processamento
As mensagens não são encaminhadas para o nó vizinho caso o link de destino da mensagem não tenha espaço para acomodar a mensagem. Quando isso acontece, é dito que a mensagem foi postergada. Para transmitir a mensagem, compara-se o ID do nó atual (cid) com o nó destino (dest). Caso cid < dest e os nós pertençam a linhas diferentes, a mensagem é enviada ao nó do norte; se os nós pertencem a mesma linha, a mensagem é enviada para o nó do leste. O caso cid > dest é análogo ao caso anterior, porém enviando as mensagens para os nós do sul e oeste, respectivamente. Caso cid = dest, a mensagem é enviada à caixa de mensagens do nó. Esse algoritmo de roteamento evita a congestão da rede e o pseudocódigo que o implementa é indicado no Programa 6.
Programa 6: Algoritmo de roteamento das mensagens na malha 2D
guidance ( cid , dest ) { if ( cid < dest ) { if ( sameline ( cid , dest )) { /* ID atual é oeste do destino? */ return EAST ; } return NORTH ; /* ID atual é sul do destino? */ }
if ( cid > dest ) { if ( sameline ( cid , dest )) { /* ID atual é leste do destino? */ return WEST ; } return SOUTH ; /* ID atual é norte do destino? */ }
return MBOX ; /* mensagem alcan ç ou o destino */ }
3.4.5 Implementação dos semáforos A arquitetura MIPS possui duas instruções para a implementação de operações atômicas: ll ( load-linked ) e sc ( store-conditional ). O par de instruções implementa a operação read-modify- write , que consiste nos processadores de um sistema multiprocessado lerem um dado, modificá-lo e escrevê-lo de volta na memória, sem que o dado seja acessado por outro(s) processador(es). O processador que executa a instrução ll reserva um endereço, indicando o início da operação
Programa 10: Implementação da operação V()
Sem_V: ll $t0 , 0( $a0 ) # tenta acesso ao sem á foro addiu $t0 , $t0 , 1 sc $t0 , 0( $a0 ) # tenta atualizar sem á foro beqz $t0 , Sem_V # sc falhou? Repete nop jr $ra nop
3.4.6 Implementação dos comandos CSP Os comandos CSP que o simulador implementa são os de entrada, saída e seleção de alternativas. Os comandos são implementados utilizando o coprocessador 2 (CP2). A interface consiste em 4 registradores, indicados na Tabela 1, juntamente com 3 operações do CP2, indicados na Tabela 2.
Tabela 1: Registradores do CP
Nº do registrador Nº de seleção Função 0 0 processador correspondente 0 1 dado enviado/recebido 0 2 status 0 5 número de alternativas
Tabela 2: Operações do CP
Nº da operação Função Apelido 2 Comando de entrada INPUT 3 Comando de saída OUTPUT 4 Comando de seleção de alternativas ALT
O texto utiliza a notação “CP2[reg][sel]” para se referir ao registrador do CP2 com o número de registrador “reg”, e número de seleção “sel”. As operações INPUT, OUTPUT e ALT modificam CP2[0][2] ( status ) para indicar a operação atual. Enquanto o registrador status for diferente de zero, o processador é bloqueado pelo con- trolador de rede ( stall ) e não pode executar a próxima instrução. O registrador status é utilizado pelo controlador da rede para identificar o estado da operação de comunicação atual.
3.4.6.1 Comando de entrada O comando de entrada comporta-se como foi definido na Seção 2.7.2. Para implementar o comando de entrada o programador escreve o ID do processador fonte em CP2[0][0] ( corr ), executa a operação INPUT e lê de CP2[0][1] ( data ) o dado recebido. Não há verificação de tipo. O código do Programa 11 mostra a implementação do comando em uma função que pode ser chamada pelo programador.
Programa 11: Implementação do comando de entrada
C2_input: mtc2 $a0 , $0 , 0 # corr cop2 2 # INPUT mfc2 $v0 , $0 , 1 # data jr $ra nop
3.4.6.1.1 Implementação no simulador Ao identificar a operação INPUT, o controla- dor da rede verifica se a mensagem do processador correspondente está na caixa de mensagens; se a mensagem não chegou, o processador fica bloqueado até o próximo ciclo. Caso a mensagem tenha chegado, o controlador de rede cria uma mensagem ACK e tenta a enviar ao processador correspondente. Se o enlace estiver ocupado, o controlador repete a operação no próximo ciclo. Se o ACK foi enviado com sucesso, o controlador retira a mensagem da caixa de mensagens, os dados da mensagem são escritos em data e por fim, escreve o valor zero em status , o que desbloqueia o processador que efetuou a operação INPUT (libera o stall ). O pseudocódigo do Programa 12 indica a implementação do controle. mbox é a caixa de mensagens do nó, insmsg(dir, msg) insere a mensagem msg no link de saída do nó na direção dir, a função newack(to, from) cria uma mensagem ACK a ser enviada para o nó to pelo nó from.
Programa 12: Implementação do controle do comando de entrada
input ( mbox , * status , corr , ID , * data ) { Direction dir ;
struct msg * msg , * ack ;
msg = mbox [ corr ]; if (! msg ) { /* se n ã o chegou nova msg , */ return TRYAGAIN ; /* stall */ }
/* cria ACK */ ack = newack ( corr , ID );
dir = guidance ( ID , corr ); if ( insmsg ( dir , ack ) == FAILURE ) { return TRYAGAIN ; /* se enlace ocupado , */ } /* stall */
/* retira mensagem */ mbox [ corr ] = NULL ;
data = msg - > data ;
status = 0; /* fim da execu ç ã o do comando */
return SUCCESS ; }
3.4.6.2 Comando de saída O comando de saída é o mesmo definido na Seção 2.7.2. Para implementar o comando de saída o programador escreve o ID do processador destino em corr , escreve o dado a ser enviado em data e executa a operação OUTPUT. Não há verificação de tipo. O
3.4.6.3 Comando de seleção de alternativas O comando de seleção de alternativas difere do apresentado na Seção 2.7.3, pois a única expressão válida para as guardas são comandos de entrada com mais uma cláusula de escape. Para implementar o comando de seleção de alternativas o programador escreve em corr o endereço do vetor de alternativas (um vetor de números inteiros de 32 bits em que cada elemento do vetor é um processador fonte de um comando de entrada), escreve em CP2[0][5] ( nalt ) o tamanho do vetor de alternativas, executa a operação ALT e lê de data o dado recebido. Não há verificação de tipo. O Programa 15 mostra a implementação.
Programa 15: Implementação do comando de seleção de alternativas
C2_alt: mtc2 $a0 , $0 , 0 # corr mtc2 $a1 , $0 , 5 # nalt cop2 4 # ALT mfc2 $t0 , $0 , 1 # data sw $t0 , 0( $a2 ) addiu $v0 , $k1 , 0 jr $ra nop
3.4.6.3.1 Implementação no simulador Ao identificar a operação ALT, o controlador de rede busca na caixa de mensagens por alguma mensagem de algum dos processadores indi- cados no vetor de alternativas. Caso alguma dessas mensagem tenha chegado, o controlador da rede carrega data com os dados da mensagem, remove a mensagem da caixa de mensagens, envia ao processador correspondente uma mensagem ACK, carrega GPR[K1] com o ID da alternativa selecionada e carrega status com o valor zero. Se o último elemento do vetor de alternativas for o número − 1 , a operação completa com sucesso, mesmo que nenhum dado tenha sido rece- bido – essa alternativa é chamada cláusula default. Se nenhuma mensagem chegou na caixa de mensagens, o controlador bloqueia o processador que tentará novamente no próximo ciclo. A operação ALT possui uma estrutura de controle que é interna ao controlador de rede. A cada ciclo, o controlador começa a verificação da caixa de mensagens a partir do índice start (variável privada de cada nó, inicializada com valor zero), incrementando o índice de forma circular. Os valores da variável start são mantidas entre as execuções da operação. Esse algoritmo tenta garantir que todos os enlaces sejam pesquisados pelo do controlador. O pseudocódigo indicado no Programa 16 mostra a implementação do controle da operação. A variável global netsize é o número de nós na rede, belongs(arr, val) indica se o valor val pertence ao vetor arr.
Espaço em branco proposital.
Programa 16: Implementação do controle do comando seleção de alternativas
alt (* mbox , * start , * status , * corr , ID , ncl , * data ) { int defaults , found , done , sel ;
if ( corr [ ncl - 1] == -1) { /* default presente? */ defaults = 1; }
/* seleciona alternativa */ sel = * start ; found = 0; while (! done ) { if (( mbox [ sel ] != NULL ) && belongs ( corr , sel )) { done = found = 1; } else { sel = ( sel + 1) % netsize ; if ( sel == start ) { done = 1; } } }
if (! found ) { /* selecionou alternativa? */ if ( defaults ) { /* caso n ão , default presente? */ return SUCCESS ; /* sucesso */ } else { /* sen ão , stall */ return TRYAGAIN ; } }
/* completa comando de entrada */ input ( mbox , status , sel , ID , data ); GPR [ K1 ] = sel ; return SUCCESS ; }
3.4.7 Temporização das instruções Dado um sistema com p processadores, as instruções que realizam operações de load e store demoram entre 1 e p ciclos para completar devido a implementação do controle do barramento, que impede inanição. As instruções que implementam os comandos de entrada e seleção de alternativas somente completam quando a mensagem com o dado é recebida pelo nó. No melhor caso, a instrução completa em um ciclo (a mensagem já estava na caixa de mensagens do nó). No caso geral, seja w ≥ 0 o tempo de espera pelo processador fonte para executar o comando de saída correspon- dente e m ≥ 1 o tempo esperando a transmissão da mensagem na rede, w e m contabilizados com base na execução da instrução. O tempo de execução em ciclos é:
w + m (12)
A instrução que implementa o comando de saída somente completa quando o nó recebe uma mensagem ACK. Seja y ≥ 0 o tempo de espera no processador destino para a execução do comando de entrada ou seleção de alternativas correspondente, y é contabilizado a partir da
Programa 18: Implementação do comando de saída no modelo base
EQV_output ( to , data ) { Sem_P (& rndz [ processor_id ()]; /* bloqueia */ mbox [ to ] = data ; /* envia dado */ Sem_V (& rndz [ to ]); /* libera quem vai ler o dado */ }
Para o comando de seleção de alternativas é necessário empregar um mecanismo de tipo round-robin para garantir justiça na seleção das alternativas. Uma variável global rridx[NPROC], um vetor de inteiros de 32 bits de inicializado com − 1 , usada para implementar o round-robin. Cada elemento do vetor armazena o índice que o processo utiliza no vetor de alternativas. Esse índice é incrementado a cada chamada ao comando de seleção de alternativas. Após incrementar o índice do round-robin , o processo atual efetua um comando de entrada para o processo indicado no elemento do vetor de alternativas. Ao completar, o comando retorna o ID do processo que foi selecionado.
Programa 19: Implementação do comando de seleção de alternativas no modelo base
EQV_alt (* clauses , ncl , * data ) { int id ;
id = processor_id ();
rridx [ id ] = ( rridx [ id ] + 1) % ncl ; /* atualiza round - robin */
Para emular as operações do semáforo utilizando os comandos CSP, são necessários dois processos: PROC_P e PROC_V. Os processos que utilizam o semáforo devem executar C2_output(PROC_P, DONTCARE) e C2_output(PROC_V, DONTCARE) para realizar as operações P() e V() respectivamente. O valor do output é descartado, por isso o valor enviado aos pro- cessos (DONTCARE) é irrelevante. O processo PROC_P é responsável por manter o valor do semáforo. Seja o vetor de inteiros de 32 bits procs[] de tamanho np o vetor de alternativas contendo os IDs dos processos que utilizam o semáforo. Após inicializar o semáforo, o processo executa as seguintes ações infinitamente: (i) caso valor do semáforo seja maior que 0, seleciona um processo que executou a operação P() e decrementa o valor; e (ii) caso valor do semáforo igual a 0, aguarda o processo PROC_V para incrementar o valor. O pseudocódigo no Programa 20 indica a implementação.
Espaço em branco proposital.
Programa 20: Implementação da operação P() no modelo CSP
PROC_P ( initval , * procs , np ) { int val ; int ret , ign ;
val = initval ; /* inicializa sem á foro */
for (;;) { if ( val > 0) { ret = C2_alt ( procs , np , & ign ); /* seleciona processo */ -- val ; /* decrementa sem á foro */ } else { C2_input ( PROC_V ); /* aguarda incremento */ ++ val ; /* incrementa sem á foro */ } } }
O processo PROC_V é responsável por indicar o incremento do valor do semáforo. A execução do processo consiste em atender uma solicitação de algum dos processos que executaram a operação V() e indicar o incremento ao processo PROC_P. O pseudocódigo no Programa 21 indica a implementação.
Programa 21: Implementação da operação V() no modelo CSP
PROC_V (* procs , np ) { int ret , ign ;
for (;;) { ret = C2_alt ( procs , np , & ign ); /* seleciona processo */ C2_output ( PROC_P , INCREMENT ); /* incremento do sem á foro */ } }
5 Código dos experimentos
Foram implementados 6 programas na linguagem C, cada um deles sendo uma solução para os problemas clássicos que nos interessam. Como o simulador representa um sistema altamente idealizado, para aproximar a simulação de um sistema realista, há um laço de espera ocupada, no qual os processos efetuam trabalho computacional, para além das operações de comunicação e sincronização.
O produtor e o consumidor se comunicam através de um vetor de números inteiros de 32 bits que implementa uma fila circular com MAXELEM = 16 elementos. O processo produtor produz um conjunto de inteiros representados em 32 bits, na faixa de 0 a MAXVAL - 1, MAXVAL = 1024. Cada inteiro é inserido no buffer pelo produtor e após produzir todos os valores, o processo produtor finaliza.