Você está na página 1de 15

Relatório Técnico Sobre Um Algoritmo De Detecção De

Impasses Com Múltiplos Recursos De Cada Tipo


Christian de Jesus da C. Marinnho1, Mário Roberto L. Pinto1,
Victor Hugo G. Oliviera1, Davison Logan dos S. Cardoso1
1
Instituto de Ciências Exatas e Naturais – Faculdade de Computação
Universidade Federal do Pará (UFPA) – Belém, PA - Brasil
christiandejesus23@gmail.com, mrlimaroberto@gmail.com,
victorhhugo128@gmail.com, logancardoso4@gmail.com

Abstract. This article aims to describe the operation of code that detects and
reports deadlocks in a system. The points covered in this article are: The
methodology of creating the source code and the computational tests made in
the source code. This article is made for the discipline of Operating Systems of
the bachelor's degree in Computer Science, taught by professor Reginaldo
Santos.
Resumo. Este artigo tem como objetivo descrever o funcionamento de um
código que detecta e reporta impasses em um sistema. Os pontos abordados
neste artigo são: A metodologia da criação do código-fonte e os testes
computacionais feitos no código fonte. Este artigo é feito para a disciplina de
Sistemas Operacionais do curso de bacharelado em Ciência da Computação,
ministrado pelo docente Reginaldo Santos.

1. Introdução
Em um sistema operacional, há vários processos sendo criados e mortos a todo instante,
muitos desses processos precisam de recursos geridos pelo próprio sistema operacional
para poderem concluir suas funções. Por vezes, espera-se que ocorram impasses
(deadlocks) que obstruam o funcionamento correto dos processos causados pela falta de
recursos disponíveis.
Segundo Tanenbaum, impasse pode ser definido como: “Um conjunto de
processos estará em situação de deadlock se todo processo pertencente ao conjunto estiver
esperando por um evento que somente um outro processo desse mesmo conjunto poderá
fazer acontecer”.
Uma forma mais simples de explicar seria com o exemplo de um cruzamento de
transito, onde um carro na via A pede passagem a um carro na via B, mas o pedido é
negado, e o carro na via B pede passagem para o carro na via C, mas o pedido também é
negado, o mesmo ocorre com o carro na via C para o da via D, e por fim o carro na via D
pede passagem pro carro na via A, que não tem como passar e nega o pedido, assim,
nenhum dos carros pode prosseguir seu caminhos, pois a outros esperando as vias serem
livres. Isso configura um impasse.
Como foi dito anteriormente, um impasse pode ocorrer naturalmente em um
sistema operacional, porém, é necessário que se cumpram 4 condições para que eles
apareçam:
Condição de exclusão mutua: em um determinado instante, todo recurso está
alocado para um único processo ou está livre.
Condição de posse e espera: um processo que já tem recursos alocados para si,
pede por mais recursos.
Condição de não preempção: Um recurso alocado à um processo não pode ser
tomado a força.
Condição de espera circular: Uma cadeia circular de espera se forma entre um ou
mais processos, onde um espera a liberação de recursos alocados em outro e assim
sucessivamente.
Para que ocorra um impasse, todas as condições acima citadas têm que ocorrer ao
mesmo tempo.
Para a melhor compreensão de um impasse, eles também podem ser representados
por um grafo, onde o recurso por um círculo e o processo é representado por um quadrado.
Quando um processo solicita um recurso, uma seta é dirigida do quadrado ao círculo.
Quando um recurso é alocado a um processo, uma seta é dirigida do círculo ao quadrado,
como mostrado na figura abaixo:

Figura 1. Grafo de uma Espera Circular


