DEE355 – Sistemas Operacionais
Prof. Jéfer – jefer@ufpr.br
Objetivos:
Importância: Deadlocks podem travar sistemas críticos, afetando a produtividade e causando perdas financeiras. Prevenir e recuperar de deadlocks é essencial em ambientes multiusuário.
Definição: Deadlock é o impasse em que processos ficam bloqueados mutuamente, cada um segurando um recurso e aguardando outro, formando um ciclo (Silberschatz, 7.1).
Exemplo Prático: Duas transações de banco que mantêm locks em tabelas necessárias podem travar o sistema.
Transação | Possui | Espera |
---|---|---|
T1 | Tabela A | Tabela B |
T2 | Tabela B | Tabela A |
Legenda: T1 espera o recurso B e T2 espera o recurso A – ciclo formado.
Condições Necessárias (Coffman, 1971):
Observação: Se faltar alguma condição, o deadlock não ocorrerá.
Estratégias: Remover ou controlar uma das condições para evitar deadlock.
Processo | Recursos Solicitados | Ordem |
---|---|---|
P1 | R1, R2 | R1 → R2 |
P2 | R2, R3 | R2 → R3 |
Resultado: Com a ordem imposta, ciclos são evitados.
Método: Utiliza-se o grafo de alocação de recursos para identificar ciclos (Silberschatz, 7.5).
Algoritmo do Banqueiro: Embora seu foco seja a prevenção, o algoritmo do Banqueiro analisa estados seguros verificando se há ciclos no grafo de recursos. Se um ciclo for identificado, o sistema está em estado inseguro e um deadlock pode ocorrer.
Processo | Possui | Espera |
---|---|---|
P1 | R1 | R2 |
P2 | R2 | R3 |
P3 | R3 | R1 |
Legenda: Ciclo P1 → P2 → P3 → P1 detectado.
Métodos:
Exemplos: Bancos de dados e sistemas transacionais utilizam rollback para recuperar estados.
Problema: 5 filósofos e 5 garfos; cada filósofo precisa de 2 garfos para comer, podendo ocorrer deadlock e starvation.
Críticas: Starvation pode ocorrer se um filósofo nunca conseguir os garfos; sem coordenação, impasses são frequentes.
Soluções: Uso de um “garçom” (host) para controlar o acesso ou protocolos de requisição ordenada.
#include <semaphore.h>
sem_t forks[5];
void *philosopher(void *num) {
int i = *(int *)num;
sem_wait(&forks[i]);
sem_wait(&forks[(i+1)%5]);
printf("Filósofo %d comendo\n", i);
sem_post(&forks[(i+1)%5]);
sem_post(&forks[i]);
}
Simulação:
Definição: Leitores podem acessar simultaneamente; escritores precisam de acesso exclusivo. Sem controle, pode ocorrer starvation.
#include <semaphore.h>
sem_t mutex, wrt;
int readcount = 0;
void *reader(void *arg) {
sem_wait(&mutex);
readcount++;
if (readcount == 1) sem_wait(&wrt);
sem_post(&mutex);
printf("Lendo...\n");
sem_wait(&mutex);
readcount--;
if (readcount == 0) sem_post(&wrt);
sem_post(&mutex);
}
void *writer(void *arg) {
sem_wait(&wrt);
printf("Escrevendo...\n");
sem_post(&wrt);
}
Simulação:
Exercício: Simule um deadlock com 3 processos e 3 recursos e identifique o ciclo.
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
sem_t r1, r2, r3;
void *p1(void *arg) {
sem_wait(&r1); sleep(1); sem_wait(&r2);
printf("P1 executando\n");
sem_post(&r2); sem_post(&r1);
return NULL;
}
void *p2(void *arg) {
sem_wait(&r2); sleep(1); sem_wait(&r3);
printf("P2 executando\n");
sem_post(&r3); sem_post(&r2);
return NULL;
}
void *p3(void *arg) {
sem_wait(&r3); sleep(1); sem_wait(&r1);
printf("P3 executando\n");
sem_post(&r1); sem_post(&r3);
return NULL;
}
int main() {
sem_init(&r1, 0, 1); sem_init(&r2, 0, 1); sem_init(&r3, 0, 1);
pthread_t t1, t2, t3;
pthread_create(&t1, NULL, p1, NULL);
pthread_create(&t2, NULL, p2, NULL);
pthread_create(&t3, NULL, p3, NULL);
pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL);
sem_destroy(&r1); sem_destroy(&r2); sem_destroy(&r3);
return 0;
}
Atividade: Identifique o ciclo formado (ex.: P1 espera R2, P2 espera R3, P3 espera R1).
Definição: Um deadlock ocorre quando um conjunto de processos forma um ciclo de espera, onde cada processo segura um recurso e espera por outro que está bloqueado por outro processo, impedindo todos de prosseguir (Tanenbaum, 6.1; Silberschatz, Cap. 7). Este exercício simula um deadlock com 3 processos (threads) e 3 recursos (semáforos), usando o código fornecido em C.
Funcionamento: O código cria três threads (p1
, p2
, p3
) que disputam três semáforos binários (r1
, r2
, r3
), cada um representando um recurso. Cada thread tenta adquirir dois recursos em sequência:
p1
: Adquire r1
, espera 1 segundo, tenta adquirir r2
, executa, libera r2
e r1
.p2
: Adquire r2
, espera 1 segundo, tenta adquirir r3
, executa, libera r3
e r2
.p3
: Adquire r3
, espera 1 segundo, tenta adquirir r1
, executa, libera r1
e r3
.Condições de Deadlock: O código satisfaz as quatro condições para deadlock:
p1
segura r1
) enquanto espera por outro (ex.: r2
).sem_post
.Simulação: O sleep(1)
aumenta a probabilidade de deadlock, permitindo que cada thread adquira seu primeiro recurso antes de tentar o segundo. Em uma execução típica:
p1
adquire r1
, bloqueia em r2
.p2
adquire r2
, bloqueia em r3
.p3
adquire r3
, bloqueia em r1
.Nenhuma thread prossegue, resultando em deadlock.
Ciclo: O deadlock forma o seguinte ciclo de espera:
P1
espera R2
(segurando R1
).P2
espera R3
(segurando R2
).P3
espera R1
(segurando R3
).Grafo de Espera: Representado textualmente como: P1 → R2 → P2 → R3 → P3 → R1 → P1
, formando um ciclo circular que confirma o deadlock.
Resumo: O código simula um deadlock com 3 processos e 3 recursos, criando um ciclo de espera onde P1
espera R2
, P2
espera R3
, e P3
espera R1
. O sleep(1)
facilita o deadlock, mas não o garante, pois depende do escalonamento. Para evitar o deadlock, estratégias como ordenação fixa de aquisição de recursos (ex.: sempre adquirir r1
, r2
, r3
na mesma ordem) ou timeouts podem ser usadas.
Teste seus conhecimentos: Responda e veja o feedback!
- Silberschatz et al., Fundamentos de SOs, 8ª ed., LTC, 2010, Cap. 7.
- Tanenbaum, SOs Modernos, 3ª ed., Prentice Hall, 2009, 2.5 (p. 115-118).
- Maziero, SOCM, Caps. 12 e 13 (link).
Simulação Interativa: Observe como processos competem por recursos e formam ciclos. Use os controles para ajustar e resolver o deadlock.
Deadlock! Ciclo detectado.
Starvation! Processo esperando demais.
Resolvido! Recursos liberados.