• Nenhum resultado encontrado

Tipos de Sincronização

N/A
N/A
Protected

Academic year: 2021

Share "Tipos de Sincronização"

Copied!
105
0
0

Texto

(1)
(2)

Tipos de Sincronização

„

Competição de vários processos por

um recurso

„

Exclusão mútua

„

Cooperação entre processos

„

Produtores-consumidores

„

Leitores-escritores

(3)
(4)

Problema da Exclusão

Mútua

int levantar_dinheiro (ref *conta, int valor)

{

if (conta->saldo >= valor) {

conta->saldo = conta->saldo – valor;

} else valor = -1

return valor;

}

(5)

Problema da Exclusão

Mútua (à lupa)

„

As instruções C correspondem normalmente a várias

instruções máquina

int levantar_dinheiro (ref *conta, int valor)

{

if (conta->saldo >= valor) {

/* conta->saldo = conta->saldo – valor; */

mov R1, conta->saldo

mov R2, valor

sub R1, R2

mov conta->saldo, R1

} else valor = -1

return valor;

}

(6)

Problema da Exclusão

Mútua (à lupa)

int levantar_dinheiro (ref *conta,

int valor)

{

if (conta->saldo >= valor) {

mov R1, conta->saldo

mov R2, valor

sub R1, R2

mov conta->saldo, R1

} valor = -1;

return valor;

}

int levantar_dinheiro (ref *conta,

int valor)

{

if (conta->saldo >= valor) {

mov R1, conta->saldo

mov R2, valor

sub R1, R2

mov conta->saldo, R1

} else valor = -1;

return valor;

}

P1

P2

Interrupção provoca

comutação de processos

Saldo inicial = 100

Valor = 50

Saldo inicial = 100

2 levantamentos de 50

Saldo final = 50!!

SO volta a escalonar P1

(7)

Secção crítica

(intuitivamente)

int levantar_dinheiro (ref *conta, int valor)

{

if (conta->saldo >= valor) {

/* conta->saldo = conta->saldo – valor; */

mov R1, conta->saldo

mov R2, valor

sub R1, R2

mov conta->saldo, R3

} else valor = -1

return valor;

}

Secção Crítica:

não pode ser

interrompida

(8)

Secção Crítica

„

Propriedade de exclusão mútua:

as instruções de uma região crítica

de dois ou mais processos não

podem ser intercaladas em

nenhuma execução.

(9)

Formato dos programas

loop

secção não crítica

fechar() // pré-protocolo

secção crítica

abrir() // pós-protocolo

(10)

Propriedades

„

Um processo pode bloquear-se na secção não crítica, sem

interferir com os outros processos.

„

Não pode haver interblocagem (deadlock). Se alguns

processos estão a tentar entrar na região crítica, então

pelo menos um deles irá consegui-lo alguma vez no

futuro.

„

Não pode haver míngua (starvation). Se um processo

inicia o pré-protocolo, então alguma vez no futuro irá

conseguir o acesso à região crítica.

„

Na ausência de contenção, se um único processo quiser

entrar na região crítica, consegui-lo-á e de uma forma

eficiente.

(11)

Deadlock, livelock,

starvation

„

Deadlock (interblocagem): Para qualquer

execução, nenhum processo consegue

fazer progresso. Todos os processos

ficam bloqueados à espera uns dos

outros.

„

Livelock: Existe pelo menos uma

execução para a qual nenhum processo

consegue fazer progresso.

„

Starvation (míngua): Um dos processos

não consegue fazer progresso, enquanto

os outros conseguem.

(12)

Interblocagem

„

Propriedade estável: não há forma dos

processos saírem da interblocagem.

Normalmente é necessário terminar um ou mais

processos

„

Fácil de detectar (“pára tudo”), impossível de

inverter

„

Ex:

Proc A está à espera de Proc B

Proc B está à espera de Proc C

Proc C está à espera de Proc A

A

B

C

(13)

Míngua

„

Propriedade instável: os processos podem fazer

progresso, mas há pelo menos uma combinação

de acontecimentos que, se se repetirem

consecutivamente, faz com que um deles nunca

consiga fazer progresso

„

Difícil de detectar pois qualquer alteração pode

fazer com que se “esconda”… mas fica à espera

do pior momento para reaparecer (Lei de

Murphy)!

„

Ex.: processo nunca é atendido porque outros

(14)

Soluções para o problema

da Secção Crítica

„

Soluções algorítmicas

„

Soluções com suporte de hardware

„

Soluções oferecidas pelo Sistema

Operativo (com suporte de

hardware)

(15)

Soluções algorítmicas

„

O pré-protocolo (fechar()) e o

pós-protocolo (abrir()) usam apenas

instruções normais com leitura e

escrita na memória

„

Algoritmo Dekker

„

Algoritmo Petersen

(16)

Primeira tentativa

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

p1_fechar()

{

while (p2—quer—entrar);

p1—quer—entrar = TRUE;

}

p1_abrir()

{

p1—quer—entrar = FALSE;

}

p2_fechar()

{

while (p1—quer—entrar);

p2—quer—entrar = TRUE;

}

p2_abrir()

{

p2—quer—entrar = FALSE;

}

Não garante exclusão mútua. Porquê ?

(17)

Segunda tentativa

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

p1_fechar()

{

p1—quer—entrar = TRUE;

while (p2—quer—entrar);

}

p1_abrir()

{

p1—quer—entrar = FALSE;

}

p2_fechar()

{

p2—quer—entrar = TRUE;

while (p1—quer—entrar);

}

p2_abrir()

{

p2—quer—entrar = FALSE;

}

Interblocagem. Porquê ?

Processo 1

Processo 2

(18)

Terceira tentativa

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

p1_fechar()

{

p1—quer—entrar = TRUE;

while (p2—quer—entrar);

}

p1_abrir()

{

p1—quer—entrar = FALSE;

}

p2_fechar()

{

p2—quer—entrar = TRUE;

while (p1—quer—entrar) {

p2-que-entrar = FALSE;

while(p1—quer—entrar);

p2—quer—entrar = TRUE;

}

}

p2_abrir()

{

p2—quer—entrar = FALSE;

}

Míngua. Porquê ?

Processo 1

Processo 2

(19)

Quarta tentativa

int proc—prio = 1;

p1_fechar()

{

while (proc-prio == 2);

}

p1_abrir()

{

proc-prio = 2;

}

p2_fechar()

{

while (proc-prio == 1);

}

p2_abrir()

{

proc-prio = 1;

}

Não garante nem a 1º nem a 4ª propriedade. Porquê ?

(20)

Algoritmo Dekker

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

int proc—prio = 1;

p1_fechar()