A figura representa uma espera circular, onde os processos A e B, retem os
recursos R1 e R2 alocados para si respectivamente, porém, solicitam os recursos alocados
ao outro processo para concluírem sua tarefa, assim causando um impasse.
A ocorrência de impasses depende da complexidade do sistema operacional,
entretanto há métodos de correção e prevenção para os mesmos, que podem ser aplicados
dependendo da situação. Um dos métodos mais simples é ignorar o impasse, que pode ser
aplicado em sistemas onde impasses são raros. Cada caso tem que ser analisado de modo
que não interfira na eficiência do sistema operacional, por isso a detecção de impasses é
importante, para que seja analisado o custo/benefício de tratar aquele impasse ou se pode
simplesmente ignora-lo
2. Metodologia
A metodologia adotada para este artigo, foi a de testes de cenário para obter diferentes
respostadas do algoritmo. O código foi submetido a diferentes entradas de dados, e então
suas saídas foram colhidas e analisadas diante do cenário apresentado.
Foi criado um código que gera matrizes e vetores com números aleatórios,
simulando os recursos de um sistema operacional, sendo eles, a Matriz A, que representa
os recursos disponíveis, a Matriz E, que representa os recursos existentes, o vetor C, que
representa os recursos alocados e o vetor R, que representa os recursos requisitados pelos
processos. Depois que esses dados são gerados aleatoriamente, eles são guardados em um
arquivo .pkl gerado pela biblioteca pickle, e então são analisados pelo código de detecção
de impasses, que retorna uma saída mostrando duas situações, quando não há impasses,
o código imprime a mensagem “não há impasses” na tela, e quando há impasses, o código
imprime uma mensagem com o número de impasses e os processos envolvidos nesses
impasses.

3. Recursos Computacionais
Para este artigo foram utilizados os seguintes recursos: Sistema Operacional Windows 11,
Sistema Operacional Linux Ubuntu 20.04, a escolha desses sistemas operacionais foi feita
mediante a porcentagem de utilização deles pelo público geral.
A linguagem de programação utilizada foi Python versão 3.8.12, a escolha da
linguagem de programação foi baseada nos recursos já oferecidos por essa que
facilitariam o processo de escrita do código, os principais utensílios que auxiliaram na
construção do código foram os recursos de biblioteca da linguagem.
A principal biblioteca utilizada foi a biblioteca Pickle, cuja função assumida foi a
de formatação e decodificação de arquivos .pkl para a transposição das informações
desses para a execução dos testes descritos em cada cenário.
A IDE utilizada foi o site de IDE online repl.it, que foi escolhido por ser uma
ótima plataforma de codificação em conjunto, onde cada participante pode dar sua
contribuição para o código ao mesmo tempo.

4. Testes Computacionais
Para os testes computacionais foram desenvolvidos 6 cenários distintos, usados como
base da entrada de dados do algoritmo de análise de impasses. Foi desenvolvido um
algoritmo de detecção de impasses que tem como entrada a matriz de requisição R, a
matriz de alocação atual C e o vetor de recursos disponíveis A. Utilizando a comparação
entre as diferentes matrizes e vetores inseridos na entrada, o algoritmo verifica cada
processo e seus recursos requisitados a fim de verificar se é possível fazer essa requisição.
Se for possível, ele aloca, considera que o processo foi terminado e libera os recursos
atualmente alocados. Caso não seja possível, ele não faz alteração alguma nas matrizes e
vetores. O algoritmo é repetido até que não haja mais mudança nos recursos alocados no
processo. Isso sinaliza que ou todos os processos foram concluídos, ou que houve um
impasse entre eles. Para os testes computacionais, foram utilizados arquivos de extensão
.pkl, que foram decodificados em dicionários que, por sua vez, foram convertidos em
diferentes matrizes e vetores para servirem de entrada para o algoritmo. Na criação desses
arquivos codificados em .pkl, foi desenvolvido um outro algoritmo que, usando como
base o primeiro já citado, gera as duas matrizes e os dois vetores aleatoriamente usando
como base o número de processos, número de recursos e a quantidade de impasses
desejados para determinado cenário. Os dados das matrizes e vetores dos cenários serão
descritos a seguir:

4.1. Cenário 1
Para o primeiro cenário, forma usados os seguintes valores:
A matriz de requisição R é:

Figura 2. Matriz R do cenário 1


A matriz de alocação atual C é:

Figura 3. Matriz C do cenário 1


O vetor de recursos disponíveis A é:

Figura 4. Vetor A do cenário 1


