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
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;
}
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;
}
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
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
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.
Formato dos programas
loop
secção não crítica
fechar() // pré-protocolo
secção crítica
abrir() // pós-protocolo
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.
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.
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
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
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)
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
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ê ?
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
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
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ê ?
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;
}
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
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;
}
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
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;
}
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
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
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
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)
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
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
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;
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
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
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;
}
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
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)
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
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
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;
}
Diagrama de Estado dos
Processos
Execução Em Executável BloqueadoSeleccionado
pelo
Despacho
Retirado pelo
Despacho
Bloqueado
num
Trinco Lógico
Desbloqueado
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
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 ?
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.
Diagrama de Estado dos
Processos
Execução Em Executável BloqueadoSeleccionado
pelo
Despacho
Retirado pelo
Despacho
Bloqueado
num
semáforo
Desbloqueado
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
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
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
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
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);
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
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
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
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);
}
Metodologia de Resolução de
Problemas de Sincronização
Assinalar acontecimento
Proc
isemEvent = criar_semaforo(0);
void esperar_acontecimento(){
esperar (semEvent);
}
Proc
jvoid assinalar_acontecimento(){
assinalar (semEvent);
}
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
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);
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)
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,
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
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
Interblocagem
fechar (trinco) fechar (trinco)Processo 1
assinalar (sem) esperar (sem)Processo 2
abrir (trinco) abrir (trinco) . . . . . . . . . .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
Interblocagem
esperar (semA) esperar (semA)Processo 1
esperar (semB) esperar (semB)Processo 2
assinalar (semA)assinalar (semB) assinalar (semA)
assinalar (semB) . . . . . . . . .
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
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
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
Problemas de Sincronização
Produtor-Consumidor
Leitores-Escritores
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);
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
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;
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.
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]);
}
}
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)
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]); } }
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); } }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);
}
}
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
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”
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
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
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
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
Sincronização em Windows
Secções críticas em Windows
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; }
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; }
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);
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
Sincronização em Linux
Secções críticas em Linux
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);
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);
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);
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);
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
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
#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"); }
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");
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 é
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
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
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
Diagrama de Estado
dos Processos
Em Execução
Executável Bloqueado Suspenso
Suspender Seleccionado pelo Despacho Preempção Desbloquear Acordar Acordar Suspender Suspender