Sei sulla pagina 1di 33

Fundamentos de Sistemas

Operacionais
Aula 5: Exclusão Mútua

Diego Passos
Última Aula
Programação Concorrente

Programas compostos por mais de um processo ou thread.


Pode trazer benefícios:
Simplificar o projeto de certos programas.
Aumentar o desempenho.
Também pode trazer problemas:
Aumento de complexidade no gerenciamento dos
processos (threads).
Acesso simultâneo a recursos compartilhados.
Região crítica.
Região Crítica

Trecho de código que manipula recursos compartilhados.


Ordem de execução dos processos (threads) é imprevisível.
Pode causar comportamento errado do programa.
Resultados inválidos.
Perda de informação.
Etc.
Soluções:
Usar operações atômicas.
Impedir acessos simultâneos à região crítica.
Exclusão mútua.
Operações Atômicas
Definição

É uma operação indivisível no tempo.


Não pode ser interrompida.
Ou é totalmente executada, ou nem começa a
execução.
Exemplos: instruções de máquina.
Processador verifica se há interrupções no início do
ciclo de execução.
Se não há, a instrução é executada completamente.
Caso contrário, a execução da instrução sequer
começa.
Operações Atômicas e Seções Críticas

Se todo código da seção crítica pudesse ser executado


atomicamente, não haveria condição de corrida.
Processos podem executar em qualquer ordem.
Jamais dois processos estariam "dentro" da seção
crítica ao mesmo tempo.
Não há risco de inconsistência nos dados.
Problema:
A seção crítica pode ser complexa demais para ser
executada em uma única instrução de máquina.
Operações Atômicas: Possível
Implementação
É preciso garantir que um processo execute toda uma
região crítica sem ser interrompido.
Solução: desabilitar interrupções.
Instrução CLI.
Exemplo:

__asm__("cli");
/* Aqui começa a Região Crítica. */
x = x + 1;
y = x + 15;
...
/* Fim da Região Crítica. */
__asm__("sti");
Operações Atômicas: Possível
Implementação (mais)
Método traz uma série de problemas:
Instrução CLI é privilegiada.
Processos de aplicação em geral rodam em modo
usuário.
Sem interrupções, não há chamadas de sistema.
Qualquer E/S tem que ser feito pelo processo do
usuário.
Propenso a falhas e brechas de segurança:
E se o programador esquece (ou decide não
colocar) a instrução STI no final?
Não funciona em máquinas multiprocessadas.
Em geral, utilizado apenas em sistemas simples.
Sistemas embarcados.
Exclusão Mútua
Definição

Significa que no máximo um processo estará na Seção


Crítica por vez.
Se um processo A já está na Seção Crítica e B quiser
entrar, B precisa esperar que A saia.
Um processo na Seção Crítica pode ser interrompido.
Processos que não compartilham o recurso podem ser
executados.
Processos que compartilham, mas não estão na seção
crítica podem ser executados.
Ao voltar, processo encontrará o recurso compartilhado
no mesmo estado.
Requisitos

Uma solução para exclusão mútua deve prover/permitir:


Exclusão mútua.
Que, caso não haja outro processo na seção crítica, um
processo não seja impedido de entrar.
Que um processo não seja indefinidamente impedido de
entrar na sua seção crítica (starvation).
A solução não deve depender de:
Ordens específicas de execução de processos.
Velocidades específicas de execução dos processos.
Protocolos de Acesso

Primeiro tipo de solução para prover exclusão mútua.


Totalmente baseados em software.
Nenhum suporte do hardware é necessário.
Exemplos:
Alternância.
Algoritmo de Peterson.
Algoritmo de Dekker.
Algoritmo de Eisenberg.
Algoritmo de Lamport (Bakery).
Alternância
Inicialização:

turn = 0; // Pode ser 1.

Processo 0: Processo 1:

while(turn == 1) { while(turn == 0) {
// Espera ocupada. // Espera ocupada.
} }
// Início da região crítica. // Início da região crítica.
... ...
// Fim da região crítica. // Fim da região crítica.
turn = 1; turn = 0;
Alternância: Problemas

Força uma sequência de execução entre os processos.


P0, P1, P0, P1, ...
Um processo mais rápido pode ter que esperar um longo
tempo até que outro chegue a seção crítica para passar a
vez.
Muito ineficiente.
A vez de um processo pode nunca chegar, se o outro
não executar sua seção crítica.
Algoritmo de Peterson (1981)
Inicialização:

flag[0] = 0;
flag[1] = 1;
turn = 0; // Pode ser 1.

Processo 0: Processo 1:
flag[0] = 1; flag[1] = 1;
turn = 1; turn = 0;
while(flag[1] == 1 && turn == 1) { while(flag[0] == 1 && turn == 0) {
// Espera ocupada. // Espera ocupada.
} }
// Início da região crítica. // Início da região crítica.
... ...
// Fim da região crítica. // Fim da região crítica.
flag[0] = 0; flag[1] = 0;
Desvantagens dos Protocolos de
Acesso
Soluções complexas.
Principalmente quando há mais de dois processos
envolvidos.
Fazem espera ocupada (busy-waiting).
Pouco eficiente.
Processador fica ocupado inutilmente.
Soluções com Suporte do
Hardware
Spin-Lock: Intuição

Podemos controlar a entrada na seção crítica com uma


variável lock.
A Seção está trancada quando há um processo nela.
A Seção está destrancada, caso contrário.
Exemplo:

// Tentar entrar na seção crítica.


while (lock == 1) {};
lock = 1;
// Início da Seção Crítica
...
// Fim da Seção Crítica.
lock = 0;
Spin-Lock: Intuição (mais)

