Un système d'exploitation est dit multitâche lorsqu'il est capable de simuler l'exécution simultanée de plusieurs processus indépendamment du nombre de cœurs disponibles dans le microprocesseur.
Pour mettre en œuvre le multitâche, deux approches sont possibles :
Un processus ne peut donc pas bloquer le reste du système; en accordant quelques millisecondes de temps d'exécution à chaque processus, un système d'exploitation multitâche préemptif comme Linux, doit cependant effectuer de nombreuses commutations de contexte
(sauvegardes et restauration des registres du processeur, changement de l'espace d'adressage,…) avec un impact parfois non négligeable sur les performances du système.
Il convient alors de gérer "au mieux" l'ordre et le temps d'exécution des processus en prenant en compte leurs priorités et l'accès aux ressources du système, tout en maintenant une certaine "équité" entre les processus. On appelle ce pilotage l'ordonnancement.
Le but de ce TP est de comprendre et de simuler en Python quelques algorithmes d'ordonnancement de processus parmi les nombreux qui existent.
Nous considérerons la situation simplifiée de quatre processus P1, P2, P3 et P4 ( de même priorité ), dont la date de début d’exécution, ainsi que la durée de cette exécution, sont données dans la figure ci-dessous par l'échelle de temps ( 1 unité de temps = 1 "tick" d'horloge = 1 carré ) :
Mais un seul processus peut s'exécuter à la fois ( système mono-cœur ) : les 4 processus ne peuvent donc pas s’exécuter en parallèle comme la figure peut le laisser croire; il faudra donc bien plus de 8 "ticks" d'horloge pour terminer l'exécution complète des 4 processus...
Un algorithme d'ordonnancement devra donc forcément gérer une "file d'attente" dans laquelle les processus "séjourneront" plus ou moins longtemps..
Cette file est appelé file d'exécution ( = running queue ).
Téléchargez ce fichier, et décompressez-le dans votre zone personnelle : il contient notamment un dossier dans lequel se trouve un fichier processus.py
, définissant une classe Processus
.
Utiliser cette classe pour créer un tableau Python contenant 4 instances de la classe Processus modélisant les 4 processus de la situation étudiée :
Dans le dossier téléchargé se trouve un fichier ordonnanceur.py
, qui définit une classe Ordonnanceur
, qui contient les méthodes communes nécessaires à tous les
algorithmes que vous allez étudier dans ce TP.
Ce fichier n'est pas destiné à être modifié.
help()
de Python ) afin de connaître les méthodes et les attributs que vous pouvez utiliser.Ordonnanceur
( rappel : pas au programme, mais bien pratique !... ), c'est à dire des classes
qui disposent des mêmes méthodes que la classe Ordonnanceur
, mais auxquelles on peut en rajouter d'autres plus spécifiques.inserer
qui devra gérer l'insertion d'un processus dans la file d’exécution de l’ordonnanceur en fonction de l'algorithme que celui-ci utilise.ordonnanceur.py
, ainsi que le fichier processus.py
, devront :
La méthode tick
de la classe Ordonnanceur simule le déroulement de l’ordonnancement lors d'un "tick" d'horloge :
La méthode ordonnancement
permet de dérouler l'ordonnancement tant qu'il reste des processus à exécuter.
Ça, ça doit vous parler...
Avec cet algorithme, chaque processus est inséré en queue de file d’exécution en fonction de sa date d'arrivée, et c'est le premier entré dans la file qui est complètement exécuté, sans interruption, avant de laisser l’exécution au suivant dans la file.
La file d’exécution complète des 4 processus est dans ce cas :
Cet algorithme ne tient donc absolument pas compte de la durée relative de chaque processus : un "gros" processus monopolise donc le temps machine tant qu'il n'est pas terminé.
FIFO
, qui hérite de la classe Ordonnanceur
, et qui implémente une méthode insérer
, qui prend comme paramètre un processus,
et qui l'insère dans la file d’exécution de l'ordonnanceur en suivant l’algorithme FIFO.
Pour comparer les "performances" des différents algorithmes d'ordonnancement, on peut calculer deux "métriques" :
Bien entendu, plus ces deux "métriques" sont petites, plus efficace et "équitable" est l'algorithme ( on se doute que ce ne sera pas le cas ici 😅 ... )
Calculer le temps de séjour de chaque processus, puis le temps de séjour moyen d'un processus; faire de même pour le temps d'attente :
Une autre méthode est de prioriser les processus ayant le temps d'exécution le plus court : lorsqu'un processus se présente dans la file d’exécution, il n'interrompt pas l'exécution du processus en cours d'exécution ( = processus élu ), mais il est placé dans la file d’exécution devant les processus déjà présents mais ayant une durée d'exécution plus longue; dans la suite des opérations d'ordonnancement, il sera donc élu avant ces derniers.
Il s'agit donc d'un algorithme glouton...
SJF
, qui hérite de la classe Ordonnanceur
, et qui implémente une méthode insérer
, qui prend comme paramètre un processus,
et qui l'insère dans la file d’exécution de l'ordonnanceur en suivant l’algorithme SJF.
Pour cette insertion, on pourra suivre le principe suivant : insérer d'abord le processus en queue de file, puis lui trouver sa place en utilisant l’algorithme utilisé lors du
tri par insertion.Calculer le temps de séjour de chaque processus, puis le temps de séjour moyen d'un processus; faire de même pour le temps d'attente :
Dans ce type d'algorithme, l'exécution du processus élu peut être interrompue, pour laisser la place à l’exécution d'un autre processus; l'exécution du premier reprendra alors plus tard en fonction de l'algorithme utilisé.
Il s'agit de la version préemptive de l'algorithme SJF précédent : lorsqu'un processus se présente dans la file d’exécution, il peut interrompre l'exécution du processus élu, dans le cas où sa durée d’exécution est plus petite que la durée restante d’exécution de ce dernier : le nouveau processus est donc placé en tête de file, il s'exécute, puis "l'ancien" processus élu reprend son exécution ( à moins qu'un processus plus court se présente à nouveau !...).
SRTF
, qui hérite de la classe Ordonnanceur
, et qui implémente une méthode insérer
, qui prend comme paramètre un processus,
et qui l'insère dans la file d’exécution de l'ordonnanceur en suivant l’algorithme SRTF.
( Remarque : une très légère adaptation du code de l'algorithme SJF suffit 😎 ... )Calculer le temps de séjour de chaque processus, puis le temps de séjour moyen d'un processus; faire de même pour le temps d'attente :
L'approche de cet algorithme est différente : les processus sont insérés dans la file d’exécution en fonction de leur date d'arrivée, sans tenir compte de leur durée d’exécution.
A chaque processus est accordée une durée fixe d’exécution, appelée quantum de temps ( exprimé en "ticks" d'horloge ); le processus élu s’exécute pendant un quantum de temps, à l'issue duquel il est replacé en queue de file pour laisser la place au processus suivant, même si son exécution n'était pas terminée.
C'est donc une répartition complètement équitable du temps machine entre les processus.
RR
, qui hérite de la classe Ordonnanceur
, et qui implémente :
tick
( à compléter ) qui tiendra compte des spécificités de cet algorithme;insérer
, qui prend comme paramètre un processus, et qui l'insère dans la file d’exécution de l'ordonnanceur en suivant l’algorithme RR.Calculer le temps de séjour de chaque processus, puis le temps de séjour moyen d'un processus; faire de même pour le temps d'attente :
Chaque algorithme a ses avantages et ses inconvénients, et est vraiment adapté à une situation particulière : par exemple, SJF/SRTF sont mal adaptés si on ne sait pas déterminer avec précision la durée d'exécution d'un processus ( on est d'ailleurs en pratique obligé de calculer une "moyenne" pour cette durée, en fonction de celles des processus déjà exécutés ).
Ces algorithmes ne tiennent de plus pas compte de la priorité des processus : les logiciels gérant des interfaces graphiques devront par exemple prioriser les processus gérant les interactions avec l'utilisateur pour plus de réactivité de l'interface.
Un "vrai" algorithme d'ordonnancement utilise souvent des structures de données plus complexes qu'une simple file afin de gérer toutes ces contraintes.
Une correction de ce TP peut être trouvée ici.