Após a realização da engenharia de
domínio partimos para a etapa de implemetação. A nossa
intenção é a implementação da família
e membros definidos anteriormente, ou seja, a família Synchronizer e
os membros Semaphore, Mutex e Condition em uma arquitetura IA32.
O primeiro passo foi a definição do arquivo de configuração
dessa família:
synchronizer.xml
Este arquivo é utilizado na automatização de parte da implementação,
proporcionando a geração de novos arquivos, através do
programa epos-newabs, que definem a família e seus membros.
Considerações sobre Implementação
No trabalho de implementar uma solução para as condições de corrida e obter assim a atomicidade desejada, algumas condições devem ser satisfeitas:
1. Exclusão Mútua: Se um processo pi estiver executando em sua seção crítica , então nenhum outro processo pode estar executando sua seção crítica.
2. Progresso: Se nenhum processo estiver executando sua seção crítica e existir algum processo pronto para entrar em sua seção crítica, então somente os processos que não estão executando seu código não-crítico poderão concorrer por ela, e esta decisão não pode ser adiada indefinidamente.
3. Espera Limitada: existe um limite no número de vezes que os processos podem entrar em suas seções críticas depois que um processo requereu entrada em sua seção crítica e antes de ser atentido.
A etapa seguinte foi a implementação do membro Mutex. Como descrito
na engenharia de domínio, Mutex realiza operações de bloqueio
e desbloqueio para possibilitar a exclusão mútua. Para garantir
essa característica é necessário que a operação
de verificação e alteração da variável de
controle do acesso da região crítica seja atômica. Uma operação
terminada sem ser interrompida é chamada uma operação atômica.
Esta atomicidade é possível através de uma isntrução
especial da CPU chamada TSL (Test and Set Lock). Na arquitetura IA32 a instrução
que faz o TSL é a
BTS
(Bit Test and Set).
Código da função TSL
Um problema existente nesta implementação utilizando a abordagem
TSL para proteger a região crítica é que ela usa um loop
de espera-ocupada (busy-waiting). Enquanto a região crítica não
está disponível, o processo gira em um loop, gastando tempo da
CPU, esperando por seu retorno à região crítica[4].
Código da implementação do membro Mutex:
mutex.cc
Na seqüência do desenvolvimento partimos para a implementação
do membro Semaphore. Um semáforo é um objeto com duas operações
básicas: P e V[4]. Possui ainda uma variável contador e, para
evitar a espera-ocupada, um semáforo deve ter associado uma fila de processos
(usualmente uma FIFO)[5].
O sistema inicializa o número de processos que podem compartilhar o recurso
ao mesmo tempo. Se um processo executa uma operação P quando
a variável contador está com valor zero, o processo é adicionado
na fila. Quando outro processo incrementa a variável contador através
da operação V, e há processos na fila, um deles é
removido da fila e passa a executar[5].
Dúvidas que surgiram durante a implementação:
Como identificar estes processos?
É necessário saber a quantidade de processos?
O código da implementação do membro Semaphore:
semaphore.cc
condition.cc
Referências Bibliográficas
[1] -
TANNEMBAUM, ANDREW S., WOODHULL, ALBERT S.: Sistemas Operacionais:
Projeto e Implementação. 2 ed. Porto Alegre: Bookman, 2000.
[2] -
SILBERSCHATZ, ABRAHAM., GALVIN, PETER B., GAGNE, GREG.: Operating
Systems Concepts. 5 ed. John Wiley & Sons, Inc., 1998.
[3] -
ANTÔNIO AUGUSTO FRÖHLICH:
Application-Oriented Operating Systems, Sankt Augustin: GMD - Forschungszentrum
Informationstechnik, 2001, 200 p.
[4] -
http://cs.smith.edu/~thiebaut/ArtOfAssembly/CH19/CH19-12.html
[5] - Wikipedia:
http://en.wikipedia.org/wiki/Semaphore_%28programming%29