{

p1—quer—entrar = TRUE;

a1: while (p2—quer—entrar) {

a2: if (proc—prio == 2) {

p1—quer—entrar = FALSE;

while (proc—prio == 2)

;

p1—quer—entrar = TRUE;

}

}

}

p1_abrir()

{

p1—quer—entrar = FALSE;

proc—prio = 2;

}

p2_fechar()

{

p2—quer—entrar = TRUE;

a1: while (p1—quer—entrar) {

a2: if (proc—prio == 1) {

p2—quer—entrar = FALSE;

while (proc—prio == 1)

;

p2—quer—entrar = TRUE;

}

}

}

p2_abrir()

{

p2—quer—entrar = FALSE;

proc—prio = 1;

}

(21)

Algoritmo Dekker

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

int proc—prio = 1;

p1_fechar()

{

p1—quer—entrar = TRUE;

a1: while (p2—quer—entrar) {

a2: if (proc—prio == 2) {

p1—quer—entrar = FALSE;

while (proc—prio == 2)

;

p1—quer—entrar = TRUE;

}

}

}

p1_abrir()

{

p1—quer—entrar = FALSE;

proc—prio = 2;

}

p2_fechar()

{

p2—quer—entrar = TRUE;

a1: while (p1—quer—entrar) {

a2: if (proc—prio == 1) {

p2—quer—entrar = FALSE;

while (proc—prio == 1)

;

p2—quer—entrar = TRUE;

}

}

}

p2_abrir()

{

p2—quer—entrar = FALSE;

proc—prio = 1;

}

•P1 anuncia que quer entrar

•Verifica se P2 quer entrar. Se não quiser, P1 entra •Se P2 quer entrar, mas P1 for o mais prioritário, P2 há-de retirar a intenção de entrar e P1 entra •Senão, P1 indica que não quer entrar (para deixar P2 entrar) e fica em espera activa qté que P2 saia

•Ao sair, indica que já não está na secção crítica •O algoritmo é totalmente simétrico, P2 faz

exactamente o mesmo que P1 (basta trocar o “1” pelo “2” e vice-versa)

•P1 volta a anunciar que quer entrar e repete o pré-protocolo

(22)

Algoritmo de Peterson

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

int proc—prio = 1;

p1_fechar()

{

a1: p1—quer—entrar = TRUE;

b1: proc—prio = 2;

c1: while (p2—quer—entrar&&

proc—prio == 2)

;

}

p1_abrir()

{

p1—quer—entrar = FALSE;

}

p2_fechar()

{

a2: p2—quer—entrar = TRUE;

b2: proc—prio = 1;

c2: while (p1—quer—entrar &&

proc—prio == 1)

;

}

p2_abrir()

{

p2—quer—entrar = FALSE;

}

(23)

Algoritmo de Peterson

int p1—quer—entrar = FALSE;

int p2—quer—entrar = FALSE;

int proc—prio = 1;

p1_fechar()

{

a1: p1—quer—entrar = TRUE;

b1: proc—prio = 2;

c1: while (p2—quer—entrar&&

proc—prio == 2)

;

}

p1_abrir()

{

p1—quer—entrar = FALSE;

}

p2_fechar()

{

a2: p2—quer—entrar = TRUE;

b2: proc—prio = 1;

c2: while (p1—quer—entrar &&

proc—prio == 1)

;

}

p2_abrir()

{

p2—quer—entrar = FALSE;

}

•P1 anuncia que quer entrar

•P1 estabelece P2 como mais prioritário •Se P2 não quer entrar, P1 entra

•Se P2 quer entrar e for o prioritário, P1 fica em espera activa até que P2 saia

•Se P1 e P2 quiserem entrar ao mesmo tempo, o último coloca o outro como prioritário

(24)

Algoritmo de Bakery, N=2

int n1 = 0;

int n2 = 0;

p1_fechar()

{

n1 = 1;

n1 = n2+1;

while (n2 && n1 > n2)

;

}

p1_abrir()

{

n1 = 0;

}

p2_fechar()

{

n2 = 1;

n2 = n1+1;

while (n1 && n2 >= n1)

;

}

p2_abrir()

{

n2 = 0;

}

(25)

Mais do que 2 processos

„

Algoritmo de Bakery (Lamport)

„

Cada processo que quer entrar num secção crítica

escolhe um inteiro, maior do que qualquer outro inteiro

escolhido.

„

O processo com o menor número inteiro entra, os

outros esperam.

„

Os empates são resolvido pela identificação do

processo

„

Garante exclusão mútua

„

Garante todas as propriedades enunciadas

anteriormente

„

Favorece ligeiramente os processo com menor

(26)

Algoritmo de Bakery

int choosing[N]; // Inicializado a FALSE

int ticket[N]; // Inicializado a 0

fechar(int i)

{

int j;

choosing[i] = TRUE;

ticket[i] = 1 + maxn(ticket);

choosing[i] = FALSE;

for (j=0; j<N; j++) {

if (j==i) continue;

while (choosing[j]) ;

while (ticket[j] &&

(ticket[j] < ticket[i])||

(ticket[i] == ticket[j] && j < i)))

;

}

}

abrir(int i)

{

ticket[i] = 0;

}

•Pi verifica se tem a menor senha de todos os Pj •Se Pj estiver a escolher uma senha, espera que termine

•Neste ponto, Pj ou já escolheu uma senha, ou ainda não escolheu

•Se escolheu, Pi vai ver se é menor que a sua

•Se não escolheu, vai ver a de Pi e escolher uma senha maior

•Pi indica que está a escolher a senha

•Escolhe uma senha maior que todas as outras •Anuncia que escolheu já a senha

•Se o ticket de Pi for menor, Pi entra

•Se os tickets forem iguais, entra o que tiver o menor identificador

(27)

Conclusões sobre as

Soluções Algorítmicas

„

Complicadas de usar directamente por

programas

„

Não funcionam dentro do sistema

operativo (ex: rotinas de interrupção)

„

Só contemplam espera activa, sendo

muito ineficientes em esperas

prolongadas (ex: esperar que uma tecla

seja premida)

„

Solução: Introduzir instruções hardware

(28)

Soluções com suporte do

hardware

„

O pré-protocolo e pós-protocolo

usam instruções especiais oferecidas

pelos processadores para facilitar a

implementação da sincronização

„

Inibição de interrupções

„

Exchange (xchg no Intel IA)

(29)

Exclusão mútua com inibição de

interrupções

int mascara;

fechar()

{

mascara = mascarar_int();

}

abrir()

{

repoe_int(m);

}

•As interrupções são mascaradas (inibidas) na entrada da secção crítica

•Durante a secção crítica, como não há forma de interromper o processo, é garantido

que as instruções se executam atomicamente

•Este mecanismo só pode ser utilizado dentro do sistema operativo em secções

críticas de muito curta duração

