Domínio
Em alguns
sistemas operacionais, processos que estão trabalhando juntos podem
compartilhar algum armazenamento comum que cada um pode ler e gravar. Situações
como essa, em que dois ou mais processos estão lendo ou gravando
alguns dados compartilhados, e o resultado final depende de quem executa
precisamente quando, são chamadas de condições de corrida[1].
Neste contexto,
o domínio a ser analisado é o sincronismo de processos em
sistemas operacionais. O sincronismo é necessário para um
adequado seqüenciamento quando estão presentes dependências
do tipo: se o processo A produz dados e o processo B os imprime, B tem de
esperar até que A tenha produzido alguns dados antes de começar
a imprimir[1].
Uma
possível família deste domínio seria o Sincronizador.
A Família Sincronizador
Para
evitar as condições de corrida é necessário
encontrar uma maneira de proibir que mais de um processo leia e grave os
dados compartilhados ao mesmo tempo. Em outras palavras, precisamos de exclusão
mútua. A escolha das operações primitivas apropriadas
para obter a exclusão mútua é uma questão de
projeto importante em qualquer sistema operacional[1].
A parte do programa em que a memória é compartilhada é
chamada de região critica.
Foram feitas várias propostas para obter a exclusão mútua.
Em 1965, Dijkstra sugeriu utilizar um novo tipo de variável chamado
semáforo. Ele propôs ter duas operações p
e v. A operação p em um semáforo verifica
se o valor é maior que 0. Se for, ele diminui o valor e simplesmente
continua. Se o valor for 0, o processo é colocado para dormir sem
completar o p[1].
Decidimos adotar como membro da Família Sincronizador o Semáforo.
Outro membro identificado é o Mutex. Mutex realiza operações
de bloqueio e desbloqueio para possibilitar a exclusão mútua.
Hoare (1974) e Brinch Hansen
(1975) propuseram uma primitiva de sincronização de nível
mais alto chamada monitor. Um monitor é uma coleção
de variáveis, de procedimentos e de estruturas de dados que são
agrupadas em um tipo especial de módulo ou de pacote. Os processos
podem chamar os procedimentos em um monitor sempre que quiserem, mas eles
não podem acessar diretamente as estruturas de dados internas do
monitor a partir de procedimentos declarados fora do monitor[1].
Os monitores são
uma construção de linguagem de programação,
de modo que o compilador sabe que eles são especiais e pode gerenciar
chamadas para procedimentos do monitor diferentemente de outras chamadas
de procedimento. O compilador deve reconhecê-los e arranjar a exclusão
mútua de algum modo. C, Pascal e a maioria das linguagens não
têm monitores, então, não é razoável esperar
que seus compiladores implementem qualquer regra de exclusão mútua[1].
Outro problema com monitores
e também com semáforos é que eles foram projetados
para resolver o problema de exclusão mútua em uma ou em mais
CPUs que têm acesso a uma memória comum. Colocando semáforos
na memória compartilhada e protegendo-os com instruções
TSL, pode-se evitar as condições de corrida. Quando um sistema
consiste em múltiplas CPUs, distribuídas, cada uma com sua
própria memória privada, conectadas por uma rede local, essas
primitivas tornam-se inaplicáveis[1].
Monitores separam o conceito
de exclusão mútua e sincronização condicional,
de forma que possuem lock mutuamente exclusivo e zero ou mais variáveis
condicionais para gerenciar o acesso concorrente aos dados.
Por ter características
que diferem das de Semaphore e Mutex, optamos por criar um
membro Monitor para a família.
Uma outra situação considerada ocorre quanto vários processos
esperam que uma determinada condição se torne positiva para
que possam continuar sua execução. Para modelar esta situação
existe o membro Condition: na necessidade de esperar que
uma determinada condição se torne verdadeira, um processo
invoca o método wait, que colocará este processo para dormir.
Todos os processos suspensos serão acordados pelo método broadcast.
Aspectos
Analisando
a família dos sincronizadores já existentes no EPOS e também
o domínio de sincronismo em sistemas operacionais, concordamos com
a presença dos aspectos Remote e Atomic nesta família.
Em um semáforo, por exemplo, verificar o valor, alterá-lo e,
possivelmente, ir dormir é tudo feito como uma ação
atômica.É garantido que uma vez que uma operação
de semáforo iniciou, nenhum outro processo acesse o semáforo
até que a operação tenha se completado ou tenha sido
bloqueada. Essa atomicidade é absolutamente essencial para resolver
problemas de sincronização e para evitar condições
de corrida, justificando assim a presença do aspecto
Atomic.
A execução remota de processos, assim como a invocação
remota de objetos, pode ser implementada modelando-se o mecanismo necessário
para tanto como um aspecto, visto que a característica "remoto" pode
existir ou não, e se aplica não só a sincronizadores
como também a outras entidades que tratam da coordenação
entre processos, como portas de comunicação.
O aspecto Remote provê um mecanismo
de invocação remota às abstrações pertencentes
à família dos sincronizadores, que quando utilizadas em cenários
distribuídos oferecem uma solução centralizada para
coordenar processos de aplicações paralelas. Porém
este mecanismo não é adequado para coordenação
distribuída de processos e pode ser um gargalo numa aplicação
paralela, visto que o nó onde o sincronizador se encontra torna-se
um ponto por onde todos os processos têm de se comunicar. Porém,
a modelagem de um novo aspecto para a família que resolva este problema
é complexa e está fora do escopo deste trabalho.
Referências Bibliográficas
[1] - TANNEMBAUM, ANDREW S., WOODHULL, ALBERT S.: Sistemas Operacionais:
Projeto e Implementação. 2 ed. Porto Alegre: Bookman, 2000.
[2] - ANTÔNIO AUGUSTO FRÖHLICH:
Application-Oriented Operating Systems, Sankt Augustin: GMD - Forschungszentrum
Informationstechnik, 2001, 200 p.