504 lines
15 KiB
Markdown
504 lines
15 KiB
Markdown
# TP : Simulateur d'Ordonnanceur — Gestion des Processus
|
|
|
|
> **Thème** : Processus, états, ordonnancement, interblocage
|
|
|
|
---
|
|
|
|
## Contexte
|
|
|
|
Votre système d'exploitation préféré gère en permanence des dizaines de processus : navigateur web, lecteur de musique, mises à jour en arrière-plan... Comment fait-il pour donner l'illusion que tout s'exécute en même temps ?
|
|
|
|
Dans ce TP, vous allez créer un **simulateur d'ordonnanceur** qui gère une file de processus et les exécute tour à tour, comme le fait un vrai système d'exploitation.
|
|
|
|
---
|
|
|
|
## Partie 1 : La classe Processus
|
|
|
|
### Exercice 1 : Modéliser un processus
|
|
|
|
Créez une classe `Processus` représentant un processus avec ses caractéristiques.
|
|
|
|
**Attributs :**
|
|
- `pid` : identifiant unique du processus (int)
|
|
- `nom` : nom du processus (str)
|
|
- `duree_totale` : durée d'exécution totale nécessaire (int, en unités de temps)
|
|
- `duree_restante` : durée restant à exécuter (int)
|
|
- `etat` : état actuel du processus (str) — "nouveau", "pret", "elu", "bloque", "termine"
|
|
- `priorite` : niveau de priorité (int, optionnel, par défaut 0)
|
|
|
|
**Méthodes :**
|
|
- `__init__` : constructeur
|
|
- `executer(quantum)` : exécute le processus pendant `quantum` unités de temps
|
|
- `est_termine()` : renvoie True si le processus est terminé
|
|
- `__repr__` : affichage du processus
|
|
|
|
```python
|
|
class Processus:
|
|
# Compteur de PID (variable de classe)
|
|
compteur_pid = 0
|
|
|
|
def __init__(self, nom, duree_totale, priorite=0):
|
|
"""
|
|
Constructeur de la classe Processus.
|
|
|
|
:param nom: (str) Nom du processus
|
|
:param duree_totale: (int) Durée totale d'exécution
|
|
:param priorite: (int) Priorité (0 = normale)
|
|
"""
|
|
Processus.compteur_pid += 1
|
|
self.pid = Processus.compteur_pid
|
|
# À compléter
|
|
pass
|
|
|
|
def executer(self, quantum):
|
|
"""
|
|
Exécute le processus pendant quantum unités de temps.
|
|
Met à jour la durée restante.
|
|
|
|
:param quantum: (int) Temps d'exécution alloué
|
|
:return: (int) Temps réellement utilisé
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def est_termine(self):
|
|
"""
|
|
Vérifie si le processus est terminé.
|
|
|
|
:return: (bool) True si terminé
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def __repr__(self):
|
|
"""Affichage du processus."""
|
|
return f"[PID {self.pid}] {self.nom} ({self.etat}) - {self.duree_restante}/{self.duree_totale}"
|
|
```
|
|
|
|
**Tests :**
|
|
```python
|
|
>>> p = Processus("Firefox", 10)
|
|
>>> print(p)
|
|
[PID 1] Firefox (nouveau) - 10/10
|
|
>>> p.etat = "elu"
|
|
>>> p.executer(3)
|
|
3
|
|
>>> print(p)
|
|
[PID 1] Firefox (elu) - 7/10
|
|
>>> p.est_termine()
|
|
False
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 2 : L'ordonnanceur FIFO
|
|
|
|
### Exercice 2 : Ordonnanceur simple (FIFO)
|
|
|
|
L'ordonnanceur **FIFO** (First In, First Out) exécute les processus dans l'ordre d'arrivée, chacun jusqu'à la fin.
|
|
|
|
```python
|
|
class OrdonnanceurFIFO:
|
|
def __init__(self):
|
|
"""Initialise l'ordonnanceur avec une file vide."""
|
|
self.file_prets = [] # File des processus prêts
|
|
self.historique = [] # Historique d'exécution
|
|
self.temps = 0 # Temps courant
|
|
|
|
def ajouter_processus(self, processus):
|
|
"""
|
|
Ajoute un processus à la file des prêts.
|
|
|
|
:param processus: (Processus) Processus à ajouter
|
|
"""
|
|
processus.etat = "pret"
|
|
self.file_prets.append(processus)
|
|
print(f"[T={self.temps}] {processus.nom} ajouté à la file")
|
|
|
|
def executer_suivant(self):
|
|
"""
|
|
Exécute le prochain processus de la file jusqu'à la fin.
|
|
"""
|
|
if len(self.file_prets) == 0:
|
|
print("File vide, rien à exécuter")
|
|
return
|
|
|
|
# Récupérer le premier processus
|
|
processus = self.file_prets.pop(0)
|
|
processus.etat = "elu"
|
|
|
|
print(f"\n[T={self.temps}] {processus.nom} commence son exécution")
|
|
|
|
# Exécuter jusqu'à la fin
|
|
while not processus.est_termine():
|
|
temps_utilise = processus.executer(1)
|
|
self.temps += temps_utilise
|
|
self.historique.append((self.temps, processus.pid, processus.nom))
|
|
|
|
processus.etat = "termine"
|
|
print(f"[T={self.temps}] {processus.nom} terminé")
|
|
|
|
def executer_tous(self):
|
|
"""Exécute tous les processus de la file."""
|
|
print("\n" + "="*50)
|
|
print("ORDONNANCEUR FIFO - Début de l'exécution")
|
|
print("="*50)
|
|
|
|
while len(self.file_prets) > 0:
|
|
self.executer_suivant()
|
|
|
|
print("\n" + "="*50)
|
|
print(f"Exécution terminée - Temps total : {self.temps}")
|
|
print("="*50)
|
|
|
|
def afficher_historique(self):
|
|
"""Affiche l'historique d'exécution."""
|
|
# À compléter
|
|
pass
|
|
```
|
|
|
|
**Tests :**
|
|
```python
|
|
>>> ordonnanceur = OrdonnanceurFIFO()
|
|
>>> ordonnanceur.ajouter_processus(Processus("Firefox", 5))
|
|
>>> ordonnanceur.ajouter_processus(Processus("VSCode", 3))
|
|
>>> ordonnanceur.ajouter_processus(Processus("Spotify", 2))
|
|
>>> ordonnanceur.executer_tous()
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 3 : L'ordonnanceur Round Robin
|
|
|
|
### Exercice 3 : Ordonnanceur à temps partagé
|
|
|
|
L'ordonnanceur **Round Robin** (tourniquet) alloue un **quantum** de temps à chaque processus, puis passe au suivant. Si le processus n'est pas terminé, il retourne en fin de file.
|
|
|
|
```python
|
|
class OrdonnanceurRoundRobin:
|
|
def __init__(self, quantum=2):
|
|
"""
|
|
Initialise l'ordonnanceur Round Robin.
|
|
|
|
:param quantum: (int) Temps alloué à chaque processus
|
|
"""
|
|
self.file_prets = []
|
|
self.quantum = quantum
|
|
self.temps = 0
|
|
self.historique = []
|
|
|
|
def ajouter_processus(self, processus):
|
|
"""Ajoute un processus à la file."""
|
|
processus.etat = "pret"
|
|
self.file_prets.append(processus)
|
|
|
|
def executer_cycle(self):
|
|
"""
|
|
Exécute un cycle : le premier processus pendant quantum unités.
|
|
S'il n'est pas terminé, il retourne en fin de file.
|
|
|
|
:return: (bool) True si un processus a été exécuté
|
|
"""
|
|
if len(self.file_prets) == 0:
|
|
return False
|
|
|
|
# Récupérer le premier processus
|
|
processus = self.file_prets.pop(0)
|
|
processus.etat = "elu"
|
|
|
|
# Exécuter pendant le quantum
|
|
temps_utilise = processus.executer(self.quantum)
|
|
self.temps += temps_utilise
|
|
|
|
# Enregistrer dans l'historique
|
|
for _ in range(temps_utilise):
|
|
self.historique.append(processus.nom[0]) # Première lettre
|
|
|
|
if processus.est_termine():
|
|
processus.etat = "termine"
|
|
print(f"[T={self.temps}] {processus.nom} TERMINÉ")
|
|
else:
|
|
# Remettre en fin de file
|
|
processus.etat = "pret"
|
|
self.file_prets.append(processus)
|
|
print(f"[T={self.temps}] {processus.nom} interrompu, retour en file ({processus.duree_restante} restant)")
|
|
|
|
return True
|
|
|
|
def executer_tous(self):
|
|
"""Exécute tous les processus jusqu'à la fin."""
|
|
print(f"\n{'='*50}")
|
|
print(f"ORDONNANCEUR ROUND ROBIN (quantum={self.quantum})")
|
|
print(f"{'='*50}\n")
|
|
|
|
while self.executer_cycle():
|
|
pass
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Temps total : {self.temps}")
|
|
print(f"Historique : {''.join(self.historique)}")
|
|
print(f"{'='*50}")
|
|
```
|
|
|
|
**Tests :**
|
|
```python
|
|
>>> Processus.compteur_pid = 0 # Reset des PID
|
|
>>> rr = OrdonnanceurRoundRobin(quantum=2)
|
|
>>> rr.ajouter_processus(Processus("Firefox", 6))
|
|
>>> rr.ajouter_processus(Processus("VSCode", 4))
|
|
>>> rr.ajouter_processus(Processus("Spotify", 2))
|
|
>>> rr.executer_tous()
|
|
```
|
|
|
|
**Sortie attendue :**
|
|
```
|
|
==================================================
|
|
ORDONNANCEUR ROUND ROBIN (quantum=2)
|
|
==================================================
|
|
|
|
[T=2] Firefox interrompu, retour en file (4 restant)
|
|
[T=4] VSCode interrompu, retour en file (2 restant)
|
|
[T=6] Spotify TERMINÉ
|
|
[T=8] Firefox interrompu, retour en file (2 restant)
|
|
[T=10] VSCode TERMINÉ
|
|
[T=12] Firefox TERMINÉ
|
|
|
|
==================================================
|
|
Temps total : 12
|
|
Historique : FFVVSSFFVVFF
|
|
==================================================
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 4 : Simulation d'interblocage
|
|
|
|
### Exercice 4 : Processus avec ressources
|
|
|
|
Modélisons des processus qui ont besoin de ressources partagées.
|
|
|
|
```python
|
|
class Ressource:
|
|
def __init__(self, nom):
|
|
"""
|
|
:param nom: (str) Nom de la ressource
|
|
"""
|
|
self.nom = nom
|
|
self.proprietaire = None # PID du processus qui détient la ressource
|
|
|
|
def est_disponible(self):
|
|
"""Renvoie True si la ressource est libre."""
|
|
return self.proprietaire is None
|
|
|
|
def acquerir(self, pid):
|
|
"""
|
|
Tente d'acquérir la ressource.
|
|
|
|
:param pid: (int) PID du processus demandeur
|
|
:return: (bool) True si acquisition réussie
|
|
"""
|
|
if self.est_disponible():
|
|
self.proprietaire = pid
|
|
return True
|
|
return False
|
|
|
|
def liberer(self):
|
|
"""Libère la ressource."""
|
|
self.proprietaire = None
|
|
|
|
def __repr__(self):
|
|
if self.est_disponible():
|
|
return f"Ressource {self.nom} : LIBRE"
|
|
else:
|
|
return f"Ressource {self.nom} : occupée par PID {self.proprietaire}"
|
|
```
|
|
|
|
### Exercice 5 : Détection d'interblocage
|
|
|
|
Créez une fonction qui détecte un interblocage potentiel.
|
|
|
|
```python
|
|
class ProcessusAvecRessources(Processus):
|
|
def __init__(self, nom, duree_totale, ressources_requises):
|
|
"""
|
|
:param ressources_requises: (list) Liste des noms de ressources nécessaires
|
|
"""
|
|
super().__init__(nom, duree_totale)
|
|
self.ressources_requises = ressources_requises
|
|
self.ressources_obtenues = []
|
|
|
|
def demander_ressources(self, ressources_dispo):
|
|
"""
|
|
Tente d'obtenir toutes les ressources nécessaires.
|
|
|
|
:param ressources_dispo: (dict) {nom: Ressource}
|
|
:return: (bool) True si toutes les ressources obtenues
|
|
"""
|
|
for nom_res in self.ressources_requises:
|
|
if nom_res not in self.ressources_obtenues:
|
|
ressource = ressources_dispo.get(nom_res)
|
|
if ressource and ressource.acquerir(self.pid):
|
|
self.ressources_obtenues.append(nom_res)
|
|
print(f" {self.nom} obtient {nom_res}")
|
|
else:
|
|
print(f" {self.nom} BLOQUÉ sur {nom_res}")
|
|
self.etat = "bloque"
|
|
return False
|
|
|
|
return True
|
|
|
|
def liberer_ressources(self, ressources_dispo):
|
|
"""Libère toutes les ressources détenues."""
|
|
for nom_res in self.ressources_obtenues:
|
|
ressources_dispo[nom_res].liberer()
|
|
print(f" {self.nom} libère {nom_res}")
|
|
self.ressources_obtenues = []
|
|
|
|
|
|
def detecter_interblocage(processus_list, ressources):
|
|
"""
|
|
Détecte si un interblocage existe.
|
|
|
|
:param processus_list: (list) Liste des processus
|
|
:param ressources: (dict) Dictionnaire des ressources
|
|
:return: (bool) True si interblocage détecté
|
|
"""
|
|
# Un interblocage existe si tous les processus non terminés sont bloqués
|
|
processus_actifs = [p for p in processus_list if p.etat != "termine"]
|
|
|
|
if len(processus_actifs) == 0:
|
|
return False
|
|
|
|
tous_bloques = all(p.etat == "bloque" for p in processus_actifs)
|
|
|
|
if tous_bloques:
|
|
print("\n*** INTERBLOCAGE DÉTECTÉ ! ***")
|
|
print("Processus bloqués :")
|
|
for p in processus_actifs:
|
|
print(f" - {p.nom} : possède {p.ressources_obtenues}, attend {[r for r in p.ressources_requises if r not in p.ressources_obtenues]}")
|
|
return True
|
|
|
|
return False
|
|
```
|
|
|
|
**Exemple de simulation d'interblocage :**
|
|
```python
|
|
# Ressources
|
|
ressources = {
|
|
"A": Ressource("A"),
|
|
"B": Ressource("B")
|
|
}
|
|
|
|
# Processus (scénario classique d'interblocage)
|
|
p1 = ProcessusAvecRessources("Programme1", 5, ["A", "B"])
|
|
p2 = ProcessusAvecRessources("Programme2", 5, ["B", "A"])
|
|
|
|
# Simulation manuelle
|
|
print("=== Simulation d'interblocage ===\n")
|
|
|
|
print("Tour 1 : P1 demande A")
|
|
p1.demander_ressources(ressources)
|
|
|
|
print("\nTour 2 : P2 demande B")
|
|
p2.demander_ressources(ressources)
|
|
|
|
print("\nTour 3 : P1 demande B (déjà pris par P2)")
|
|
p1.demander_ressources(ressources)
|
|
|
|
print("\nTour 4 : P2 demande A (déjà pris par P1)")
|
|
p2.demander_ressources(ressources)
|
|
|
|
# Détection
|
|
detecter_interblocage([p1, p2], ressources)
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 5 : Statistiques et comparaison
|
|
|
|
### Exercice 6 : Calcul des métriques
|
|
|
|
Ajoutez des méthodes pour calculer les statistiques d'ordonnancement :
|
|
|
|
```python
|
|
def calculer_statistiques(self):
|
|
"""
|
|
Calcule les statistiques d'ordonnancement.
|
|
|
|
:return: (dict) Statistiques
|
|
"""
|
|
# Temps de réponse moyen
|
|
# Temps d'attente moyen
|
|
# Temps de rotation moyen
|
|
# À compléter
|
|
pass
|
|
```
|
|
|
|
**Métriques importantes :**
|
|
- **Temps de rotation** (turnaround) : temps total entre l'arrivée et la fin
|
|
- **Temps d'attente** : temps passé en file d'attente
|
|
- **Temps de réponse** : temps avant la première exécution
|
|
|
|
### Exercice 7 : Comparaison FIFO vs Round Robin
|
|
|
|
Créez un programme qui compare les deux algorithmes sur le même jeu de processus.
|
|
|
|
```python
|
|
def comparer_ordonnanceurs():
|
|
"""Compare FIFO et Round Robin."""
|
|
|
|
processus_test = [
|
|
("Navigateur", 8),
|
|
("Editeur", 4),
|
|
("Terminal", 2),
|
|
("Musique", 6)
|
|
]
|
|
|
|
print("="*60)
|
|
print("COMPARAISON DES ORDONNANCEURS")
|
|
print("="*60)
|
|
|
|
# Test FIFO
|
|
Processus.compteur_pid = 0
|
|
fifo = OrdonnanceurFIFO()
|
|
for nom, duree in processus_test:
|
|
fifo.ajouter_processus(Processus(nom, duree))
|
|
fifo.executer_tous()
|
|
|
|
# Test Round Robin
|
|
Processus.compteur_pid = 0
|
|
rr = OrdonnanceurRoundRobin(quantum=2)
|
|
for nom, duree in processus_test:
|
|
rr.ajouter_processus(Processus(nom, duree))
|
|
rr.executer_tous()
|
|
|
|
# À compléter : afficher la comparaison des statistiques
|
|
```
|
|
|
|
---
|
|
|
|
## Résumé des algorithmes
|
|
|
|
| Algorithme | Principe | Avantages | Inconvénients |
|
|
|------------|----------|-----------|---------------|
|
|
| **FIFO** | Premier arrivé, premier servi | Simple | Temps d'attente long pour les petits processus |
|
|
| **Round Robin** | Quantum de temps pour chacun | Équitable, bon temps de réponse | Overhead des changements de contexte |
|
|
| **Priorité** | Processus prioritaire d'abord | Urgent traité en premier | Famine des processus peu prioritaires |
|
|
|
|
---
|
|
|
|
## Pour aller plus loin
|
|
|
|
- **Ordonnanceur à priorité** : Implémenter un ordonnanceur qui choisit le processus de plus haute priorité
|
|
- **Ordonnanceur SJF** (Shortest Job First) : Le processus le plus court d'abord
|
|
- **Vieillissement** (Aging) : Augmenter la priorité des processus qui attendent longtemps
|
|
- **Interface graphique** : Visualiser l'exécution avec une barre de Gantt
|
|
|
|
---
|
|
|
|
Auteur : Florian Mathieu
|
|
|
|
Licence CC BY NC
|
|
|
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|