A partir das matrizes apresentadas, o algoritmo irá comparar a matriz de requisição
(R) com o vetor de recursos disponíveis (A) com o fim de concluir se um impasse irá
ocorrer ou não.
Figura 5. Saída de dados do cenário 1
Como é possível observar, o sistema retornou 2 impasses nos processos 3 e 5,
portanto, os recursos disponíveis não eram suficientes para suprir os recursos na matriz
de requisição, dessa forma o algoritmo dá-se por encerrado e as matrizes de R e C e o
vetor A não sofrem mais nenhuma alteração.

4.2. Cenário 2
Para o segundo cenário, foram usados os seguintes valores:
A matriz de requisição R é o seguinte:

Figura 6. Matriz R do cenário 2


A matriz de alocação atual C é:

Figura 7. Matriz C do cenário 2


O vetor de recursos disponíveis A é:

Figura 8. Vetor A do cenário 2


A partir das matrizes apresentadas, o algoritmo irá comparar a matriz de
requisição (R) com o vetor de recursos disponíveis (A) com o fim de concluir se um
impasse irá ocorrer ou não.

Figura 9. Saída de dados do cenário 2


Como é possível observar, o sistema não retornou impasses, portanto, os recursos
disponíveis eram suficientes para suprir os recursos na matriz de requisição, dessa e os
vetores de A e E (recursos existentes) adquirem os mesmos valores e R e C não sofrem
mais nenhuma alteração, após isso o algoritmo dá-se por encerrado.

4.3. Cenário 3
Para o terceiro cenário, foram usados os seguintes valores:
A matriz de requisição R é o seguinte:

Figura 10. Matriz R do cenário 3


A matriz de alocação atual C é:

Figura 11. Matriz C do cenário 3


O vetor de recursos disponíveis A é:

Figura 12. Vetor A do cenário 3


A partir das matrizes apresentadas, o algoritmo irá comparar a matriz de
requisição (R) com o vetor de recursos disponíveis (A) com o fim de concluir se um
impasse irá ocorrer ou não.

Figura 13. Saída de dados do cenário 3


Como é possível observar, o sistema retornou 3 impasses nos processos 5, 6 e 7,
portanto, os recursos disponíveis não eram suficientes para suprir os recursos na matriz
de requisição, dessa forma o algoritmo dá-se por encerrado e as matrizes de R e C e o
vetor A não sofrem mais nenhuma alteração.

4.4. Cenário 4
Para o quarto cenário, foram usados os seguintes valores:
A matriz de requisição R é o seguinte:

Figura 14. Matriz R do cenário 4


A matriz de alocação atual C é:

Figura 15. Matriz C do cenário 4


O vetor de recursos disponíveis A é:

Figura 16. Vetor A do cenário 4


A partir das matrizes apresentadas, o algoritmo irá comparar a matriz de
requisição (R) com o vetor de recursos disponíveis (A) com o fim de concluir se um
impasse irá ocorrer ou não.

Figura 17. Saída de dados do cenário 4


Como é possível observar, o sistema não retornou impasses, portanto, os recursos
disponíveis eram suficientes para suprir os recursos na matriz de requisição, dessa e os
vetores de A e E (recursos existentes) adquirem os mesmos valores e R e C não sofrem
mais nenhuma alteração, após isso o algoritmo dá-se por encerrado.

4.5. Cenário 5
Para o quinto cenário, foram usados os seguintes valores:
A matriz de requisição R é o seguinte:

Figura 18. Matriz R do cenário 5


A matriz de alocação atual C é:

Figura 19. Matriz C do cenário 5


O vetor de recursos disponíveis A é:

Figura 20. Vetor A do cenário 5


A partir das matrizes apresentadas, o algoritmo irá comparar a matriz de
requisição (R) com o vetor de recursos disponíveis (A) com o fim de concluir se um
impasse irá ocorrer ou não.

Figura 21. Saída de dados do cenário 5


Como é possível observar, o sistema retornou 2 impasses nos processos 4 e 5,
portanto, os recursos disponíveis não eram suficientes para suprir os recursos na matriz
de requisição, dessa forma o algoritmo dá-se por encerrado e as matriz de R e C e o vetor
A não sofrem mais nenhuma alteração.
4.6. Cenário 6
Para o sexto cenário, foram usados os seguintes valores:
A matriz de requisição R é o seguinte:

Figura 22. Matriz R do cenário 6


A matriz de alocação atual C é:

Figura 23. Matriz C do cenário 6


O vetor de recursos disponíveis A é:

Figura 24. Vetor A do cenário 6


A partir das matrizes apresentadas, o algoritmo irá comparar a matriz de
requisição (R) com o vetor de recursos disponíveis (A) com o fim de concluir se um
impasse irá ocorrer ou não.

Figura 25. Saída de dados do cenário 6


Como é possível observar, o sistema não retornou impasses, portanto, os recursos
disponíveis eram suficientes para suprir os recursos na matriz de requisição, dessa e os
vetores de A e E (recursos existentes) adquirem os mesmos valores e R e C não sofrem
mais nenhuma alteração, após isso o algoritmo dá-se por encerrado.

5. Conclusão
Um impasse ocorre em um sistema operacional, casualmente, e depende da
complexidade do mesmo. Um bom método de evitar problemas em tempo de execução,
é detectar esses impasses e tentar resolve-los, sem muito prejuízos para o sistema. Um
algoritmo de detecção de impasses se faz extremamente necessário em casos assim, pois
apenas com a análise de matrizes e vetores que representam os recursos e processos no
cenário do sistema operacional, é possível saber se aquela ação resultará em um impasse
ou não, assim, tornando mais fácil saber qual decisão o sistema operacional deverá tomar.
Com a análise dos dados obtidos do processamento do algoritmo de detecção de
impasses, pode-se concluir que em cada cenário o algoritmo cumpre seu papel de avisar
ao sistema sobre possiveis impasses perfeitamente, podendo ser utilizado em um sistema
operacional sem nenhum tipo de problema.

6. Referencias
Tanenbaum, Andrew S. (2016) “Sistemas Operacionais Modernos”, 4. ed - São Paulo
Anexo 1 – Código do Algoritmo detector de impasses
import pickle

def checar_recursos(r: list, a: list, c: list) -> set:


# Define o número de recursos
n_recursos = len(a)

# Define o número de processos


n_processos = len(c)

# Cria um conjunto vazio que será preenchido com os índices dos


processos envolvidos em um empasse
# caso haja um.
impasses = set()

# O programa irá iterar por todos os processos


for i_processo in range(0, n_processos):

# A variável 'liberar' será utilizada para decidir se os re-


cursos disponíveis devem
# ou não ser liberados para o processo selecionado.
liberar = 1

# O programa irá iterar por todos os tipos de recursos.


for i_recurso in range(0, n_recursos):

# Se houver um tipo de recurso que não pode ser alocado,


ele muda a variável 'liberar'
# e adiciona o processo selecionado ao conjunto de proces-
sos que não puderam ser concluídos.
if r[i_processo][i_recurso] > a[i_recurso]:
impasses.add(i_processo)
liberar = 0

# Se o loop que itera pelos recursos acabou e a variável 'li-


berar' não foi modificada, ocorrerá
# a execução do processo selecionado e a liberação dos recur-
sos que estavam sendo utilizados por ele.
if liberar == 1:
for i_recurso in range(0, n_recursos):
recurso = c[i_processo][i_recurso]
c[i_processo][i_recurso] = 0
r[i_processo][i_recurso] = 0
a[i_recurso] += recurso

# Printa o estado atual dos dados envolvidos nas operações.


print(f'A: {a}')
print(f'C: {c}')
print(f'R: {r}')
print('\n\n')

# Retorna o conjunto de processos que não puderam ser atendidos.


return impasses

if __name__ == '__main__':
with open(r'cenario3.pkl', 'rb') as file:
cenario = pickle.load(file)
# Atribuindo os devidos valores do cenário escolhido
A = cenario['A']
E = cenario['E']
C = cenario['C']
R = cenario['R']

print(f'A: {A}')
print(f'E: {E}')
print(f'C: {C}')
print(f'R: {R}')
print('\n\n')

a0 = None
a1 = None

# Entra em um loop para alocar os recursos para cada processo pos-