•Inibição das interrupções impede que se executem serviços de sistema (I/O,

etc)

•Se o programa se esquecer de chamar abrir(), as interrupções ficam inibidas e o

sistema fica parado

•Não funciona em multiprocessadores

•Na saída da secção crítica é reposta a máscara de interrupções que existia antes da secção crítica

(30)

O problema da atomicidade

com multiprocessadores

CPU

CPU

CPU

Cache

Cache

Cache

Cache

Memoria

IO

bus

CPU

P2 na secção crítica ERRO!! P1 na secção crítica ERRO!!

Instante 3

P2 inibe as suas interrupções e entra na secção crítica

Instante 2

P1 inibe as suas interrupções e entra na secção crítica

Instante 1

P2

P1

(31)

Exclusão mútua com

exchange

int trinco = FALSE;

fechar()

{

li R1, trinco ;

guarda em R1 o endereço do trinco

li R2, #1

l1: exch R2, 0(R1); faz o

exchange

bnez R2, l1

;

se não estava a 0 entra, senão repete

}

abrir()

{

trinco = FALSE;

(32)

Exclusão mútua com

exchange

int trinco = FALSE;

fechar()

{

li R1, trinco ;

guarda em R1 o endereço do trinco

li R2, #1

l1: exch R2, 0(R1); faz o

exchange

bnez R2, l1

;

se não estava a 0 entra, senão repete

}

abrir()

{

trinco = FALSE;

}

•O exchange corresponde às seguintes operações

executadas de forma atómica

•lw Rt, 0(R1) •sw 0(R1), R2 •mov R2, Rt

•A atomicidade é conseguida mantendo o bus trancado entre o Load e o Store

•R2 contém o valor que estava no trinco •Se era 0, o trinco estava livre

•O processo entra

•O exchange deixou o trinco trancado •Se não era 0, significa que o trinco estava trancado

•O processo fica em espera activa até que encontre o trinco aberto

(33)

Exchange em

multiprocessadores

CPU

CPU

CPU

Cache

Cache

Cache

Cache

Memoria

IO

bus

CPU

P2 verifica que o trinco está trancado e fica em espera activa P1 entra secção crítica

Instante 3

P2 tenta fazer exchange mas bloqueia-se a tentar obter o bus P1 completa exchange e tranca a

secção crítica

Instante 2

P1 inicia exchange e tranca o bus

Instante 1

P2

P1

(34)

Exclusão mútua com

test-and-set

int trinco = FALSE;

fechar()

{

li R1, trinco ;

guarda em R1 o endereço do trinco

l1: tst R2, 0(R1) ;

faz o test-and-set

bnez R2, l1

;

se não estava set entra, senão repete

}

abrir()

{

trinco = FALSE;

}

(35)

Exclusão mútua com

test-and-set

int trinco = FALSE;

fechar()

{

li R1, trinco ;

guarda em R1 o endereço do trinco

l1: tst R2, 0(R1) ;

faz o test-and-set

bnez R2, l1

;

se não estava set entra, senão repete

}

abrir()

{

trinco = FALSE;

}

•O test-and-set corresponde às seguintes operações

executadas de forma atómica

•lw R2, 0(R1) •sw 0(R1), #1

•A atomicidade é conseguida mantendo o bus trancado entre o Load e o Store

•R2 contém o valor que estava no trinco •Se era 0, o trinco estava livre

•O processo entra

•O test-and-set deixou o trinco trancado •Se não era 0, significa que o trinco estava trancado

•O processo fica em espera activa até que encontre o trinco aberto

(36)

Conclusões sobre as Soluções

com Suporte do Hardware

„

Oferecem os mecanismos básicos para a

implementação da exclusão mútua,

mas...

„

Não podem ser usadas directamente por

programas em modo utilizador (ex:

inibição de interrupções)

„

Só contemplam espera activa, sendo

muito ineficientes em esperas

prolongadas (ex: esperar que uma tecla

seja premida)

(37)

Soluções oferecidas pelo

Sistema Operativo

„

Analisando as soluções anteriores (algorítmica e com

suporte de hardware), conclui-se que…

„

… tem que haver uma solução melhor para os

programadores!

„

Solução:

„

O Sistema Operativo oferece objectos de sincronização

Trinco Lógico: secções críticas

Semáforo: sincronização genérica

„

Programas utilizador sincronizam-se usando apenas Trincos

Lógicos e Semáforos

Interface simples

Semântica clara

Implementação eficiente e segura, assegurada pelo sistema

operativo

„

A implementação dos objectos de sincronização no Sistema

Operativo usa um misto das soluções algorítmicas e do

suporte de hardware

(38)

Trinco Lógico

„

Objecto do Sistema Operativo para a implementação de

secções críticas em programas utilizador

„

Oferece duas operações:

„

fechar (trinco):

chamada pelo programa quando quer

entrar na secção crítica

Se a secção crítica estiver aberta, o processo entra na secção

crítica e fecha-a

Se a secção crítica estiver fechada, o processo bloqueia-se até

ela ser aberta; nessa altura, entra na secção crítica e fecha-a

„

abrir (trinco):

Chamada pelo programa quando quer sair

da secção crítica

Abre a secção crítica

(39)

Trinco lógico

„

O trinco lógico serve exclusivamente para implementar exclusão mútua

„

Um processo só pode fazer abrir(t) se tiver previamente feito

fechar(t)

„

O processo que faz abrir(t) tem que ser o mesmo que fez fechar(t)

Um trinco é criado sempre no estado ABERTO

No início da secção crítica, os processos têm que chamar

fechar(t)

Se o trinco estiver FECHADO, o processo espera que ele fique aberto. Quando ficar ABERTO, passa-o ao estado FECHADO. Estas operações executam-se

atomicamente.

No fim da secção crítica, os processos têm que chamar

abrir(t)

Passa o trinco para o estado ABERTO e desbloqueia um processo que esteja à sua espera em fechar()

Secção

Crítica:

trinco_t t = ABERTO;

int levantar_dinheiro (ref *conta, int valor)

{

fechar(t);

if (conta->saldo >= valor) {

conta->saldo = conta->saldo – valor;

} else valor = -1

abrir(t);

return valor;

}

(40)

Diagrama de Estado dos

Processos

Execução Em Executável Bloqueado

Seleccionado

pelo

Despacho

Retirado pelo

Despacho

Bloqueado

num

Trinco Lógico

Desbloqueado

(41)
(42)

Cooperação entre Processos

„

Vários processos executam em

conjunto uma ou mais tarefas, nas

quais

„

Competem por recursos

„

Indicam uns aos outros a:

Ausência/existência de recursos

(43)

Exemplo de Cooperação entre

Processos: produtor - consumidor

/* ProdutorConsumidor com semaforos */ int buf[N];

int prodptr=0, consptr=0; produtor()

{

while(TRUE) {

int item = produz(); buf[prodptr] = item; prodptr = (prodptr+1) % N; } } consumidor() { while(TRUE) { int item; item = buf[consptr]; consptr = (consptr+1) % N; consome(item); } } Produtor Produtor Consumidor Consumidor prodptr consptr

Que acontece se não houver itens no buffer ?

Que acontece se o buffer estiver cheio ?

(44)

Semáforos

„

Objecto do sistema operativo para sincronização

genérica

„

Um semáforo é conceptualmente composto por

„

Contador

„

Fila de processos bloqueados no semáforo

„

Primitivas

„

criar_semaforo():

cria um semáforo e inicializa o contador

„

esperar(s):

bloqueia o processo chamador se o contador for

menor ou igual a zero; senão decrementa o contador

„

assinalar(s):

se houver processos bloqueados, liberta um;

senão, incrementa o contador

„

Todas as primitivas se executam atomicamente

„

esperar()

e assinalar() podem ser chamadas por

processos diferentes.

(45)

Diagrama de Estado dos

Processos

Execução Em Executável Bloqueado

Seleccionado

pelo

Despacho

Retirado pelo

Despacho

Bloqueado

num

semáforo

Desbloqueado

(46)

Semáforos –

Estrutura de Dados

Semáforo s

Tabela de

Semáforos do Sistema

Fila de processos

bloqueados

Contador

Descritores dos processos

bloqueados no semáforo s

(47)

Semáforos

„

Existem muitas variantes de semáforo

„

Genérico: assinalar() liberta um processo qualquer

da fila

„

FIFO: assinalar() liberta o processo que se bloqueou

há mais tempo

„

Semáforo com prioridades: o processo especifica em

esperar()

a prioridade, assinalar() liberta os

processos por ordem de prioridades

„

Semáforo com unidades: as primitivas esperar() e

assinalar()

permitem especificar o número de

unidades a esperar ou assinalar

(48)

Semáforos: especificação do

semáforo genérico

typedef struct {

int contador;

queue_t

fila_procs;

} semaforo_t;

esperar

contador > 0

contador--

bloqueia processo

S

N

assinalar

processos

bloqueados

desbloqueia processo

contador++

S

N

criar_semaforo(n)

cria estrutura dados

(49)

Exclusão Mútua com Semáforos

semaforo_t sem = 1;

int levantar_dinheiro (ref *conta, int valor)

{

esperar(sem);

if (conta->saldo >= valor) {

conta->saldo = conta->saldo – valor;

} else valor = -1

assinalar(sem);

return valor;

}

No início da secção crítica, todos os processos têm que chamar esperar()

No fim da secção crítica, todos os processos têm que chamar assinalar()

Um semáforo para exclusão mútua tem que ser inicializado com 1 unidade

„

O semáforo é mais genérico que o trinco lógico, por isso pode ser usado

para garantir exclusão mútua

„

Mas…

„

Mais ineficiente que o trinco lógico

„

O programador tem que garantir o uso simétrico de esperar() e

(50)

Produtor-Consumidor com

Semáforos

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; esperar(pode_cons); fechar(trinco); item = buf[consptr]; consptr = (consptr+1) % N; abrir(trinco); assinalar(pode_prod); consome(item); } } Produtor Produtor Consumidor Consumidor prodptr consptr int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

