Introduction
Dans tout système multitâches l'un (si ce n'est le) des
problèmes centraux est celui de l'ordonnancement, c'est-à-dire
la sélection à un instant donné du processus
auquel le processeur sera alloué. C'est
la composante du système assurant cette fonction,
l'ordonnanceur (scheduler)
qui permet de garantir une exécution concurrente
de plusieurs tâches. Il est bien clair que les objectifs visés
ne sont pas les mêmes selon la nature du système
considéré et par suite les stratégies mises
en oeuvre dans les ordonnanceurs de ces différents types de
systèmes sont différents.
Le problème d'ordonnancement est encore plus délicat dans
un système temps réel, car non seulement il faut résoudre
le problème classique, mais il faut en plus tenir compte de la forte
contrainte qu'est le temps réel. Pour cela, on utilise la
priorité d'une tâche. Celle-ci est
liée à son caractère plus ou moins critique.
Pour résumer, le contexte dans lequel on se place est donc le suivant:
Quelques notions de base
- Les processus ont au cours de leur existence différents états possibles. Citons les trois principaux:
- Priorité: valeur numérique
associée à une tâche permettant de classer
les différents tâches selon leur importance.
En général il s'agit de valeurs entières,
les petites valeurs correspondant souvent aux grandes priorités.
Ces priorités peuvent être statiques
(si elles sont fixées au lancement des
tâches et ne sont pas modifiées par le système)
ou dynamiques (si elles sont mises à
jour périodiquement par le système).
Ainsi, typiquement, dans un système temps réel,
les priorités
sont fixes et reflètent le caractère critique des tâches.
Dans les systèmes orientés sur le partage du temps,
les priorités sont dynamiques: schématiquement la priorité
d'un processsus y diminuera si le processus est gros consommateur de CPU
et augmentera dans le cas contraire (ce qui favorisera les processus
interactifs).
- Quantum/tranche de temps (time slicing): le temps est découpé en tranches et, à chaque tranche une interruption permet à l'ordonnanceur du système de reprendre le contrôle;
- Tourniquet (round robin): c'est la technique de base consistant à allouer alternativement le processeur à chacun des processus prêts pour une durée maximale correspondant au quantum de temps (elle sera inférieure pour les tâches s'endormant durant leur période d'activité).
Les algorithmes d'ordonnancement généraux
- Ordonnancement FCFS
L'algorithme FCFS (First-Come-First-Served) (premier arrivé, premier servi) est le plus simple des algorithmes d'ordonnancement.
- Ordonnancement SJF
L'algorithme SJF (Shortest-Job-First) (le plus court en premier)
Les algorithmes d'ordonnancement pour le temps-réel
- Ordonnancement cyclique
- L'ordonnancement HPF
L'algorithme HPF (Highest Priority First) repose sur des priorités statiques associées aux différentes tâches selon la méthode d'analyse monotone par taux (Rate-Monotonic Scheduling). Le principe en est le suivant:
Dans l'exemple ci-dessous (en cliquant sur l'image, une version animée en est donnée), les trois tâches ont des périodes respectives de 10, 15 et 35 unités de temps, soit des priorités respectives 0.1, 0.07 et 0.03. Le temps de traitement nécessaire à chacune de leur période est respectivement de 4, 4 et 12 unités de temps.
Au temps 35, la tâche T3 a consommé 10 des 12 unités de temps nécessaires au traitement et le temps correspondant à sa période, un nouvel exemplaire en est lancé provoquant probablement l'abandon du traitement de l'occurrence en cours de traitement avant que celui-ci n'ait donné de résultat.
- L'ordonnancement EDF
La stratégie EDF (Earliest-Deadline First) est basée sur des priorités dynamiques : une tâche est d'autant plus prioritaire que son échéance est proche.
- L'ordonnancement LLF
La stratégie LLF (Least-Laxity First) est une variante de EDF.
Exemples
- JAVA
Une thread Java hérite à sa création de
la priorité de celle qui la crée (de la même
manière qu'un processus hérite de la priorité de
son processus père lors d'un fork). Cette priorité
peut être modifiée ultérieurement en lui appliquant
la méthode setPriority.
Les priorités des threads sont comprises entre les valeurs
MIN_PRIORITY et MAX_PRIORITY
définies dans la classe Thread
(1 pour MIN_PRIORITY,
10 pour MAX_PRIORITY
et 5 comme priorité standard
NORM_PRIORITY).
L'algorithme d'ordonnancement utilisé est un algorithme
préemptif avec tourniquet sur les systèmes supportant
le temps partagé (par tranchage du temps). De manière
plus précise, une thread restera active jusqu'à ce que
l'une des conditions suivantes soit réalisée:
yield
(noter
que cela ne permettra pas d'élire une thread de priorité
inférieure);
- MACH
A chaque activité (thread) est associé en vue de sa prise en compte par l'ordonnanceur d'un couple formé de
Le noyau de Mach est de manière standard configuré avec les politiques suivantes:
- CHORUS
Comme c'est le cas dans MACH, dans le système CHORUS une
activité possède une
classe d'ordonnancement et une priorité dans cette classe. D'un point de vue
interne, une activité possède une priorité système
comprise entre 0 (la plus grande priorité) et 255 (la plus petite).
Par défaut, une politique FIFO est appliquée:
Remarque: on retrouve la même approche avec les deux classes FIFO et RR proposées dans l'interface POSIX pour les activités (les PTHREADS). Une troisième politique (TS) correspondant à l'ordonnancement temps partagé à la Unix y a été ajoutée.
- QNX
On y retrouve la même approche avec les deux classes
FIFO et RR. Une troisième classe y a
également été ajoutée (l'ordonnancement adaptatif)
utilisé pour les aspects temps-partagé. Lorsqu'un processus
(QNX a adopté le parti pris de ne pas utiliser le concept de
threads) a consommé tout son quantum de temps, sa priorité est
décrémentée (dégénérescence
de priorité) et lorsqu'il s'endort, il reprend sa
priorité initiale.
Cependant dans ce système basé sur un micro-noyau,
le modèle client/serveur est largement utilisé pour
le développement de nouvelles applications et services.
Quelles que soit la politique d'ordonnancement adoptée pour
ces applications, les clients s`exécutent généralement
à une priorité inférieure à celle des serveurs.
Dans ces conditions, si un client de faible priorité requiert un service
d'un serveur de haute priorité en lui adressant un message,
cette requête sera a priori traité avec la priorité
élevée du serveur. Donc d'une certaine manière le
client verra sa priorité augmentée. On peut imaginer
qu'ainsi la requête non prioritaire s`exécutera au détriment
d'applications plus prioritaires que le client (mais moins prioritaire que
le serveur). C'est pour cette raison qu'il est possible pour un serveur
de spécifier qu'il traitera les demandes avec la priorité
de ses clients (client-driven priority). Finalement afin
de pouvoir traiter une nouvelle demande plus prioritaire qui arriverait
pendant le traitement d'une requête, la priorité de
la requête sera adoptée immédiatement par le
serveur pour poursuivre le traitement en cours.