Para funcionar, a variável lock precisa ser compartilhada


entre os vários processos.
Problema:
Se lock é um recurso compartilhado, a manipulação
desta variável está susceptível a condições de corrida.
O algoritmo não resolve o problema, apenas cria outro.
Solução:
Suporte do hardware.
Processador permite que a manipulação da variável lock
seja atômica.
Spin-Lock: Implementação

Utiliza uma instrução específica.


Varia de processador para processador:
Test-and-set-lock (TSL), Swap (SWP), Compare-on-
Store (COS),...
Exemplo: instrução Swap(reg, mem).
Recebe dois argumentos: um registrador e um endereço
de memória.
Troca o valor do registrador com o valor armazenado no
endereço de memória.

[mem] -> aux;


reg -> [mem];
aux -> reg;
Spin-Lock: Implementação (mais)

Utilizando a instrução Swap, o código fica:

// Tentar entrar na seção crítica.


do {
reg = 1;
swap(reg, lock);
} while(reg == 1);

// Início da Seção Crítica


...
// Fim da Seção Crítica.
lock = 0;
Spin-Lock: Análise
O código sempre escreve 1 na variável lock.
Se o valor de lock já era 1 (há um processo na região
crítica):
lock continua 1.
reg passa a ser 1.
Repetição continua.
Se o valor de lock era 0 (não há processo na região crítica):
lock continua 1.
reg passa a ser 0.
Repetição para.
Quando um processo fica na repetição aguardando, ele faz
uma espera ocupada (consome inutilmente recursos do
processador).
Quando há vários processos disputando a entrada na
região crítica, não há garantias de que um dado processo
conseguirá entrar.
Semáforos

É um tipo de dado especial:


Composto por um valor inteiro e por uma fila de
processos.
Aceita dois tipos de operação:
P(S): decrementa e testa. Também chamada de down.
Decrementa o valor do semáforo e testa se é menor
que zero.
Se for, processo é bloqueado e colocado na fila do
semáforo.
V(S): incrementa, desbloqueia. Também chamada de
up.
Incrementa o valor do semáforo.
Se há um processo na fila, desbloqueia.
Semáforos: Exemplos de Uso

Proteção de acesso à região crítica.


Antes de entrar na região crítica, processo realiza a
operação P no semáforo correspondente.
Ao sair, ele realiza a operação V no semáforo
correspondente.

Processo 0: Processo 1:

P(S); P(S);
x = x + 1; x = x + 1;
V(S); V(S);
Semáforos: Exemplos de Uso (mais)

Sincronização entre processos.


Suponha que o processo 0 só possa executar
determinada ação, após o processo 1 chegar a um
determinado ponto da sua execução.
Utiliza-se um semáforo S de controle, inicializado com 0.
Processo 0: Processo 1:

/* Aguarda liberação de P1. */ /* Chega ao ponto em que


P0 pode ser liberado. */
P(S);
V(S);
/* Prossegue com a sua
execução. */ /* Prossegue com a sua
execução. */
Semáforos: Mutex vs. Contador.

Um mutex (ou semáforo binário) tem funcionamento


ligeiramente diferente.
Ele só pode assumir os valores 0 e 1.
Suficiente para garantir exclusão mútua.
Operações também são chamadas de lock() e
unlock().
Semáforos comuns são chamados semáforos
contadores.
Úteis quando o acesso a um dado recurso pode ser
dado a um conjunto de processos simultaneamente.
Semáforo Contador: Exemplo

Problema do Produtor-Consumidor
Inicialização:

s_mutex m = 1;
s_contador vagas = N;
s_contador dados = 0;
Produtor: Consumidor:
while (1) { while (1) {
P(vagas); P(dados);
P(m); P(m);
insere_dado(); remove_dado();
V(m); V(m);
V(dados); V(vagas);
} }
Semáforos: Implementação

Um semáforo também é um recurso compartilhado.


Suas operações, portanto, podem sofrer condições de
corrida.
É preciso proteger o acesso. Várias opções.
Desabilitar interrupções.
Pode não funcionar com vários processadores.
Spin-locks.
Protocolos de acesso.
Como P e V são operações curtas e executadas pelo SO,
problemas das soluções anteriores são mitigados.
Semáforos: Implementação (mais)

Quando um processo executa uma operação P() em um


semáforo com valor 0 (ou negativo), o SO:
Remove o processo da fila de aptos.
Insere o processo na lista de bloqueados.
Insere uma referência a ele na fila de processos do
semáforo.
Quando um processo executa uma operação V() em um
semáforo, o SO:
Verifica se há processos na fila do semáforo.
Remove o primeiro da fila (ou mais prioritário).
Remove o processo da lista de bloqueados.
Insere o processo na fila de aptos.
Semáforos: Análise

Garante exclusão mútua.


Não provoca starvation.
Fila de processos esperando garante que todos os
processos eventualmente serão atendidos.
Provê a funcionalidade de contador.
Útil em certos problemas.
Processo da aplicação não precisa realizar espera
ocupada.
Processo fica no estado bloqueado.
Revisão
Para Lembrar
Requisitos da exclusão mútua.
O que é starvation?
Utilização de operações atômicas.
Por que não é usado em geral (por aplicações)?
Desvantagens de desabilitar interrupções.
Protocolos de acesso.
Alternância.
Spin-locks.
Como são implementados.
Desvantagens.
Semáforos.
Mutex vs. Contador.
Operações.
Exemplos de uso.
Por que sua implementação pode utilizar os outros
métodos?

Potrebbero piacerti anche