(51)

Produtor-Consumidor com

Semáforos

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; esperar(pode_cons); fechar(trinco); item = buf[consptr]; consptr = (consptr+1) % N; abrir(trinco); assinalar(pode_prod); consome(item); } } Produtor Produtor Consumidor Consumidor prodptr consptr int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

Cada semáforo representa um recurso:

•pode_produzir: espaços livres, inicialmente N •pode_consumir: itens no buffer, inicialmente 0

Espera que haja 1 item no buffer

Assinala que há 1 espaço livre no buffer Espera que haja 1

espaço livre no buffer

Assinala que há 1 item no buffer

(52)

Sincronização Genérica

„

Inclui

„

Secções críticas

„

Cooperação entre processos

„

Mecanismos de programação

„

Trincos Lógicos para as secções críticas

„

Semáforos para a cooperação entre processos

„

Problemas de Sincronização:

„

Produtor-Consumidor

„

Leitores-Escritores

(53)

Metodologia de Resolução de

Problemas de Sincronização

„

Cooperação entre processos

1.

Identificar os recursos (condições,

acontecimentos) partilhados entre processos

2.

Associar um semáforo a cada recurso

(condição, acontecimento)

3.

Inicializar o semáforo com o número de

recursos inicialmente existentes

4.

Chamar esperar(s) quando se tem que

garantir a existência do recurso associado ao

semáforo s

5.

Chamar assinalar(s) quando se produziu um

(54)

Metodologia de Resolução de

Problemas de Sincronização

Gestão de recursos

semGest = criar_semaforo(N);

void reservar_recurso(){

esperar (semGest);

algoritmo

}

void libertar_recurso(){

algoritmo

assinalar (semGest);

}

(55)

Metodologia de Resolução de

Problemas de Sincronização

Assinalar acontecimento

Proc

i

semEvent = criar_semaforo(0);

void esperar_acontecimento(){

esperar (semEvent);

}

Proc

j

void assinalar_acontecimento(){

assinalar (semEvent);

}

(56)

Metodologia de Resolução de

Problemas de Sincronização

„

Secções Críticas

1.

Identificar as secções críticas

1.

Uma secção crítica é uma secção de código onde se

lê e/ou escreve uma ou mais variáveis partilhadas

por vários processos

2.

Associar um trinco lógico a cada uma delas

1.

A mesma variável partilhada tem que estar

protegida sempre pelo mesmo trinco

2.

Usam-se trincos diferentes para proteger variáveis

completamente independentes, que podem ser

acedidas concorrentemente

3.

Chamar fechar(t) no início e abrir(t) no

(57)

Metodologia de Resolução de

Problemas de Sincronização

Exclusão mútua

trinco_t trinco;

fechar (trinco);

Secção critica

abrir (trinco);

Ou

mutex = criar_semaforo(1);

esperar (mutex);

Secção critica

assinalar (mutex);

(58)

Aplicação da Metodologia ao

Produtor-Consumidor

„

Cooperação entre processos

1.

Recursos: posições livres para os produtores produzirem;

itens no buffer para os consumidores consumirem

2.

Posições livres: semáforo pode_prod; Itens no buffer:

semáforo pode_cons

3.

Número inicial de posições livres: N; Número inicial de itens

no buffer: 0

4.

Produtores: têm que garantir a existência de uma posição

livre antes de produzirem => têm que chamar esperar no

semáforo correspondente (pode_prod); Consumidores: têm

que garantir a existência de um item no buffer antes de o

