• Nenhum resultado encontrado

Figura 7.9 Thread utilizando o bloco de operações Test-and'Set.

No documento [PT] SILBERSCHATZ - Sistemas Operacionais (páginas 150-155)

z - n instrução Swap, d e f i n i d a no m é t o d o swap( ) na F i g u r a 7 . 1 0 , o p e r a c o m o c o n t e ú d o de duas palavras; como a i n s t r u ç ã o Test-And-Set, ela é executada de f o r m a a t ó m i c a .

Sc a m á q u i n a s u p o r t a r a i n s t r u ç ã o Swap, a e x c l u s ã o m ú t u a p o d e r á ser o b t i d a da seguinte m a n e i r a : T o - dos o s t h r e a d s c o m p a r t i l h a m u m o b j e t o l o c k d a classe HardwareData, q u e é i n i c i a l i z a d o c o m o f a l s e . A l é m

disso, cada t h r e a d t a m b é m t e m um o b j e t o l o c a l key da classe HardwareData. A e s t r u t u r a do t h r e a d T, aparece na Figura 7 . 1 1 .

public s t a t i c void swap(HardwareData s. Hardware Data b) { HardwareData temp = new HardwareData(a.get( ) ) ;

a.set(b.get( ) ) ; b.set(temp.get( ) ) ; I

F i g u r a 7.10 Instrução Swap.

HardwareData lock • new HardwareData(false); HardwareData key = new HardwareData(true); while (true) { k e y . s e t ( t r u e ) ; do { HardwareSoluti on.swap(1ock,key); } while (key.get( ) " t r u e ) ; c r í t i c a l S e c t i o n ( ) ; l o c k . s e t ( f a l s e ) ; nonCriticalSection( ) ; F i g u r a 7.11 T h r e a d u t i l i z a n d o a i n s t r u ç ã o Swap.

130 • Sistemas Operacionais

• Semáforos

As soluções para o problema de seção crítica apresentadas na Seção 7.3 não são fáceis de generalizar para problemas mais complexos. Para superar essa dificuldade, podemos usar uma ferramenta de sincroniza- ção, denominada semáforo. Um semáforo S é uma variável inteira q u e , além da inicialização, só é acessa- da através de duas operações-padrão: P e V. Essas operações receberam seus nomes de termos holandeses: P de proberen, que significa testar, e Vde verhogen, que significa incrementar*. As definições de P c V são as seguintes: P(S) { while S 0 ; // no-op

i

V(S) { )

As modificações no valor inteiro do semáforo nas operações P e V devem ser executadas de forma in- divisível. Ou seja, quando um thrcad modifica o valor do semáforo, nenhum o u t r o rhread pode modifi- car simultaneamente o valor do mesmo semáforo. Além disso, no caso de P(S),o teste do valor inteiro de S(S S 0) e de sua possível modificação (S--) também deve ser executado sem interrupção. Na Seção 7.5.2, veremos como essas operações podem ser implementadas; primeiro vamos ver como os semáforos podem ser usados.

7.5.1 Uso

Os sistemas operacionais muitas vezes fazem a distinção entre semáforos de contagem e semáforos binários. O valor de um semáforo de contagem pode variar de modo irrestrito. O valor de um semáforo binário pode variar apenas entre 0 ç 1.

A estratégia geral para usar um semáforo binário no controle do acesso a uma seção crírica é a seguinte (vamos supor que o semáforo seja inicializado em 1):

Semaphore S; P(S);

criticalSectionf );

V(S);

Assim, podemos usar o semáforo para controlar o acesso à seção crítica em um processo ou rhread. Uma so- lução generalizada para múltiplos threads aparece no programa Java da Figura 7.12. Cinco threads separados são criados, mas apenas um pode estar na sua seção crítica de cada vez. O semáforo sçm que é compartilhado por todo* os threads controla o acesso à seção crítica.

Os semáforos de contagem podem ser usados para controlar o acesso a determinado recurso em quanti- dade limitada (finita). O semáforo é inicializado com o número de recursos disponíveis. Cada thrcad que de- sejar usar um recurso executaria uma operação P no semáforo (decrementando, assim, a contagem). Quando um thrcad libera um recurso, ele realiza uma operação V (incrementando a contagem). Quando a contagem para o semáforo chegar a 0, todos os recursos estarão sendo utilizados. Os threads subsequentes que deseja- rem utilizar um recurso ficarão bloqueados até que a contagem fique maior do que 0.

Sincronização de Processos • 131

public class Worfcer çxtends Thread

t

public Worker(Semaphore s, String n) { name » n; sem = s ;

I

p u b l k void run{ ) { while ( t r u e ) ( &em.P( ); System.out.println(name * " is in c r i t i c a i s e c t i O n . " ) ; R u n n e r . c r i t k a l S e c t i o n ( ) ; iem.V( ); System.out.println(name * " is out of c r i t i c a i s e x t i o n . " ) ; Runner.nonCrlticalSection( ); ) ) p r i v a t e Semaphore sem; p r i v a t e String name; \ p u b l i c class FirstSemaphore

I

public s t a t i c void main{String args[ ]) ( Semaphore sem * new Semaphore(l); Workerf ] bees » new Worker[5]; f o r ( i n t i = 0; i < 5; i**)

bees[i] • new Worker(sem, "Worker " + (new t n t e g e r ( i ) ) . t o S t r i n g ( ) );

for (int i • 0; i < 5; i+*) bees[i].start( ) ;

1

)

Figura 7.12 Sincronização utilizando semáforos. 7 . 5 . 2 I m p l e m e n t a ç ã o

A principal desvantagem das soluções de exclusão mútua da Scção 7.2, e da definição de semáforo apresenta- da aqui, é* que todas exigem espera ocupada. Enquanto um processo estiver em sua seção crítica, qualquer ou- tro processo que tentar entrar em sua seção crítica deverá fazer um laço contínuo no código de entrada. Esse laço contínuo é claramente um problema em um sistema de multiprogramação, no qual uma única CPU é compartilhada entre muitos processos. A espera ocupada desperdiça ciclos de CPU que outros processos po- deriam usar de fornia produtiva. Esse tipo de semáforo também é chamado de spinlock (porque o processo "gira" enquanto espera um bloco de operações). Os sphúocks são úteis nos sistemas com várias processado- res. A vantagem de um spinlock é que não há necessidade de troca de contexto quando um processo precisa esperar em um bloco de operações, e uma troca de contexto pode demorar muito. Assim, quando os blocos de operações devem ser mais curtos, os spinlocks são úteis.

Para superar a necessidade da espera ocupada, podemos modificar a definição das operações de semáforo P e V. Quando um processo executar uma operação P e descobrir que o valor do semáforo não é positivo, ele deverá esperar. No entanto, em vez de utilizar a espera ocupada, o processo poderá se bloquear. A operação de bloco de operações coloca um processo cm uma fila de espera associada com o semáforo, e o estado do processo é passado para o estado de espera. Em seguida, O controle é transferido para o escalonador de CPU, que seleciona outro pro- cesso para executar.

Um processo bloqueado, esperando em um semáforo S, deve ser retomado quando algum outro processo executar uma operação V. O processo é reiniciado por uma operação wakeup (acordar), que muda o processo do estado de espera para o estado de pronto. O processo é então colocado na fila de processos prontos. (A CPU pode ou não ser passada do processo cm execução para o novo processo pronto, dependendo do algo- ritmo de escalonamento de CPU.)

1.Í2 • Sistemas Operacionais

Para implementar semáforos com essa definição, definimos um semáforo como sendo um valor inteiro e uiua lista de processos. Quando um processo precisa esperar em um semáforo, ele é acrescentado à lista de processos daquele semáforo. Uma operação V remove um processo da lista de processos em espera, e acorda esse processo.

As operações de semáforo agora podem ser definidas como:

P(S)Í

value--; if(value < 0){

add this process to list

block; } } V(S){ value + + ; if(value 0)í

remove ã process P from list

wakeup(P); )

}

A operação block suspende o processo que o chama. A operação wakeup (P) retoma a execução de um pro- cesso bloqueado P. Essas duas operações são fornecidas pelo sistema operacional como chamadas ao sistema básicas.

Observe que, embora em uma definição clássica de semáforos com espera ocupada o valor do semáforo nunca seja negativo, essa implementação pode ter valores de semáforo negativos. Se o valor do semáforo for negativo, seu valor absoluto é o número de processos esperando naquele semáforo. Esse fato é resultado da troca da ordem entre o decremento e o teste na implementação da operação P.

A lista de processos cm espera pode ser facilmente implementada por um ponteiro em cada bloco de con- trole de processo (PCB). Cada semáforo contém um valor inteiro e um ponteiro para uma lista de PCBs. Uma forma de adicionar e remover processos da lista, que garante uma espera limitada, seria utilizar uma fila FIFO, na qual o semáforo contenha ponteiros de início e fim para a fila. Em geral, no entanto, a lista poderá usar qualquer estratégia de enfileiramento. O uso correto de semáforos não depende de alguma estratégia es- pecífica para as listas de semáforo.

O aspecto crítico dos semáforos é que eles são executados de forma atómica. Devemos garantir que dois processos não podem executar as operações P e V no mesmo semáforo ao mesmo tempo. Essa situação cria uni problema de seção crítica, que pode ser resolvido de duas maneiras.

Em um ambiente monoprocessador (ou seja, onde só exista uma CPU), podemos simplesmente inibir in- terrupções durante a execução das operações P e V. Assim que as interrupções forem inibidas, as instruções

dos diferentes processos não podem ser intercaladas. Apenas o processo em execução no momento executa, até que as interrupções sejam habilitadas novamente e o escalonador possa retomar o controle-

Em um ambiente multiprocessador, no entanto, a inibição de interrupções não funciona. As instruções de processos distintos (executando em diferentes processadores) podem ser intercaladas de forma arbitrária. Sc o hardware não fornecer instruções especiais, podemos empregar qualquer solução de software correia para o problema de seção crítica (Seção 7.2), na qual as seções críticas consistirão nas operações P e V.

Não eliminamos completamente a espera ocupada com essa definição das operações P c V. Em vez disso, passamos a espera ocupada para as seções críticas dos aplicativos. Além disso, limitamos espera ocupada ape- nas às seções críticas das operações P e V, c essas seções são curtas (se adequadamente codificadas, não devem ter mais do que 10 instruções). Assim, a seção crítica quase nunca é ocupada, e a espera ocupada ocorre rara- mente c sõ por um período limitado. Uma situação inteiramente diferente existe com aplicativos, cujas seções críticas talvez sejam longas (minutos ou mesmo horas) ou estejam quase sempre ocupadas. Nesse caso, a espe- ra ocupada é extremamente ineficiente.

S i n c r o n i z a ç ã o de Processos • 133

7.5.3 Deadlocks e paralisação

A implementação de um semáforo com uma fila de espera poderá resultar cm uma situação na qual dois ou mais processos estão esperando indefinidamente por um evento que somente pode ser causado por um dos processos em espera. O evento em questão c* a execução de uma operação V. Quando tal estado é alcançado, esses processos são considerados em estado de impasse ou deadloek.

Como ilustração, consideramos um sistema que consiste cm dois processos, P„ e P„ cada qual acessando dois semáforos, S e Q, iniciadas em I:

Po Pi P(S); P(0); P ( Q ) ; P ( S ) ; V(S); V(Q); V(Q); V(S); p u b l i c c l a s s BoundedBuffer { p u b l i c BoundedBufferf ) { / / o b u f f e r e s t á i n i c i a l m e n t e v a z i o count • 0; i n • 0 ; out • 0 ; b u f f e r • new Object[BUFFER_SIZE];

mutex • new Semaphore(l);

empty • new Semaphore(8UFFER_SIZE); f u l l = new Semaphore(O);

I

// o p r o d u t o r e o consumidor chamam e s t e método para • c o c h i l a r " p u b l i c s t a t i c v o i d napping( ) { i n t sleepTime - ( i n t ) ( N A P T I M E * Math.random( ) ) ; t r y < Thread.s1eep(sIeepTime"1000); I c a t c h ( I n t e r r u p t e d E x c e p t Í o n e) 1 ) 1 p u b l i c v o i d e n t e r ( 0 b j e c t i t e m ) ( //Figura 7.14 \ p u b l i c Object removei ) | //Figura 7.15 I p r i v a t e s t a t i c f i n a l i n t NAP_TIME • 5 ; p r i v a t e s t a t i c f i n a l i n t BUFFER_SIZE • 5 ; p r i v a t e O b j e c t f ] b u f f e r ; p r i v a t e i n t c o u n t , i n , o u t ;

/ / mutex c o n t r o l a o acesso a c o u n t , i n , out

private Semaphore mutex; private Semaphore empty;

p r i v a t e Semaphore f u l l ;

134 • Sistemas Operacionais

public void enter(Object item) { empty.P( );

mutex.P( );

//adiciona um item ao buffer *+count;

buffer[in] • item;

in » (in + 1) % BUFFERSIZE; if (count « BUFFER_SIZE)

System.out.println("Producer Entered " * item + • Buffer RIU.");

else

System.out.println{"Producer Entered " • item + • Buffer Size •" • count);

mutex.Vt ); full.VÍ >;

1

Figura 7.14 O método enter( ).

Vamos supor que P„ executa P(S) e /', executa P(Q). Quando P0 executar P(0), ele deve esperar até que P, execu-

te V (0). Da mesma forma, quando P, executar P(S), ele deve esperar até que P„ execute V (S). Como essas operações V( ) não podem ser executadas, P„ e P, estão em estado de deadlock.

Dizemos que um conjunto de processos está cm estado de deadlock quando todo processo no conjunto estiver esperando por um evento que só pode ser causado por outro processo no conjunto. Os eventos que enfocaremos aqui são a aquisição e a liberação de recursos; no entanto, outros tipos de eventos poderão re- sultar em deadlocks, conforme demonstrado no Capítulo 8. Nesse capítulo, descrevemos vários mecanismos para lidar com o problema de deadlocks.

Outro problema relacionado com deadlocks é o bloco de operações indefinido ou starvation (estagna- ção) - uma situação na qual os processos esperam indefinidamente no semáforo. O bloco de operações inde- finido poderá ocorrer se adicionarmos e removermos processos da lista associada a um semáforo usando a ordem LIFO.

No documento [PT] SILBERSCHATZ - Sistemas Operacionais (páginas 150-155)

Outline

Documentos relacionados