sível. Se o estado dos dados não muda, significa
# que, ou há um impasse, ou todos os processos foram atendidos.
while True:
a1 = checar_recursos(R, A, C)
# print(a1)

if a0 == a1:
break

a0 = a1

i = 0

# Faz a formatação da saída do programa


if a1 != set():
qtd_processos = len(a1)
print(f"Há {qtd_processos} impasses no sistema.")
print("Processos envolvidos no empasse:", end='')
for elemento in a1:
if i > 0:
print(',', end='')
print(f' {elemento + 1}', end='')
i += 1
print('.')

else:
print("Não há impasses no sistema.")
Figura 26. algoritmo_detector.py
Anexo 2 – Algoritmo Gerador de Impasses
from random import randint
import copy
from algoritmo_detector import checar_recursos

def impasse_generator(n_processos: int, n_recursos: int, n_impasses:


int) -> dict:

# Gera o vetor de recursos existentes E.


vetor_e = list()
for recurso in range(0, n_recursos):
vetor_e.append(randint(5, 15))

# Gera o vetor de recursos disponíveis A.


vetor_a = list()
for recurso in range(0, n_recursos):
vetor_a.append(vetor_e[recurso])

# Inicializa a matriz de recursos alocados C.


matriz_c = list()

# Coloca os devidos valores na matriz C.


for processo in range(0, n_processos):

# O loop se certifica de que não haja processos com 0 recursos


alocados.
while True:

# Faz uma deep copy do vetor A para que ele não seja modi-
ficado antes
# de se ter certeza de que o procedimento dará certo
vetor_a_backup = copy.deepcopy(vetor_a)

linha_processo = []

# Distribui os recursos de maneira mais ou menos distribu-


ída.
for recurso in range(0, n_recursos):
qtd_recurso = randint(0, vetor_e[recurso]//2)
if qtd_recurso >= vetor_a_backup[recurso]:
qtd_recurso = 0
linha_processo.append(qtd_recurso)
vetor_a_backup[recurso] -= qtd_recurso

# O loop while continua se todos os recursos alocados para


o processo
# forem iguais a 0.
if not all(v == 0 for v in linha_processo):
matriz_c.append(linha_processo)
vetor_a = copy.deepcopy(vetor_a_backup)
break

# Como os recursos atribuídos são gerados aleatóriamente,


há a possibilidade
# dos recursos acabarem antes de preencher todos os pro-
cessos. Nesse caso,
# o programa irá induzir a um erro e terminará.
if all(v == 1 for v in vetor_a):
raise(TypeError("Execute o código novamente."))

# Esse loop irá continuar até que se forme uma matriz requisição R
que atenda ao número de
# impasses passado como parâmetro na função
while True:
matriz_r = []
vetor_checagem = []
for i in range(0, n_processos):
vetor_checagem.append(0)

# Preenche a matriz de requisição C.


for processo in range(0, n_processos):
recursos_r = []
for recurso in range(0, n_recursos):
recursos_r.append(randint(0, vetor_e[recurso] - ma-
triz_c[processo][recurso]))

# A variável 'vetor_checagem' vai ser responsável por


registrar se todos os valores
# de requisição do atual processo são maiores que 0.
if recursos_r[recurso] != 0:
vetor_checagem[processo] = 1
matriz_r.append(recursos_r)

# Faz uma deep copy dos dados que entrarão na função che-
car_recursos.
matriz_rcopia = copy.deepcopy(matriz_r)
a_copia = copy.deepcopy(vetor_a)
c_copia = copy.deepcopy(matriz_c)

# Verifica se vai ocorrer um empasse.


impasse1 = checar_recursos(matriz_rcopia, a_copia, c_copia)

# Verifica se o empasse é entre a quantidade de processos es-


pecificada.
if len(impasse1) == n_impasses:

# Se certifica que nem todas as colunas de cada processo


da matriz requisição R são iguais a 0.
for i in vetor_checagem:
if i == 0:
break
return {'C': matriz_c,
'R': matriz_r,
'E': vetor_e,
'A': vetor_a,
}

if __name__ == '__main__':
# Testa a função.
print(impasse_generator(4, 5, 2))
Figura 27. impasse_generator.py

Você também pode gostar