usarem => têm que chamar esperar no semáforo

correspondente (pode_cons);

5.

Produtores: produzem itens quando os colocam no buffer

=> chamam assinalar no semáforo correspondente

(pode_cons); Consumidores: produzem posições livres no

buffer => chamam assinalar no semáforo correspondente

(pode_prod)

(59)

Aplicação da Metodologia ao

Produtor-Consumidor

„

Secções críticas

1.

Variáveis partilhadas pelos vários processos: buffer,

prodptr

, consptr

2.

Proteger o acesso a essas variáveis com o trinco trinco

3.

Chamar fechar(trinco) antes de as ler ou escrever,

(60)

Produtor-Consumidor com

Semáforos

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; esperar(pode_cons); fechar(trinco); item = buf[consptr]; consptr = (consptr+1) % N; abrir(trinco); assinalar(pode_prod); consome(item); } } Produtor Produtor Consumidor Consumidor prodptr consptr int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

Cada semáforo representa um recurso:

•pode_produzir: espaços livres, inicialmente N •pode_consumir: itens no buffer, inicialmente 0

Espera que haja 1 item no buffer

Assinala que há 1 espaço livre no buffer Espera que haja 1

espaço livre no buffer

Assinala que há 1 item no buffer

(61)

Produtor-Consumidor com

Semáforos: erro comum #1

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; fechar(trinco); esperar(pode_cons); item = buf[consptr]; consptr = (consptr+1) % N; abrir(trinco); assinalar(pode_prod); consome(item); } } int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

Se não houver itens no buffer (situação inicial): • Consumidor fecha o trinco e bloqueia-se no semáforo (com o trinco fechado)

• Outros consumidores bloqueiam-se no trinco

• Os produtores vão tentar produzir (e

desbloquear os consumidores) mas encontram o trinco fechado e bloqueiam-se

• Interblocagem: os consumidores estão à espera dos produtores, e vice-versa

(62)

Interblocagem

fechar (trinco) fechar (trinco)

Processo 1

assinalar (sem) esperar (sem)

Processo 2

abrir (trinco) abrir (trinco) . . . . . . . . . .

(63)

Interblocagem

fechar (trinco) fechar (trinco)

Processo 1

assinalar (sem) esperar (sem)

Processo 2

abrir (trinco) abrir (trinco) Bloqueado Bloqueado

„

Solução: Um processo não se pode bloquear num semáforo dentro de

uma secção crítica

„

Chama esperar fora da secção crítica, ou

„

Liberta a secção crítica, faz esperar e volta a readquirir a secção

(64)

Interblocagem

esperar (semA) esperar (semA)

Processo 1

esperar (semB) esperar (semB)

Processo 2

assinalar (semA)

assinalar (semB) assinalar (semA)

assinalar (semB) . . . . . . . . .

(65)

Interblocagem

esperar (semA) esperar (semA)

Processo 1

esperar (semB) esperar (semB)

Processo 2

assinalar (semA)

assinalar (semB) assinalar (semA)

assinalar (semB)

Expira o quantum do processo

Bloqueado

Bloqueado

„

Solução: quando um processo precisa de adquirir vários semáforos ou

trincos, eles têm que ser adquiridos sempre pela mesma ordem

(66)

Produtor-Consumidor com

Semáforos: erro comum #2

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; esperar(pode_cons); fechar(trinco); item = buf[consptr]; consptr = (consptr+1) % N; assinalar(pode_prod); abrir(trinco); consome(item); } } int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

O consumidor assinala que há espaço para produzir antes de libertar o trinco

• O produtor vai tentar produzir mas encontra o trinco fechado e bloqueia-se • De seguida executa-se o consumidor que liberta o trinco

• Agora o produtor já se pode executar

• Não há interblocagem mas existem 2 comutações de processos

(67)

Produtor-Consumidor com

Semáforos: versão optimizada

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco_prods); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco_prods); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; esperar(pode_cons); fechar(trinco_cons); item = buf[consptr]; consptr = (consptr+1) % N; abrir(trinco_cons); assinalar(pode_prod); consome(item); } } Produtor Produtor Consumidor Consumidor prodptr consptr int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco_prods, trinco_cons;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

•Consumidores e produtores têm secções críticas diferentes pois não partilham variáveis •Permite produzir e consumir ao mesmo tempo em partes diferentes do buffer

(68)

Problemas de Sincronização

„

Produtor-Consumidor

„

Leitores-Escritores

(69)

Produtor-Consumidor com

Semáforos

produtor() {

while(TRUE) {

int item = produz();

esperar(pode_prod); fechar(trinco); buf[prodptr] = item; prodptr = (prodptr+1) % N; abrir(trinco); assinalar(pode_cons); } } consumidor() { while(TRUE) { int item; esperar(pode_cons); fechar(trinco); item = buf[consptr]; consptr = (consptr+1) % N; abrir(trinco); assinalar(pode_prod); consome(item); } } Produtor Produtor Consumidor Consumidor prodptr consptr int buf[N];

int prodptr=0, consptr=0;

trinco_t trinco;

semaforo_t pode_prod = criar_semaforo(N), pode_cons = criar_semaforo(0);

(70)

Leitores-Escritores com

Semáforos

„

Problema

„

Pretende-se gerir o acesso a uma estrutura de

dados partilhada em que existem duas classes

de processos:

Leitores – apenas lêem a estrutura de dados

Escritores – lêem e modificam a estrutura de dados

„

Condições

Os escritores só podem aceder em exclusão mútua

Os leitores podem aceder simultaneamente com

outro leitores mas em exclusão mútua com os

escritores

Nenhuma das classes de processos deve ficar à

mingua

(71)

Leitores-Escritores com

Semáforos

inicia_leitura() { fechar(m); if (em_escrita || escritores_espera > 0) { leitores_espera++; abrir(m); esperar(leitores); fechar(m); leitores_espera--; if (leitores_espera > 0) assinalar(leitores); } nleitores++; abrir(m); } acaba_leitura() { fechar(m); nleitores--;

if (nleitores == 0 && escritores_espera > 0)

assinalar(escritores);

abrir(m);

}

int nleitores=0;

boolean_t em_escrita=FALSE;

int leitores_espera=0, escritores_espera=0;

inicia_escrita() { fechar(m); if (em_escrita || nleitores > 0) { escritores_espera++; abrir(m); esperar(escritores); fechar(m); escritores_espera--; } em_escrita = TRUE; abrir(m); } acaba_escrita() { fechar(m); em_escrita = FALSE; if (leitores_espera > 0) assinalar(leitores); else if (escritores_espera > 0) assinalar(escritores); abrir(m); }

semaforo_t leitores=0, escritores=0;

(72)

Jantar dos Filósofos

„

Cinco Filósofos estão reunidos para

filosofar e jantar spaghetti. Para comer

precisam de dois garfos, mas a mesa

apenas tem um garfo por pessoa.

„

Condições:

Os filósofos podem estar em um de três estados :

Pensar; Decidir comer ; Comer.

O lugar de cada filósofo é fixo.

Um filósofo apenas pode utilizar os garfos

imediatamente à sua esquerda e direita.

(73)
(74)

Jantar dos Filósofos com

Semáforos, versão #1

„

INCORRECTA, pode conduzir a interblocagem

semaforo_t garfo[5] = {1, 1, 1, 1, 1};

filosofo(int id)

{

while (TRUE) {

pensar();

esperar(garfo[id]);

esperar(garfo[(id+1)%5]);

comer();

assinalar(garfo[id]);

assinalar(garfo[(id+1)%5]);

}

}

(75)

Interblocagem

„

Esta solução pode conduzir a interblocagem

„

Filósofo 0 adquire garfo 0 e perde o processador (expira o seu quantum ou há

um processo mais prioritário para se executar)

„

Filósofo 1 adquire o garfo 1 e perde o processador (idem)

„

Filósofo 2 adquire o garfo 2 e perde o processador (idem)

„

Filósofo 3 adquire o garfo 3 e perde o processador (idem)

„

Filósofo 4 adquire o garfo 4, tenta adquirir o garfo 1 e bloqueia-se (está

adquirido pelo filósofo 1)

„

Filósofos 0, 1, 2 e 3 voltam a executar-se e ficam bloqueados à espera,

respectivamente, dos garfos 1, 2, 3 e 4

„

Interblocagem: os processos estão todos à espera uns dos outros e

nunca sairão dessa situação

„

Existe uma cadeia circular de processos na qual cada processo possui um ou

mais recursos que são pretendidos pelo processo seguinte na cadeia

„

Razão: Os processos necessitam de adquirir vários semáforos, mas eles

não foram adquiridos sempre mesma ordem

„

Filósofo 0: esperar(0), esperar(1)

„

Filósofo 4: esperar(4), esperar(0)

(76)

Jantar dos Filósofos com

Semáforos, versão #2

„

Adquirir os semáforos sempre pela mesma ordem (ordem

crescente de número de semáforo)

semaforo_t garfo[5] = {1, 1, 1, 1, 1}; semaforo_t sala = 4; filosofo(int id) { while (TRUE) { pensar(); if (i == 4) { esperar(garfo[(id+1)%5]); esperar(garfo[id]); } else { esperar(garfo[id]); esperar(garfo[(id+1)%5]); } comer(); assinalar(garfo[id]); assinalar(garfo[(id+1)%5]); } }

(77)

Jantar dos Filósofos com

Semáforos, versão #3

„

Usar um semáforo para representar a condição de bloqueio (e não o estado dos

garfos/filósofos),

„

Representar o estado dos garfos/filósofos com variáveis acedidas numa secção

crítica

#define PENSAR 0 #define FOME 1 #define COMER 2 #define N 5 int estado[N] = {0, 0, 0, 0, 0}; semaforo_t semfilo[N] = {0, 0, 0, 0, 0}; trinco mutex; Testa(int k){

if (estado[k] == FOME &&

estado[(k+1)%N] != COMER &&

estado[(k-1)%N] != COMER){

estado[k] = COMER; assinalar(semfilo[K]); } } filosofo(int id) { while (TRUE) { pensar(); fechar(mutex); estado[id] = FOME; Testa(id); abrir(mutex); esperar(semfilo[id]); comer(); fechar(mutex); estado[id] = PENSAR; Testa((id-1+N)%N); Testa((id+1)%N); abrir(mutex); } }

(78)

Jantar dos Filósofos com

Semáforos, versão #4

„

Limitar o acesso à “sala” a N-1 filósofos (fica sempre pelo menos

1 garfo livre)

semaforo_t garfo[5] = {1, 1, 1, 1, 1};

semaforo_t sala = 4;

filosofo(int id)

{

while (TRUE) {

pensar();

esperar(sala);

esperar(garfo[id]);

esperar(garfo[(id+1)%5]);

comer();

assinalar(garfo[id]);

assinalar(garfo[(id+1)%5]);

assinalar(sala);

}

}

(79)

Implementação do Trinco

Lógico no SO

„

Pode ser chamada pelos programas

utilizador através das chamadas sistemas

fechar()

e abrir()

„

Também usada para sincronização

interna do SO

„

Três técnicas de implementação:

„

Inibição de interrupções e bloqueio de

processos

„

Exchange e bloqueio de processos

„

Exchange e espera activa

(80)

Implementação do trinco lógico no SO

„

Inibição de interrupções e bloqueio de

processos

void fechar (trinco_t t)

{

int m = mascara_int();

if (t == FECHADO)

bloqueia processo

t = FECHADO;

repoe_int(m);

}

void abrir (trinco_t t)

{

int m = mascara_int();

t = ABERTO;

if (processos bloqueados em t) {

desbloqueia um processo

}

repoe_int(m);

}

trinco_t t = ABERTO;

• Inibição das interrupções garante exclusão mútua

• Processo é bloqueado no trinco, passando ao estado “bloqueado”

(81)

Implementação do trinco lógico no SO

„

Exchange e bloqueio de processos

void fechar (trinco_t t)

{

int m = FECHADO;

while (exchange(t,m) == FECHADO) {

bloqueia_processo(t);

m = FECHADO;

}

}

void abrir (trinco_t t)

{

t = ABERTO;

desbloqueia_processos(t);

}

trinco_t t = ABERTO;

• Instrução exchange(t,m) obtém o estado do trinco e fecha-o atomicamente • Se o trinco estava aberto, fica fechado e rotina termina

(82)

Implementação do trinco lógico no SO

(apenas para uso interno do SO)

„

Exchange e Espera Activa

void so_fechar (trinco_t t)

{

int m = FECHADO;

while (exchange(t,m) == FECHADO)

;

}

void so_abrir (trinco_t t)

{

t = ABERTO;

}

so_trinco_t t = ABERTO;

•Instrução exchange(t,m) obtém o estado do trinco e fecha-o atomicamente

•Espera activa só é possível se for usada apenas para sincronização interna do sistema operativo

•Eficiente em secções críticas pequenas (4-5 instruções C)

•Instruções load linked – store conditional podem ser usadas em vez do exchange

•Para abrir o trinco, basta posicionar a variável

•Os processos bloqueados estão em espera activa e vão imediatamente ver que o trinco já está aberto

(83)

Implementação do trinco lógico no SO

„

Optimização: exchange em modo

utilizador

void fechar (trinco_t t)

{

int m = FECHADO;

if (exchange(t,m) == ABERTO)

return;

_fechar(t);

}

void abrir (trinco_t t)

{

_abrir(t);

}

trinco_t t = ABERTO;

• Rotinas fechar() e abrir() são bibliotecas em modo utilizador, _fechar() e _abrir() as chamadas sistemas apresentadas anteriormente

• fechar() tenta fechar o trinco em modo utilizador com exchange. Se conseguir porque o trinco estava aberto, é muito eficiente (1 instrução máquina).

• Se o trinco estiver fechado, efectua a chamada sistema _fechar()

• Para abrir o trinco, efectua a chamada sistema _abrir() para desbloquear os processos que estejam bloqueados

(84)

Exclusão mútua em

multiprocessadores modernos:

load linked – store conditional (R4600)

int trinco = FALSE;

fechar()

{

li R1, trinco

l1: ll R2, 0(R1)

bnez R2, l1

li R2, #1

sc R2, 0(R1)

beqz R2, l1

}

abrir()

{

trinco = FALSE;

}

As instruções exchange e test-and-set não são adequadas aos multiprocessadores modernos:

•Deixam o bus trancado durante os dois acessos à

memória (load e store), que significam muitas dezenas de instruções

•Não se adequam à estrutura de pipeline dos processadores RISC

•Ocupam o bus na espera activa, impedindo que o processador que a obteve execute a secção crítica e a liberte

Solução: load linked, store conditional

•Em conjunto, funcionam como um “exchange” com controlo de concorrência optimista

•Load linked

•Faz um load

•Inicia o snoop ao bus, detectando acessos ao endereço do load

•Store conditional

•Se não houve acessos ao endereço do load, faz um store normal

•Se houve, falha

•Se o conjunto load linked – store conditional falhar, terá que ser repetido até funcionar

(85)

Sincronização em Windows

„

Secções críticas em Windows

(86)

Sincronização em Windows

Modo utilizador

„

Interlocked

LONG InterlockedExchangeAdd(PLONG plAddend, LONG lIncrement); LONG InterlockedExchange(PLONG plTarget, LONG lValue);

PVOID InterlockedExchangePointer(PVOID* ppvTarget, PVOID pvValue); // Secção crítica com InterlockedExchange

BOOL g_trinco = FALSE;

void levantar_dinheiro (ref *conta, int valor) {

while (InterlockedExchange (&g_trinco, TRUE) == TRUE) Sleep(0);

if (conta->saldo >= valor) {

conta->saldo = conta->saldo – valor; } else valor = -1

InterlockedExchange(&g_trinco, FALSE);

return valor; }

(87)

Sincronização em Windows

Modo utilizador – threads do mesmo

processo

„

Critical Section

VOID InitializeCriticalSection(PCRITICAL_SECTION trinco); VOID DeleteCriticalSection(PCRITICAL_SECTION trinco); VOID EnterCriticalSection(PCRITICAL_SECTION trinco); VOID LeaveCriticalSection(PCRITICAL_SECTION trinco); BOOL TryEnterCriticalSection(PCRITICAL_SECTION trinco); // Secção crítica com CRITICAL_SECTION

CRITICAL_SECTION trinco;

void levantar_dinheiro (ref *conta, int valor) {

EnterCriticalSection(trinco);

if (conta->saldo >= valor) {

conta->saldo = conta->saldo – valor; } else valor = -1

LeaveCriticalSection(trinco);

return valor; }

(88)

Sincronização em Windows

Modo núcleo

„

WaitFor object

DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds); DWORD WaitForMultipleObjects(DWORD dwCount, CONST HANDLE* phObjects,

BOOL fWaitAll, DWORD dwMilliseconds);

„

Mutex

HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa, BOOL fInitialOwner, PCTSTR pszName); HANDLE OpenMutex(DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName);

BOOL ReleaseMutex(HANDLE hMutex);

„

Semaphore

HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,

LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);

HANDLE OpenSemaphore(DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName); BOOL ReleaseSemaphore(HANDLE hsem, LONG lReleaseCount, PLONG plPreviousCount);

(89)

DeleteCriticalSection(&cs); CloseHandle(hmtx);

--Pode ser misturada com outros

objectos núcleo

Outros

LeaveCriticalSection(&cs); ReleaseMutex(hmtx);

Notify

TryEnterCriticalSection(&cs ); WaitForSingleObject (hmtx, 0);

Wait condicional

--WaitForSingleObject (hmtx, dwMilliseconds);

Wait com timeout

EnterCriticalSection(&cs); WaitForSingleObject (hmtx, INFINITE);

Wait

InitializeCriticalSection(& cs); hmtx= CreateMutex (NULL, FALSE, NULL);

Inicialização

CRITICAL_SECTION m; HANDLE m;

Declaração

Threads do mesmo processo Threads de qualquer processo

Âmbito

Eficiente (modo utilizador) Ineficiente (modo núcleo)

Performance

Critical Section

Mutex

(90)

Sincronização em Linux

„

Secções críticas em Linux

(91)

pthreads

#include <thread.h>

int thr_create(void * stack_base, size_t stack_size, void *(*start_routine)(void *)

void * arg, long flags, thread_t * new_thread); size_t thr_min_stack(void);

int thr_join(thread_t wait_for, thread_t *departed, void **status);

thread_t thr_self(void); void thr_yield(void);

int thr_suspend(thread_t target_thread); int thr_continue(thread_t target_thread); void thr_exit(void *status);

int thr_kill(thread_t target_thread, int sig); int thr_setconcurrency(int new_level); int thr_getconcurrency(void);

int thr_setprio(thread_t target_thread, int pri); int thr_getprio(thread_t target_thread, int *pri); int thr_keycreate(thread_key_t *keyp,

void (*destructor)(void *value)); int thr_setspecific(thread_key_t key, void *value); int thr_getspecific(thread_key_t key, void **valuep);

(92)

Mutexes

#include <synch.h>

int mutex_init(mutex_t *mp, int type,

void * arg);

int mutex_destroy(mutex_t *mp);

int mutex_lock(mutex_t *mp);

int mutex_trylock(mutex_t *mp);

int mutex_unlock(mutex_t *mp);

(93)

Read-Write Locks

int rwlock_init(rwlock_t *rwlp, int

type, void * arg);

int rwlock_destroy(rwlock_t *rwlp);

int rw_rdlock(rwlock_t *rwlp);

int rw_wrlock(rwlock_t *rwlp);

int rw_unlock(rwlock_t *rwlp);

int rw_tryrdlock(rwlock_t *rwlp);

int rw_trywrlock(rwlock_t *rwlp);

(94)

Semaphores

#include <synch.h>

int sema_init(sema_t *sp, unsigned

int count, int type, void * arg);

int sema_destroy(sema_t *sp);

int sema_wait(sema_t *sp);

int sema_trywait(sema_t *sp);

int sema_post(sema_t *sp);

(95)
(96)

IPC no SV - Semáforos

„

um semáforo consiste num

conjunto de contadores que só

podem ter valores positivos

„

criação de um semáforo:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semget (key, nsems, semflg)

key_t key;

int nsems, semflg;

„

“nsems” especifica o número de

contadores, que são inicializados

a zero na criação

„

operações sobre semáforos:

int semop (semid, sops, nsops)

int semid;

struct sembuf (*sops)[ ];

int nsops;

„

são executadas as nsops operações

definidas em sops e devolvido o

valor do último contador acedido

„

uma operação é definida, de

acordo com a estrutura sembuf,

por:

short sem_num;

/* numero */

short sem_op; /* operação */

short sem_flag;

/* flags */

„

segundo o valor de sem_op temos:

Ö

sem_op > 0

o valor de

sem_op é adicionado ao do

contador

Ö

sem_op < 0

o valor de

sem_op é adicionado ao do

contador; o processo pode

ficar bloqueado

Ö

sem_op = 0

o

processo é suspenso até o

valor do contador atingir

zero

(97)

Semáforos - Controlo

„

sintaxe:

int semctl (semid,

semnum, cmd, arg)

int semid, semnum, cmd;

union semun {

int val;

struct semid_ds *buf;

ushort *array;

} arg;

Ö

comandos possíveis:

Š IPC_STAT preenche arg.buf com estado actual

Š IPC_SET inicializa parâmetros a partir de buf.arg

Š IPC_RMID elimina o semáforo em causa

Š GETALL copia os valores dos contadores para

arg.array

Š SETALL

inicializa os valores a partir de

arg.array

Ö

comandos possíveis com semnum especificado:

Š GETVAL devolve o valor do contador

Š SETVAL inicializa o valor do contador

Š GETPID devolve o pid da última operação

Š GETNCNT devolve o número de processos

aguardando um valor não zero do contador

Š GETZCNT devolve número de processos

(98)

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define SEMMUTEX 5 #define SEMALOC 6 int mutex, semaloc;

void IniSem(int nmax) { union semun {

int val;

struct semid_ds *buf; ushort *array;} init;

if ((mutex = semget (SEMMUTEX, 1,0777|IPC_CREAT)) < 0) erro ("semget SEMMUTEX");

if ((semaloc = semget (SEMALOC, 1, 0777|IPC_CREAT)) < 0) erro("semget SEMALOC");

init.val = 1;

if (semctl (mutex, 0, SETVAL, init) < 0) erro("semctl mutex"); init.val = nmax;

if (semctl (semaloc, 0, SETVAL, init) < 0) erro("semctl SemAloc"); }

(99)

Distribuidor de Memória

(cont.)

void Esperar (key_t semid, int semnum, int uni) {

struct sembuf s;

s.sem_num = semnum;

s.sem_op = -uni;

s.sem_flg = 0;

if (

semop (semid, &s, 1)

< 0) erro ("semop

Esperar");

}

void Assinalar (key_t semid, int semnum, int uni) {

struct sembuf s;

s.sem_num = semnum;

s.sem_op = uni;

s.sem_flg = 0;

if (

semop (semid, &s, 1)

< 0) erro ("semop

Assinalar");

}

void EsperarMutex (key_t semid, int semnum) {

struct sembuf s;

s.sem_num = semnum;

s.sem_op = -1;

s.sem_flg = SEM_UNDO;

if (

semop (semid, &s, 1)

< 0) erro ("semop

EsperarMutex");

}

void AssinalarMutex (key_t semid, int semnum) {

struct sembuf s;

s.sem_num = semnum;

s.sem_op = 1;

s.sem_flg = SEM_UNDO;

if (

semop (semid, &s, 1)

< 0) erro ("semop

AssinalarMutex");

(100)

Interblocagem

„

Soluções preventivas

-procuram evitar

situações de interblocagem

„

Garantir que os recursos são adquiridos todos

pela mesma ordem

„

Requisitar todos os recursos que o processo

necessita no início da sua execução

„

Quando a aquisição de um recurso não é

(101)

Interblocagem

„

Soluções de Detecção de Interblocagem

„

detecção de um estado de interblocagem e

tentativa de resolução por libertação forçada

de recursos

„

Soluções de Eliminação do Estado de

Interblocagem

„

o algoritmo analisa a evolução do conjunto de

recursos que os processos possuem e procura

evitar o aparecimento de um estado de

(102)

Mecanismos Directos de

Sincronização

„

Objectivo

„

Suspender temporariamente a execução de

subprocessos

„

Limitações:

„

A sincronização directa implica o

conhecimento do identificador do processo

sobre o qual se pretende actuar.

„

Não se pode dar aos programas dos

utilizadores a possibilidade de interferirem

com outros utilizadores

„

A restrição habitual é apenas permitir o uso

de sincronização directa entre processos do

mesmo utilizador

(103)

Mecanismos Directos de

Sincronização

„

Funções que actuam directamente sobre

o estado dos processos

„

Suspender (IdProcesso)

„

Acordar (IdProcesso)

„

A função de suspensão é também

frequentemente utilizada para

implementar mecanismos de atraso

temporizado que funcionam como uma

auto suspensão

(104)

Diagrama de Estado

dos Processos

Em Execução

Executável Bloqueado Suspenso

Suspender Seleccionado pelo Despacho Preempção Desbloquear Acordar Acordar Suspender Suspender

Referências

Documentos relacionados

O Museu Digital dos Ex-votos, projeto acadêmico que objetiva apresentar os ex- votos do Brasil, não terá, evidentemente, a mesma dinâmica da sala de milagres, mas em

nhece a pretensão de Aristóteles de que haja uma ligação direta entre o dictum de omni et nullo e a validade dos silogismos perfeitos, mas a julga improcedente. Um dos

Mova a alavanca de acionamento para frente para elevação e depois para traz para descida do garfo certificando se o mesmo encontrasse normal.. Depois desta inspeção, se não

Equipamentos de emergência imediatamente acessíveis, com instruções de utilização. Assegurar-se que os lava- olhos e os chuveiros de segurança estejam próximos ao local de

Como analisa Junior (2013), Deng Xiaoping realizou uma reforma nas esferas política e econômica não só internamente, mas também como forma de se aproximar do ocidente. Como

No pavimento superior e na fachada principal do pavimento inferior mantém vergas retas, folhas de abrir externas em veneziana, folhas de abrir internas em madeira cega, sendo

Tal será possível através do fornecimento de evidências de que a relação entre educação inclusiva e inclusão social é pertinente para a qualidade dos recursos de

6 Consideraremos que a narrativa de Lewis Carroll oscila ficcionalmente entre o maravilhoso e o fantástico, chegando mesmo a sugerir-se com aspectos do estranho,