SlideShare ist ein Scribd-Unternehmen logo
1 von 40
Downloaden Sie, um offline zu lesen
Systèmes multitâches et systèmes temps réel
Support de cours
Systèmes multitâches et systèmes temps réel -2-
Systèmes multitâches et systèmes temps réel
TABLE DES MATIERES
Table des matières_______________________________________________________________2
Systèmes multitâches et systèmes temps réel _______________________________________3
Quelques définitions _____________________________________________________________3
Algorithmes adaptées aux contraintes de temps______________________________________3
Détermination de contraintes de temps __________________________________________________ 4
Contraintes de temps faibles avec quelques événements contraignants ______________________ 5
Conclusion partielle__________________________________________________________________ 11
Systèmes multitâches et noyau temps réel _________________________________________12
Etats d’une tâche ___________________________________________________________________ 12
Non créé __________________________________________________________________________________ 12
Dormant ou créé ____________________________________________________________________________ 12
Prêt ou éligible _____________________________________________________________________________ 13
En exécution _______________________________________________________________________________ 13
Bloqué ou Suspendu ________________________________________________________________________ 13
Transitions entre états d'une tâche ____________________________________________________ 13
Création d'une tâche (crée) ___________________________________________________________________ 13
Activation d'une tâche (active) _________________________________________________________________ 13
Attribution du processeur à la tâche _____________________________________________________________ 13
Suspension d'une tâche ______________________________________________________________________ 13
Reprise d'une tâche bloquée __________________________________________________________________ 13
Fin d'une tâche ou destruction d'une tâche _______________________________________________________ 13
Suppression d'une tâche _____________________________________________________________________ 13
Attribution du processeur réel__________________________________________________________________ 13
Attribution du processeur virtuel ________________________________________________________________ 13
Politiques d'ordonnancement_________________________________________________________ 14
Systèmes avec ou sans réquisition du processeur _______________________________________ 15
Services offerts Mtr86-68K _______________________________________________________16
Structure d’une application Mtr86-68K _________________________________________________ 16
Mise en evidence de la politique d’ordonnancement dans Mtr86-68K___________________________________ 17
Création et activation des d’instances multiples d’une même tâche ____________________________________ 18
Problèmes posés par l’accès concurrent aux fonctions et aux données partagées_____________ 19
Réentrance des fonctions_____________________________________________________________________ 19
Sections critiques ___________________________________________________________________________ 20
Verrouillage des sections critiques dans un même processus_________________________________________ 20
Modèles standard d'échanges de données entre taches et processus _______________________ 20
Modèle producteur-consommateur______________________________________________________________ 20
Implémentation du modèle producteur consommateur dans les systèmes multitâches temps
réel___________________________________________________________________________27
Implémentation des tubes dans Mtr86 Win32________________________________________27
Modèle lecteur - rédacteur ____________________________________________________________________ 29
Synchronisations des tâches et processus _____________________________________________ 32
Mécanismes de synchronisation directs__________________________________________________________ 32
Mécanismes de synchronisation indirects ________________________________________________________ 32
Exemples _________________________________________________________________________________ 32
Systèmes multitâches et systèmes temps réel -3-
Systèmes multitâches et systèmes temps réel
SYSTEMES MULTITACHES ET SYSTEMES TEMPS REEL
QUELQUES DEFINITIONS
Multitâche = exécution de fonctions de façon concurrente.
Multi processus = exécution de processus de façon concurrente.
Tâches = fonctions ou procédures exécutées de façon concurrente.
Temps Réel =
Chaque tâche d'un procédé de type contrôle-commande, est exécuté en réponse à des sollicitations
externes ou internes avec des contraintes de temps fixées par l'évolution du procédé à contrôler, par les
dialogues avec l'opérateur ou par l'écoulement du temps.
Pour chaque événement Ei (modification de l'état d'un capteur ou de toute information liée au
processus...), il est nécessaire de calculer un ensemble de fonctions Fj dépendant de l'état Q de la
commande. La durée du calcul t est fonction des algorithmes utilisés et de la puissance du processeur.
Le procédé contrôlé fixe une échéance de temps Te pour chaque calcul. La présence de cette
échéance caractérise un système temps réel.
Pour des procédés industriels, Te est compris entre quelques dizaines de microsecondes et quelques
centaines de millisecondes, voire quelques heures.
Suivant la nature des contraintes de temps et la complexité des traitements, diverses solutions peuvent
être retenues.
On distingue les systèmes à :
• Contraintes de temps faible et variées (t très inférieur à Te).
• Contraintes de temps faible avec quelques événements contraignants,
• Contraintes de temps fortes (t voisin de Te).
Dans le premier cas on parlera de temps réel mou, dans les deux derniers cas, de temps réel dur.
Un système temps réel est un système qui respecte les contraintes de temps.
ALGORITHMES ADAPTEES AUX CONTRAINTES DE TEMPS
CONTRAINTES DE TEMPS FAIBLES
Ce type de contrainte peut être solutionné par une
programmation en mode bouclé appelée également
gestion des entrées - sorties par scrutation. Les bouclages
en arrière en attente d'événements sont interdits, car ils
allongent la durée du cycle (ty) de façon indéterminée.
Le programme doit pouvoir scruter en permanence les
entrées avec un temps ty < Te (figure 1.).
Ce mode de programmation simple à mettre en œuvre, est
difficile à maintenir et à faire évoluer.
Lors de modifications ou d'ajouts de fonctions, les
contraintes de temps peuvent ne plus être respectées.
Pour éviter alors la réécriture complète du logiciel, la
seule solution consiste remplacer le calculateur par un
modèle plus puissant.
L’inconvénient majeur de ce mode de programmation est
qu’il fait cohabiter des contraintes de temps faible avec
contrainte de temps fortes, ces dernières imposant le
temps de cycle.
Ce mode de programmation est très utilisé essentiellement
du fait de sa simplicité.
lire les
entrées
détecter les
événements
traiter les
événements
affecter les
sorties
ty < Te
Figure 1 : gestion des entrées/sorties par scrutation.
Systèmes multitâches et systèmes temps réel -4-
Systèmes multitâches et systèmes temps réel
Exemple : programmation d'un terminal.
Cet exemple permet d'illustrer les points précédents.
Un terminal est un périphérique de dialogue opérateur permettant d'afficher sur sa sortie de dialogue (généralement
constituée d'un tube cathodique), les caractères arrivant sur son entrée de communication (figure 4).
Simultanément tout caractère frappé au clavier (entrée de dialogue) est transmis sur sa sortie de communication.
Les ports de communication sont généralement constitués de ports série (RS232, RS422, boucle de courant).
Le cahier des charges fixe les contraintes de temps suivantes :
• Vitesse de frappe maximale : 10 caractères / secondes ;
• Vitesse d'affichage constatée sur l'écran : 1000 caractères / secondes ;
• Vitesse de réception ou d'émission des caractères sur la ligne: 50..38400 Bauds, format 8 bits, 1 bit de start, 1 bit
de stop (10 bits au total).
Sur le diagramme de la figure 6 on fait apparaître deux tâches :
• Une tâche affichant le caractère présent sur le port série. Cette tâche est déclenchée par la réception d'un
caractère (tâche lire).
• Une tâche effectuant l'acquisition et l'envoi des caractères frappés au clavier. Cette tâche est déclenchée par la
frappe d'un caractère (tâche afficher).
caractère
touche
car. reçu
caractère et attributs
car. frappé
caractère ascii
Clavier
Port
série
Port
série
Ecran
LIRE
AFFICHER
Figure 4 : terminal fonctionnant par scrutation.
L'algorithme simplifié du programme terminal est donné à la figure 5. On supposera qu’un caractère a eu le temps
d’être transmis entre deux frappes successives au clavier.
caractère
frappé
Lire le
caractère.
Emettre le
caractère
caractère
reçu
Lire le car.
sur la ligne
Afficher le
caractère
ty
Tâche
LIRE
Tâche
AFFICHER
Figure 5 : terminal fonctionnant par scrutation.
Détermination des contraintes de temps
Systèmes multitâches et systèmes temps réel -5-
Systèmes multitâches et systèmes temps réel
Pour une vitesse de réception de 600 Bauds.
• tâche acquisition Te1 = 0,1s (indépendant de la vitesse de réception),
• tâche réception Te2 = (1+8+1)/600 = 16,7 10
-3
s
Vérification du respect des contraintes de temps
Pour déterminer le temps de scrutation il est nécessaire de prendre en compte le cas le plus défavorable, c'est à dire
lorsque qu'un caractère doit être affiché sur l'écran et qu'un caractère est lu au clavier.
Nous supposerons pour simplifier, que le temps scrutation (ty) est essentiellement liée à la vitesse d'affichage (1000
caractères/secondes).
Le temps de cycle est donc de :
ty = 10-3
s
La contrainte de temps la plus forte est :
Te2 = (1 + 8 + 1) / 600 = 16,7 10-3 s.
Ce qui vérifie ty << Te2 << Te1.
Si la vitesse de réception des caractères est 19200 Bauds, la contrainte sera de :
Te2 = 10 / 19200 = 0,52 10
-3
s; Te < ty
Ce qui ne vérifie plus ty << Te2, tout en satisfaisant Te1.
La contrainte Te2 n'étant pas satisfaite, le terminal perdra des caractères reçus sur la ligne série mais par contre ne
perdra aucun caractère frappé au clavier !
La programmation par scrutation ne convient pas dans les cas de contraintes de temps hétérogènes. On en conclut qu'il
faut dissocier les tâches à fortes contraintes de temps, de celles à faibles contraintes de temps, par un traitement hors de
la boucle scrutation.
CONTRAINTES DE TEMPS FAIBLES AVEC QUELQUES EVENEMENTS CONTRAIGNANTS
Si le temps de cycle de la boucle de scrutation ne permet pas de satisfaire un nombre réduit de contraintes, on associe les
fonctions concernées par ces contraintes à des interruptions matérielles.
Chaque interruption peut être liée à un ou plusieurs événements. Lorsque l'événement se produit, l'interruption arrête
l'exécution de la boucle de scrutation et lance un programme appelé tâche matérielle. Le calculateur sera ainsi
synchronisé sur ces événements et exécutera les traitements liés aux interruptions en suspendant le programme en
exécution ( voir figure 7 ).
Cette solution présente plusieurs avantages :
• Les événements liés aux interruptions ne nécessitent aucune attention dans la boucle de scrutation. La durée de
la boucle n'a pas d'importance par rapport à l'événement considéré si elle ne gère pas un procédé possédant des
contraintes de temps.
• La boucle de scrutation n'est plus ralentie par le test systématique de présence d'événements asynchrones,
l'efficacité du traitement réalisé s'en trouve accru.
• Le rythme d'échange des informations est fixé par le dispositif lié aux interruptions en non par des contraintes
de programmation de la boucle de scrutation.
Tâche de
fond (boucle de
scrutation)
Traitement P1
evénement 1
Traitement P2
evénement 2ty0 ty1 ty2
I1 I2
Figure 6 : gestion des entrées/sorties avec quelques événements contraignants.
Systèmes multitâches et systèmes temps réel -6-
Systèmes multitâches et systèmes temps réel
On doit avoir à l’esprit que :
• Lorsque survient une interruption, la boucle de scrutation est stoppée et peut même s'arrêter complètement si
plusieurs interruptions surviennent à un rythme très rapide. Ce phénomène peut être mis très facilement
évidence en pilotant une ligne d'interruption à l'aide d'un générateur de signaux périodique.
• La programmation des échanges de données entre la boucle de scrutation et le(s) programme(s) d'interruption
peut s'avérer délicate. Il n'est trivial de réaliser des échanges fiables entre des programmes fonctionnant de façon
asynchrone. Ce point sera particulièrement abordé par la suite.
Vérification des contraintes de temps
Le traitement d'une interruption allonge le temps de cycle du programme interrompu. Les contraintes temporelles
devront donc être analysées avec soin. Plusieurs cas doivent êtres envisagés selon que les interruptions sont emboîtées
ou non.
1. Cas d'une seule interruption (I1) de récurrence f1=1/T1 (figure 6)
Soient deux procédés possédant des contraintes de temps hétérogènes. Le traitement du procédé à forte contrainte
(P1) est géré par interruption, le traitement du procédé à faible contrainte de temps (P0) est géré dans la boucle de
scrutation :
On connaît :
• T1 période de l’interruption I1. Ce temps correspond à l’échéance de temps fixé par le procédé à contrôler : Te1
• ty1 temps de calcul (continu) de la fonction P1.
• Te0 échéance de temps du procédé géré par P0
Pour ne pas perdre des signaux d'interruptions ou pour éviter une récursion infinie ont doit avoir :
ty1 < T1
La durée du traitement de l'interruption doit être plus faible que sa périodicité !
I1 I1
t
Code
exécuté
Boucle de
scrutation
code
d'interruption
ty1
T1
P0
P1
Figure 7 : exécution d’une interruption périodique.
La durée de traitement de P0 se trouve multiplié par un facteur K égal environ à :
1
1 1
T
K
T ty
≈
−
Pour satisfaire la contrainte Te0 on doit avoir :
K ty ty Te* = ' <0 0 0
Lorsque le programme doit gérer plusieurs interruptions simultanées, deux cas sont à envisager : les interruptions
sont traitées séquentiellement ou sont traitées parallèlement. On parlera dans ce dernier cas d'interruptions
emboîtées.
2. Cas de plusieurs interruptions traités de façon séquentielles :
Soient :
T1 , période de l’interruption I1, fréquence f1 = 1 / T1
ty1, temps de calcul (continu) de la fonction P1.
T2 , période de l’interruption I2 , fréquence f2 = 1 / T2 , et T2 < T1.
ty2 , temps de calcul (continu) de la fonction P2.
Te0 , échéance de temps du procédé géré par P0
Systèmes multitâches et systèmes temps réel -7-
Systèmes multitâches et systèmes temps réel
I1Code
exécuté
Boucle de
scrutation
code
d'interruption
P0
P1
I2 I1 I2
ty2
T1
ty1
code
d'interruption
P2 retard dans le
traitement de P2
du au mécanisme
d'it non emboîté
t
T2
Figure 8 : exécution d’interruptions périodiques non emboîtées.
Pour ne pas perdre des signaux d'interruptions on doit avoir :
ty ty T T T1 2 2 2 1+ < ( < )
La durée de traitement de P0 se trouve multiplié par un facteur K égal environ à :
2
2 1 2-( + )
T
K
T ty ty
≈
Pour satisfaire la contrainte Te0 on doit avoir :
ty K Te0 0 0* <
3. Cas d'interruptions emboîtées
Dans ce modèle (figure 9), une interruption peut être exécutée durant l'exécution d'une interruption moins
prioritaire.
L'interruption la plus prioritaire doit être celle de récurrence la plus élevée :
Soient :
T1 , période de l’interruption I1,
ty1 , temps de calcul (continu) de la fonction P1.
T2 , période de l’interruption I2, et T2 < T1
ty2 , temps de calcul (continu) de la fonction P2.
Te0 , échéance de temps du procédé géré par P0
Systèmes multitâches et systèmes temps réel -8-
Systèmes multitâches et systèmes temps réel
I1
t
Code
exécuté
P0
P1
P2
I2 I2 I2 I2 I2I2 I1
T2
T1
ty2
Figure 9 : exécution d’interruptions emboîtées.
Pour ne pas perdre de signaux d'interruptions et pour éviter une récursion infinie ont doit avoir :
ty2 < T2
Le traitement de l'interruption P2 emboîtée dans T1 allonge le temps de traitement ty1 d'un facteur K2 égal à :
2
2 2
2
-
T
K
T ty
≈
Pour satisfaire la contrainte T1 doit avoir :
1 2 1* <ty K T
Le traitement de l'interruption P1 allonge à son tour le temps de traitement de la boucle P0 d'un facteur K1 :
( )
1
1
1 1 2- *
T
K
T ty K
≈
Pour satisfaire la contrainte Te0 doit avoir :
0 1 0* <ty K T
Systèmes multitâches et systèmes temps réel -9-
Systèmes multitâches et systèmes temps réel
Exemple : terminal avec réception de caractères à vitesse élevée
Le diagramme figure 10 comporte trois tâches :
• Une tâche déclenchée par l'interruption liée à la réception d'un caractère (tâche lire_ligne). Celle-ci place le
caractère reçu dans un tampon (FIFO). Le débordement du tampon n'est pas envisagé.
• Une tâche, dont l'exécution dépend d'une condition interne (taux de remplissage du tampon) : afficher.
• Une tâche dont l'exécution dépend de conditions externes non liées à une interruption (caractère frappé) : lire.
caractère
touche
Interruption
caractère
et attributs
status
caractère
Clavier
Port
série
Port
série
Ecran
LIRE
AFFICHERLIRE
ligne
Tampon
caractère caractère
Figure 10 : diagramme de flux de données d'un terminal fonctionnant par interruption.
L'algorithme simplifié du programme terminal est donné à la figure 11.
Hypothèses :
• Temps de cycle cumulé des taches afficher et lire : ty0 = 20 10-3s ;
• Echéance de temps fixée par la tâche lire : Te0 = 0,1s ;
• Durée du programme d'interruption : ty1 = 0,255 10-3s.
Pour une réception à 9600 bauds :
f1 = 9600 / 10 bits = 960 interruptions/secondes.
Les contraintes de temps sont :
T1 = 10 / 9600 = 1,04 10-3s ;
ty1 vérifie la contrainte T1 (0,255 10-3 s < 1,04 10-3 s) ;
ty0 * T1/(T1-ty1) = 2 10-3 * 1,04 10-3/(1,04 10-3 - 0,255 10-3)= 2,65 10-3s
Vérifie la contrainte Te0 (0,265 10-3 s < 0,1 s) ;
Systèmes multitâches et systèmes temps réel -10-
Systèmes multitâches et systèmes temps réel
caractère
frappé
Lire le
caractère.
Emettre le
caractère
tampon # vide
Afficher le
caractère
ty0
Tâche LIRE
Tâche
AFFICHER
Caractère reçu
Lire le
caractère et
gérer le tampon
Tâche matérielle
LIRE ligne
Extraire le car.
gérer le tampon
ty1
Restituer les
registres utilisés
Acquitter
l'interruption
Sauvegarder les
registres utilisés
Figure 11 : terminal fonctionnant par interruption.
Contraintes de temps pour une réception à 38400 bauds :
Te1 = 10 / 38400 = 0,26 10-3 s ;
ty1 vérifie la contrainte Te1 ( 0,255 10-3 s < 0,26 10-3 s ) ;
ty0 * T1/(T1-ty1) = 2 10-3 * 0,26 10-3/(0.26 10-3 - 0,255 10-3) = 1.04 s
1.04 s > 0,1 s
La contrainte Te0 n'est pas satisfaite : il faut alors lier la frappe d'un caractère à une interruption (de priorité
inférieure à l'interruption de réception).
La figure 12 représente le diagramme de flots de données correspondant. Celui-ci fait apparaître quatre tâches :
• Une tâche matérielle déclenchée par l'interruption due à la réception d'un caractère (tâche lire ligne). Celle-ci
place le caractère reçu dans un tampon (FIFO). Le débordement du tampon n'est pas envisagé.
• Une tâche matérielle déclenchée par l'interruption due à la frappe d'un caractère clavier (tâche lire caractère).
Celle-ci place le caractère reçu dans un autre tampon. Le débordement du tampon n'est pas envisagé.
• Deux tâches logicielles dont l'exécution dépend de conditions internes ( taux de remplissage des tampons
d'émission et de réception ) : tâche afficher et émettre.
Systèmes multitâches et systèmes temps réel -11-
Systèmes multitâches et systèmes temps réel
caractère
caractère
Interruption
caractère et attributs
Interruption
caractère
Clavier
Port
série
Port
série
Ecran
LIRE EMETTRE
LIRE
ligne
Tampon
caractère caractère
Tampon
caractère
AFFICHER
Figure 12 : diagramme de flux de données d'un terminal fonctionnant par interruption.
L'algorithme de programmation du programme est donné figure 13.
// Boucle de scrutation
Faire
Si Tampon de réception # vide Alors
extraire, afficher le caractère en tête du tampon
Si Tampon clavier # vide Alors
extraire, émettre le caractère en tête du tampon
Fin
// Programme d'interruption de réception d'un caractère
Sauvegarder les registres utilisés
Lire le caractère, le mémoriser dans le tampon de réception
Restituer les registres utilisés
Acquitter l'interruption
Fin
// Programme d'interruption de lecture du clavier
Sauvegarder les registres utilisés
Lire le code clavier et le transformer en code ASCII
Mémoriser le code ASCII dans le tampon d'émission
Restituer les registres sauvegardés
Acquitter l'interruption
Fin
Figure 13 : algorithme de principe d'un terminal d'un terminal fonctionnant par interruption.
Pour assurer une gestion correcte des tampons, le code barré d’un trait vertical, doit être ininterruptible.
Au lecteur de vérifier si les contraintes de temps sont respectées.
CONCLUSION PARTIELLE
Lorsque les contraintes de temps sont nombreuses et variées, la programmation de ce type d'application peut devenir
très complexe. On préfère alors contrôler les tâches à l’aide un programme spécial appelé moniteur temps réel. Celui-ci
se charge de lancer, de synchroniser et d’arrêter les tâches. Il fournit en outre de puissants moyen de communication
entre les tâches concurrentes.
Systèmes multitâches et systèmes temps réel -12-
Systèmes multitâches et systèmes temps réel
SYSTEMES MULTITACHES ET NOYAU TEMPS REEL
Dans les exemples précédant est apparu la notion de tâches : tâche matérielle (tâche liée aux interruptions) et tâche
logicielle (tâche de fond). Un moniteur temps réel et un système multitâche permettent d’exécuter des fonctions de
façon concurrente en leur attribuant :
• Un niveau de priorité,
• Pour les systèmes monoprocesseur : un quantum de temps processeur
• Pour les systèmes multiprocesseur : un processeur.
ETATS D’UNE TACHE
A un instant donné, chaque tâche se trouve dans des états suivants :
(CREE)
activer
créer
suspendre
reveiller
physique
Non
CREE
supprimer
détruire
détruire
détruire
supprimer
supprimer supprimer
δt
δt
BLOQUE
EXEC
virtuelle
PRETDORMANT
Le passage d'un état à un autre s'effectue à l'aide d’appel de requêtes programmées dans le noyau.
L'ensemble de ces requêtes forme les primitives de l'exécutif temps réel ou de l'exécutif multitâche.
Les différents états possèdent les propriétés suivantes :
NON CREE
Tâche inconnue de l'exécutif.
DORMANT OU CREE
Tâche connue de l'exécutif. Un identificateur, ainsi qu'une zone de pile est attribué à la tâche. La tâche reste dans cet état
tant qu'une requête d’activation ne la fait pas évoluer.
Systèmes multitâches et systèmes temps réel -13-
Systèmes multitâches et systèmes temps réel
PRET OU ELIGIBLE
Une tâche à l'état prêt est candidate pour l'exécution. Son lancement ne dépend que de sa priorité par rapport aux autres
tâches éligibles ou en cours d'exécution. Lorsque celle-ci devient prioritaire, l'ordonnanceur lui attribue le processeur.
L'exécution de la tâche commence alors à son début.
EN EXECUTION
Une tâche en exécution est une tâche en possession du processeur. Dans un système monoprocesseur, plusieurs tâches
de même priorité peuvent être en exécution mais une seule tâche est en possession du processeur réel, chaque tâche se
voyant attribuer un quantum de temps processeur. Une tâche reste dans cet état tant qu'elle est prioritaire et qu'elle ne
n’exécute pas une requête de suspension directe ou indirecte.
BLOQUE OU SUSPENDU
Une tâche à l'état bloqué ou à l'état suspendu ne possède plus le processeur et son état est sauvegardé en vue de sa
reprise ultérieure..
TRANSITIONS ENTRE ETATS D'UNE TACHE
Le franchissement des transitions et la conséquence d'appels directs ou indirects aux primitives de l'exécutif.
CREATION D'UNE TACHE (CREE)
Créer une tâche consiste à la faire connaître de l'exécutif : fournir une adresse de début, une taille de pile et un niveau
de priorité. En retour le noyau fournit un identificateur permettant de référencer la tâche.
La détermination de la taille de pile n'est pas chose aisée. La pile est utilisée pour loger les variables locales et les
adresses de retour lors des appels de fonctions. Elle sert également à mémoriser le contexte du processeur lors des
commutations de tâche. Une pile insuffisante est souvent la cause de dysfonctionnement mystérieux. En marquant la
pile de la tâche, lors de la création, il est possible de déterminer en fin de programme la quantité de pile consommée et
ainsi optimiser la taille de la pile. Ce mécanisme est utilisé dans plusieurs noyaux temps réels dont MTR86® DOS.
Certains systèmes utilisent les mécanismes d'exceptions du processeur pour déterminer les débordements de pile et
réallouer automatiquement une pile supplémentaire. Cette opération est cependant très dispendieuse en temps
processeur.
ACTIVATION D'UNE TACHE (ACTIVE)
L'activation d'une tâche correspond à une demande d'exécution. L'activation peut être immédiate, différée ou cyclique.
L'activation immédiate consiste à faire passer une tâche de l'état dormant à l'état prêt. En aucun cas elle ne correspond à
l'appel direct de la fonction tâche : le lancement d'une tâche reste dévolu à l'ordonnanceur.
Les demandes d'activation sont généralement mémorisées. Ainsi à trois demandes successives d'activations
correspondent trois cycles ( on suppose que la tâche de termine ) :
DORMANT → PRET → EXEC → DORMANT.
Une activation différée consiste à lancer une tâche après un délai précis.
Une activation cyclique consiste à relancer avec une période précise.
Ces dernières requêtes ne sont pas toujours disponibles dans tous les noyaux.
ATTRIBUTION DU PROCESSEUR A LA TACHE
La tâche est lancée depuis son début. Son début d’exécution n'est pas forcément immédiat : si d'autres tâches (de même
priorité) sont déjà dans cet état, la tâche attendra son tour suivant le mécanisme du tourniquet. Cette transition n'est pas
maîtrisée par le programmeur, mais par l'ordonnanceur.
SUSPENSION D'UNE TACHE
La tâche est stoppée et son contexte est enregistré de façon à pouvoir reprendre exactement à l'endroit ou elle s’est
arrêtée. La suspension de la tâche est obtenu par un appel direct (dort) ou indirect à travers une primitive et sur des
conditions particulières (voir par la suite).
REPRISE D'UNE TACHE BLOQUEE
Cette transition n'est pas contrôlée directement. Lorsque toutes les conditions sont réunies pour continuer une tâche
(priorité maximale de la tâche par rapport aux autres, disparition des conditions de blocage) le contexte de la tâche est
rechargé et celle-ci est relancée à l'endroit où elle s'était arrêtée.
FIN D'UNE TACHE OU DESTRUCTION D'UNE TACHE
Une tâche terminée ou détruite est replacée à l'état prêt. La fin d'une tâche peut être obtenue par un appel explicite au
noyau ou implicite lors de la fin de la tâche.
SUPPRESSION D'UNE TACHE
Pour optimiser l'usage des ressources, une requête supprime remet une tâche à l'état non créé.
ATTRIBUTION DU PROCESSEUR REEL
Cette transition a lieu de façon tous les quantum pour une tâche à l'état EXEC. Elle n'est pas maîtrisable directement.
ATTRIBUTION DU PROCESSEUR VIRTUEL
Cette transition a lieu chaque fois qu'une tâche a utilisé le processeur réel durant un quantum.
Systèmes multitâches et systèmes temps réel -14-
Systèmes multitâches et systèmes temps réel
POLITIQUES D'ORDONNANCEMENT
La politique d'ordonnancement définit le critère utilisé par le noyau pour élire une tâche. Diverses politiques
d'ordonnancement sont utilisées :
• Ordonnancement circulaire ou tourniquet : Chaque tâche possède la même priorité. Les tâches se partagent le
processeur sans affinité particulière. Convient rarement pour les systèmes temps réel.
16
0609
CPU
tâches actives
tâches éligibles
tâches
suspendues
tâches
terminées
file des tâches actives
02 04 03
0810
file des tâches suspendues
• Ordonnancement à priorité fixe : Chaque tâche possède un niveau de priorité différent. La tâche exécutée est
toujours la plus prioritaire. Bien adapté aux systèmes temps réels.
08
04
10
07
03
0609
CPU
tâches actives
tâches éligibles
tâches
suspendues
tâches
terminées
filedestâchesactives
file des tâches suspendues
Priorités
• Ordonnancement à classe de priorités : Combinaison des deux politiques précédentes. Les tâches sont groupées
par classe de priorité au sein des quelles l'ordonnancement est de type circulaire. Ce système présente le
maximum de souplesse.
16
0609
CPU
tâches actives
tâches éligibles
tâches
suspendues
tâches
terminées
file des tâches actives
02 04 03
12 05
11 07 13
0810
file des tâches suspendues
Priorité
Systèmes multitâches et systèmes temps réel -15-
Systèmes multitâches et systèmes temps réel
• Ordonnancement par priorité avec modification dynamique de la priorité en fonction de l'age de la tâche. Cette
politique fait perdre progressivement la priorité aux tâches : les tâches de courtes durée sont toujours exécutées
en premier. Cette politique substitue au contrôle des tâches exercées par le programmeur, par celui exercé par le
noyau. Permet de faire fonctionner (mal) des programmes présentant des erreurs de conception. A déconseiller
pour la commande temps réel.
SYSTEMES AVEC OU SANS REQUISITION DU PROCESSEUR
Les systèmes multitâches et les noyaux temps réels se divisent en deux catégories :
• Noyaux sans réquisition du processeur (no préemptive scheduling) ou encore appelés systèmes coopératifs,
• Noyaux avec réquisition du processeur (préemptive scheduling).
Noyau coopératif
Avec un noyau coopératif, le programmeur décide de l’instant de commutation par un appel explicite. Selon la situation
présente lors de l'appel, l'ordonnanceur décide, en fonction des priorités des tâches en cours, si celle-ci doit ou non se
poursuivre. Si la priorité des tâches éligibles est supérieure à la tâche courante, le noyau la suspend et lance la tâche de
priorité maximale.
Ce type de noyau, très efficace mais moins tolérant aux erreurs de conception des applications, est généralement utilisé
dans les applications à très forte contrainte de temps.
Si pour une raison quelconque une tâche se bloque (erreur de programmation ou erreur matérielle), l'ensemble des
tâches se bloque également.
Windows 3.1 est un exemple typique de système coopératif.
Noyau préemptif
Un noyau avec réquisition du processeur alloue celui-ci de façon transparente aux tâches ( toujours en fonction des
priorités en cours ). La tâche qui se trouve préemptée n'a aucune possibilité de le savoir.
Ce mécanisme est réalisé par un programme d'interruption lancé par une interruption périodique à forte priorité. Ce
programme d'interruption explore les files de tâches prêtes ou suspendues et attribue le processeur à l'une d'elle suivant
une politique d'ordonnancement spécifique à chaque noyau.
Temps de latence
Les sections de code non interruptibles perturbent le fonctionnement des systèmes coopératifs et préemtif. Ces sections
doivent être très courtes de façon à diminuer le temps s'écoulant en apparition d'une interruption et son traitement
effectif. Ce temps, généralement appelé temps de latence, est des critères qui distinguent les noyaux temps réels mou
(systèmes multitâches), des noyaux temps réel durs.
Temps réel dur, temps réel mou
Un noyau temps réel mou ne spécifie ni les temps de commutation de contexte, ni les temps de latence.
Un noyau temps réel dur garanti un temps maximal de commutation de contexte et un temps de retard maximal entre
l'apparition d'une d'interruption et son début de traitement.
Les temps de commutation de contexte sont de l'ordre de 1 à 100 microsecondes (en fonction du noyau et du
processeur), soit 10000 commutations / secondes dans le cas le plus défavorable.
Le temps de latence est généralement inférieur à 50 microsecondes.
Nota : la notion de temps réel dur et mou est très subjective : un noyau mou exécuté sur un processeur récent est plus
performant qu'un noyau dur exécuté sur un processeur plus ancien. La puissance actuelle des processeurs permet
d'utiliser des noyaux temps réel mou de nombreux cas d'applications de type contrôle-commande.
Systèmes multitâches et systèmes temps réel -16-
Systèmes multitâches et systèmes temps réel
SERVICES OFFERTS MTR86-68K
Ce chapitre sera consacré aux mécanismes de gestions des tâches, aux problèmes posés par l’accès concurrent aux
données partagées, aux mécanismes de communications et aux mécanismes de synchronisation. Le support utilisé est le
noyau Mtr86.
Mtr86 existe sous différentes formes :
• Mtr86 Dos : noyau temps réel dur.
• Mtr86 68K pour EID210 : noyau temps réel dur.
• Mtr86 Win32 : noyau temps réel mou (dans sa forme actuelle)
• Mtr86 Win32 Objet : noyau temps réel mou (dans sa forme actuelle)
STRUCTURE D’UNE APPLICATION MTR86-68K
// ex1dos.c
// Les fichiers à inclure dépendent de la cible
#include "mtr86.h"
TACHE init(void);
TACHE T(void);
// Tâche d’initialisation. Possède la priorité maximale. Aucune autre tâche ne peut
// s’exécuter tant que celle ci n’est pas terminée ou suspendue
TACHE init(void)
{
// créer les sémaphores, tubes, et autres mécanismes
//...
// créer et éventuellement activer les tâches
active(cree(T, 1, 512)); // crée et active la tâche T. priorité 1, 512 octets de pile.
// suspendre ou terminer la tâche. La fin de l’application est obtenu par un
// appel à la primitive mtr86exit(0) dans une tâche quelconque;
}
TACHE T(void)
{
// ...
}
main()
{
// Lance la tâche init avec une taille de pile de 512 octets
start_mtr(init, 512);
}
NOTA
La taille de la pile est fonction des variables locales des tâches et contient le contexte de la tâche après sa commutation.
Certaines fonctions de la bibliothèque C comme printf nécessitent 1Ko de pile. Il est prudent de déclarer 1Ko de pile au
départ, puis la réduire jusqu’à ce que l’application se « plante », on ajoute ensuite environ 20% de pile.
Systèmes multitâches et systèmes temps réel -17-
Systèmes multitâches et systèmes temps réel
MISE EN EVIDENCE DE LA POLITIQUE D’ORDONNANCEMENT DANS MTR86-68K
// CEx1.c
#include "mtr86.h"
TACHE T1(void)
{
long j, i;
// Durant cette séquence T2 et T3 sont arrêtés
printf("T1: actif (priorite 1) et bloque T2 et T3 (priorite 2)n");
for (j=0; j < 1000000; j++);
// remarque : cette séquence alloue le processeur pour un travail inutile (pour peut
// être effectuer une temporisation). Cette façon de procéder gaspille le temps CPU
// et ne doit jamais être utilisée.
// Durant la séquence suivante T2, T3 sont actifs à chaque appel de dort durant 1 quantum.
printf("T1: se suspend périodiquement et libère du temps processeur pour T2 et T3nn");
for ( j=0; j < 250; j++) {
for (i=0; i < 10000; i++); // simuler l'activité de la tâche
dort(1); // T2 et T3 actifs durant 1 quantum
}
MONITOR // accès exclusif de T1 à la ressource écran ( voir par la suite )
gotoxy(1,8);
printf("T1 : fin -> accélération de T2 et T3nn");
ENDM // fin accès
} // <- T1 est arrêté, ce qui alloue le processeur à T2 et T3 : l'affichage est accéléré
TACHE T2(void)
{
// T2 est exécuté de façon concurrente avec T3
unsigned j, i;
for (j=0 ; j < 300 ; j++) {
MONITOR // accès exclusif de T2 à la ressource écran ( voir par la suite )
gotoxy(1,6);
cprintf("T2 : j=%dn",j);
ENDM // fin accès
for (i=0; i < 1000; i++); // simuler l'activité de la tâche
}
} // <- T2 est arrêté
TACHE T3(void)
{
unsigned j,i;
// T3 est exécuté de façon concurrente avec T2
for (j=0 ; j < 500 ; j++) {
MONITOR // accès exclusif de T2 à la ressource écran ( voir par la suite )
gotoxy(1,7);
cprintf("T3 : j=%dn",j);
ENDM // fin accès
for (i=0; i < 300; i++); // simuler l'activité de la tâche
}
mtr86exit(0) ; // le noyau est stoppé. Sortie après start_mtr()
// jamais exécuté
}
// Tâche d'initialisation. Possède la priorité maximale. Aucune autre tâche ne peut
// s'exécuter tant que celle ci n'est pas terminée ou suspendue
// La tâche init crée et active 3 tâches.
// La tâche T1 est la plus prioritaire et préempte le processeur au détriment des
// tâches T2 et T3.
TACHE init(void)
{
printf("Init debutn");
active(cree(T1, 1, 1024)); // crée et active la tâche T. priorité 1, 512 octets de pile.
active(cree(T2, 2, 1024)); // crée et active la tâche T. priorité 2, 512 octets de pile.
active(cree(T3, 2, 1024)); // crée et active la tâche T. priorité 2, 512 octets de pile.
printf("Init finn");
} // -> A partir d'ici la tâche T1 est lancée
main()
{
// Lance la tâche init avec une taille de pile de 1024 octets
clsscr();
printf("----------------Exemple ex1--------------------n");
start_mtr(init,1024);
}
Systèmes multitâches et systèmes temps réel -18-
Systèmes multitâches et systèmes temps réel
CREATION ET ACTIVATION DES D’INSTANCES MULTIPLES D’UNE MEME TACHE
Lorsque les tâches effectuent un travail similaire, il est souhaitable de créer des instances multiples d’une même tâche.
Le problème qui se pose alors à la tâche est de pouvoir s’identifier.
L’exemple suivant crée et active 8 instances d’une même tâche, chacune affichant la valeur d’un compteur sur un
emplacement différent de l’écran.
EXEMPLE CEX2.C
#include "mtr86.h"
TACHE init(void); /* PROTOTYPES DES TACHES */
TACHE T(void);
TACHE init(void)
{
int j;
// dsp_stk(); // visualise les consummations en fin de prog
for (j=0;j<8;j++)
active(cree(T, 2, 1024)); /* créer et lance 60 tache */
dort(cvrtic(10000));
mtr86exit(0);
}
TACHE T(void)
{
int j=0;
int x, y,tache;
for (;;) {
tache=tache_c()-2; /* lit le num‚ro de la tƒche courante (1er Nø= 2)*/
x=((tache%10)+1)*7;
y=(tache/10)+1;
MONITOR
gotoxy(x,y+10);
printf("%dn", j++);
ENDM
dort(10);
}
}
/*
** M A I N
*/
main()
{
clsscr();
printf(" Exemple de creation d'instances multiples d'une meme tache.rn"
"init() creee 8 taches T() identiques, chacune incremente un compteurrn"
"et l'affiche sur l'ecran.rn"
"Fin dau bout de 10s.rn");
start_mtr(init, 1024);
}
Systèmes multitâches et systèmes temps réel -19-
Systèmes multitâches et systèmes temps réel
PROBLEMES POSES PAR L’ACCES CONCURRENT AUX FONCTIONS ET AUX DONNEES PARTAGEES
REENTRANCE DES FONCTIONS
Une fonction est réentrante si elle s'exécute correctement lorsqu'elle est appelée simultanément par plusieurs tâches ou
par un programme d'interruption. Une fonction effectuant des récursions directes ou indirectes doit être réentrante. Une
fonction réentrante ne peut modifier des variables situées à des emplacements fixe de la mémoire (variables statiques en
'C' ou ‘C++’). Toutes les variables utilisées et modifiées par une fonction réentrante doivent être situées dans la pile ou
les registres du processeur.
Voici un exemple de fonction non réentrante:
int fac;
int factorielle( int n )
{
fac = 1;
while ( n != 0 ) {
fac = fac * n;
n--;
}
return ( fac );
}
TACHE A(void)1
{
...
factorielle(5);
...
}
TACHE B(void)
{
...
factorielle(3);
...
}
Deux taches A et B effectuent simultanément l'appel à factorielle. La fonction factorielle n'est pas réentrante du fait que
le variable fac est de type static, donc situé à un emplacement fixe de la mémoire.
Supposons que les tâches A et B soient lancés simultanément et que la tâche B s'exécute en premier et effectue fac = 1.
Supposons que la tâche A prend possession du processeur juste après cette affectation et réalise le calcul factorielle(5).
Lorsque la tâche A se termine et le calcul de factorielle(5) a donné à fac la valeur 120 (!5). Le calcul reprend ensuite
dans B avec fac = 120 et fournit évidemment un résultat faux pour factorielle(3).
Pour rendre la fonction factorielle réentrante il suffit de déclarer fac dans le corps de la fonction (avec le type auto ou
register). L'appel de factorielle par une tâche créé une nouvelle variable fac dans la pile de la tâche. Comme chaque
tâche possède sa propre pile, la fonction factorielle peut travailler simultanément avec plusieurs tâches.
L'exécution d'une fonction non réentrante n'a ici pour conséquence qu'une erreur de calcul. Il en serait tout autrement si
la fonction doit prendre des décisions (test sur la valeur d’une variable à accès concurrent).
Une fonction réentrante ne doit pas appeler une fonction non réentrante sous peine de devenir à son tour non réentrante.
Une fonction doit être réentrante si elle est susceptible d'être appelée simultanément par plusieurs tâches.
Utilisation de la librairie C et C++ MsDos et GNU
Un certain nombre de fonctions de la librairie C ne sont pas réentrantes.
La bibliothèque mathématique utilisant les flottants n'est jamais réentrante. Cela est dû au procédé utilisé pour retourner
les résultats. Pour Borland C++ par exemple, les flottants sont retournés dans la pile du NPD (émulée ou non). Lorsque
le NPD est émulée le résultat est placé en mémoire statique. Dans le cas contraire (présence effective du coprocesseur),
le résultat est situé dans la pile du processeur mathématique et la réentrance est assurée à condition d'autoriser les
commutations de contexte du coprocesseur.
Les fonctions d'allocation mémoire (malloc, calloc, free, sbrk ...) ne sont pas réentrantes car elles utilisent une variable
globale pour gérer l'extension du tas.
Les fonctions E/S fichier utilisant des tampons (fopen, fclose, fwrite, fread, printf, fprintf ...) ne sont pas réentrantes car
elles utilisent les fonctions d'allocation de mémoire dynamique (malloc, ...) pour créer les tampons.
Les fonctions d'une l'application compilée avec l'option de vérification de pile ne sont pas réentrantes.
La plupart des appels bios ne sont pas réentrant particulièrement les E/S clavier et console.
Systèmes multitâches et systèmes temps réel -20-
Systèmes multitâches et systèmes temps réel
SECTIONS CRITIQUES
Une section critique est une zone de programme où se produit un accès concurrent et dont le résultat des traitements et
fonction de l’ordonnancement des tâches ou des processus.
Un programme multitâche ou multiprocessus absolument verrouiller l’accès aux sections critiques en en autorisant
l’accès à une seule tâche à la fois. La multiplicité des sections critiques produit à un code peu efficient du fait des appels
au noyau destiné à verrouiller les sections critiques.
VERROUILLAGE DES SECTIONS CRITIQUES DANS UN MEME PROCESSUS
Une section critique peut être verrouillée par différents moyen : moniteurs, gestionnaire de ressource, mutex,
sémaphores.
VERROUILLAGE DES SECTIONS CRITIQUES PAR MONITEUR ET PAR GESTIONNAIRE DE RESSOURCES
Mtr86-68K
Mtr86 offre un seul moniteur pour permettre le verrouillage rapide à une seule ressource généralement l’écran :
MONITOR
//instructions d’accès à l’écran
ENDM
Mtr86 Dos et Mtr86Win32 disposent également d’une méthode plus générale constitué par le gestionnaire de
ressources.
Une ressource (utilisable uniquement au sein du même processus) est crée par la requête suivante :
RESSOURCE r1 ; // doit être global
...
r1 = r_cree();
Une section critique est verrouillée par les appels suivant :
r_wait(r1); // verouillage par le verrou r1
... // section critique
r_signal(r1); // déverrouillage de la section critique
Le gestionnaire de ressource s'assure que seule la tâche ayant pris possession de la ressource par r_wait peut la libérer.
MODELES STANDARD D'ECHANGES DE DONNEES ENTRE TACHES ET PROCESSUS
L'exclusion mutuelle des sections critiques est un mécanisme rudimentaire de verrouillage lorsqu'il est utilisé pour
échanger des données. Dans ce type d'échange, il n'y a aucune synchronisation entre les protagonistes. Le seul service
fourni par l'exclusion mutuelle est la garantie qu'une donnée est lue ou écrite de façon atomique, ou que dans l'intervalle
de temps entre la lecture de la donnée et son test, celle-ci n'a pas été modifiée.
Lorsqu'on désire synchroniser les échanges entre protagonistes deux modèles sont utilisés :
• Le modèle producteur-consommateur
• Le modèle lecteur-rédacteur
MODELE PRODUCTEUR-CONSOMMATEUR
Ce modèle est la base de nombreux échanges de données que ce soit au cœur d'un système d'exploitation ou au sein
d'applications. Son importance est telle que la plupart des noyaux fournissent directement ce type d'échange (tubes,
messages, ...).
Les règles de production et de consommation sont les suivantes :
• Une donnée ne peut être consommée que si a été produite,
• Une donnée ne peut être produite que si la donnée précédente a été consommée,
Il est résulte qu'une même donnée ne peut être consommée ou produite plusieurs fois.
L'implémentation de ce mécanisme est basé sur la propriété des sémaphores.
LES SEMAPHORES
Les sémaphores ont été proposés pour la première fois en 1965 par E. W. Dijkstra. Dijkstra propose de compter le
nombre de tâches endormies ou les réveils en attente à l'aide d'une variable appelée sémaphore.
Un sémaphore est constitué d'un entier s signé pouvant prendre des valeurs positives et négatives ou nulles et d'une file
d'attente mémorisant (indirectement) les contextes des tâches ou processus en attente. L'accès à la variable s est effectué
dans une section critique.
Systèmes multitâches et systèmes temps réel -21-
Systèmes multitâches et systèmes temps réel
Deux primitives permettent de manipuler le sémaphore : P(s) et V(s).
P(s) :
s <- s-1
Si s < 0 Alors
bloquer la tâche courante
mémoriser l'identificateur de la tâche courante dans la file f(s)
Fin
V(s) :
s <- s+1
Si s ≥ 0 Alors
relancer de la tâche en tête de la file f(s)
Fin
La modification et le test de s (zone en gras) constituent une section critique protégé par un mécanisme d'exclusion
mutuel non représenté.
Implémentation des sémaphores dans Mtr86
Création d'un sémaphore
SEMAPHORE s1; // variable globale
...
s1 = s_cree(VALEUR_INITIALE);
P(s)
s_wait(s1, TIMEOUT);
Pour éviter tout risque d'interbloquage, la durée d'attente peut être bornée par une valeur TIMEOUT. Si sa valeur est 0,
l'attente n'est pas bornée et s_wait retourne toujours OK. Si l'opération est bornée la valeur retournée est TIM si le délai
d'attente est dépassé.
V(s)
s_signal (s1); // incrémente le sémaphore
Exemples d'utilisation
Création
// Instanciation d'un tableau de sémaphores, puis création
Semaphore s[10];
...
for (int j=0; j<10; j++)
s[j].cree(1); // valeur initiale 1, valeur max 2048
// Instanciation d'un sémaphore local
Semaphore s1(1); // valeur initiale 1, valeur max 2048
// Instanciation d'un sémaphore inter processus
Semaphore s2(1, VALEUR_MAX, "Semaphore2" );
// Instanciation d'un sémaphore inter processus déjà existant
Semaphore s2(1, NB_TACHES, "Semaphore2", OPEN );
P(s)
s1.P();
V(s)
s1.V();
Systèmes multitâches et systèmes temps réel -22-
Systèmes multitâches et systèmes temps réel
IMPLEMENTATION DU MODELE PRODUCTEUR-CONSOMMATEUR
Modèle producteur consommateur simple
Mtr86-68K
SEMAPHORE sCons, sProd;
DONNEE d;
...
TACHE init()
{
...
sCOns = s_cree(0);
sProd = s_cree(1);
...
}
TACHE consommateur()
{
...
s_wait(sCons,0); // P(s)
consommer(d);
s_signal(sProd); // V(s)
...
}
TACHE producteur()
{
...
s_wait(sProd,0); // P(s)
produire(&d);
s_signal(sCons); // V(s)
...
}
Le sémaphore sCons est crée avec la valeur initiale 0, sProd avec la valeur initiale 1.
Si la tâche consommateur s'exécute en premier elle se bloque par s_wait(sCons,0).
Quand la tâche producteur s'exécute elle franchit la requête s_wait(sProd,0) puisque sProd est initialisé à 1.
Le producteur produit la donnée et exécute V(s) ce qui relance le consommateur. Si le producteur exécute P(s) avant que
le consommateur exécute V(s) il se bloque à son tour.
Systèmes multitâches et systèmes temps réel -23-
Systèmes multitâches et systèmes temps réel
Dans les exemples suivants, la donnée est constituée d'un entier 0..255.
Exemple C complet pour Mtr86-68K
// ProdConsS.c
#include "mtr86.h"
#define P(s) s_wait (s,1000)
#define V(s) s_signal(s)
unsigned donnee;
SEMAPHORE sProd, sCons;
TACHE Producteur(void)
{
int j;
for (j=0; j < 256; j++) {
P(sProd);
donnee = j;
V(sCons);
}
}
TACHE Consommateur(void)
{
for (;;) {
if ( P(sCons) == TIM ) // sortie si attente de plus de 1000 tics
break; // plus de données
cprintf(" %3dn", donnee);
V(sProd);
}
s_close(sProd); s_close(sCons);
mtr86exit(0);
}
TACHE init(void)
{
sProd = s_cree(1);
sCons = s_cree(0);
active(cree(Producteur, 1, 256));
active(cree(Consommateur, 1, 1024));
}
main()
{
clrscr();
start_mtr(init, 512);
}
Systèmes multitâches et systèmes temps réel -24-
Systèmes multitâches et systèmes temps réel
Modèle producteur consommateur avec stocks
Le modèle producteur consommateur simple est très peut utilisé puisqu'il synchronise son exécution sur l'acteur le plus
lent. En mémorisant les données produites dans une file fifo, il devient possible de produire et de consommer les
données à des vitesses différentes.
Cependant :
• lorsque la file est pleine le producteur se synchronise sur le consommateur,
• lorsque la file est vide le consommateur se synchronise sur le producteur,
La taille du stock fixe la valeur valeur maximale du sémaphore producteur et la taille de la fifo.
Exemple C complet pour Mtr86-68K
// ProdConsStock.c
#include <stdio.h>
#include "mtr86.h"
#define P(s) s_wait (s,cvrtic(1000))
#define V(s) s_signal(s)
#define TAILLE_FIFO 10
/*
* Déclaration de la fifo
*/
typedef struct
{
int stock[TAILLE_FIFO];
unsigned ie, is;
} Fifo;
void fifo_init(Fifo* fifo)
{
fifo->ie=fifo->is=0;
}
void Add(Fifo* fifo, int val)
{
fifo->stock[fifo->ie++ % TAILLE_FIFO] = val;
}
int Get(Fifo* fifo)
{
return fifo->stock[fifo->is++ % TAILLE_FIFO];
}
SEMAPHORE sProd, sCons;
RESSOURCE r1;
Fifo q1;
TACHE Producteur(void)
{
int j;
for (j=0; j<TAILLE_FIFO; j++)
{
P(sProd);
Add(&q1, j);
V(sCons);
}
}
TACHE Consommateur(void)
{
for (;;) {
if ( P(sCons) == TIM )
break; // plus de données
cprintf("%03dn", Get(&q1));
V(sProd);
}
s_close(sProd); s_close(sCons);
mtr86exit(0);
}
TACHE init(void)
{
fifo_init(&q1);
sProd = s_cree(TAILLE_FIFO); sCons = s_cree(0);
active(cree(Consommateur, 2, 1024));
active(cree(Producteur, 2, 512));
}
Systèmes multitâches et systèmes temps réel -25-
Systèmes multitâches et systèmes temps réel
main()
{
clrscr();
start_mtr(init, 512);
}
La valeur initiale du sémaphore sProd doit être égale à la quantité de la file fifo que l'on désire utiliser.
Il faut noter ici que la file fifo est un objet global dont l'accès est concurrent, mais l’accès ne porte pas sur les mêmes
données. Il n’est donc nécessaire de verrouiller l’accès à la fifo.
Exemple C++ complet pour Mtr86-68K
En écrivant le programme en C++, il se simplifie notablement.
La gestion de la fifo est encapsulée dans une classe Fifo comme le montre l’exemple ci-dessous.
// ProdConsStock.cpp
#include <stdio.h>
#include "mtr86.h"
#define P(s) s_wait (s,cvrtic(1000))
#define V(s) s_signal(s)
#define TAILLE_FIFO 10
class Fifo
{
int stock[TAILLE_FIFO];
unsigned ie, is;
public :
Fifo() { ie=0; is=0; }
void Add(int val) { stock[ie++ % TAILLE_FIFO] = val; }
int Get() { return stock[is++ % TAILLE_FIFO]; }
};
SEMAPHORE sProd, sCons;
Fifo q1;
TACHE Producteur(void)
{
int j;
for (j=0; j<TAILLE_FIFO; j++)
{
P(sProd);
q1.Add(j);
V(sCons);
}
}
TACHE Consommateur(void)
{
for (;;) {
if ( P(sCons) == TIM ) break; // plus de données
cprintf("%03dn", q1.Get());
V(sProd);
}
s_close(sProd); s_close(sCons);
mtr86exit(0);
}
TACHE init(void)
{
sProd = s_cree(TAILLE_FIFO);
sCons = s_cree(0);
active(cree(Consommateur, 2, 1024));
active(cree(Producteur, 2, 512));
}
main()
{
clrscr();
start_mtr(init, 512);
}
Systèmes multitâches et systèmes temps réel -26-
Systèmes multitâches et systèmes temps réel
Utilisation de la stl avec Mtr86-68K
Exemple C++ complet pour Mtr86-68K
La fifo peut être un conteneur de la STL. La fifo de la STL possède une taille auto adaptative : elle s'agrandit au fur est à
mesure des besoins, tout dépend de la vitesse de consommation par rapport à la production, mais en aucun cas la taille
de la file ne peut dépasser la valeur initiale du sémaphore sProd ( TAILLE_FIFO ).
La STL standard n’est pas multithread il faut donc verrouiller l’accès au conteneur, à moins de d’analyser le code
d’accès au conteneur queue pour vérifier qu’il n’y a pas de section critique.
// ProdConsStockSTL.cpp
#include <iostream>
#include <queue>
#include "mtr86.h"
using namespace std;
#define P(s) s_wait (s,cvrtic(1000))
#define V(s) s_signal(s)
#define TAILLE_FIFO 10
SEMAPHORE sProd, sCons;
RESSOURCE r1;
// Définir le conteneur de la stl utilisé
typedef queue<int> Fifo;
Fifo q1;
TACHE Producteur(void)
{
int j;
for (j=0; j<TAILLE_FIFO; j++)
{
P(sProd);
r_wait(r1);
q1.push(j);
r_signal(r1);
V(sCons);
}
}
TACHE Consommateur(void)
{
for (;;) {
if ( P(sCons) == TIM )
break; // plus de données
r_wait(r1);
cout << q1.front() << endl;
q1.pop();
r_signal(r1);
V(sProd);
}
s_close(sProd); s_close(sCons);
mtr86exit(0);
}
TACHE init(void)
{
sProd = s_cree(TAILLE_FIFO);
sCons = s_cree(0);
r1 = r_cree(); //utilisé pour l'exclusion mutuelle des sections critiques dans la STL
active(cree(Consommateur, 2, 1024));
active(cree(Producteur, 2, 1024));
}
main()
{
clrscr();
start_mtr(init, 512);
}
Systèmes multitâches et systèmes temps réel -27-
Systèmes multitâches et systèmes temps rée
IMPLEMENTATION DU MODELE PRODUCTEUR CONSOMMATEUR
DANS LES SYSTEMES MULTITACHES TEMPS REEL
Vu l'importance du modèle producteur consommateur, celui-ci est directement implémenté dans la plupart systèmes
multitâches et les noyaux temps réel. Cette implémentation porte le nom de tube, pipe, ..., messages.
IMPLEMENTATION DES TUBES DANS MTR86-68K
Un tube ou pipe peut être considéré comme ayant une taille infinie. La quantité de données pouvant passer par le tube
n’est pas limitée par la taille du tube.
Un pipe est crée par l'appel suivant :
Tube p1;
...
p1 = p_open( id_producteur, id_consommateur, TIMEOUT );
On remarque que l'id du producteur et du consommateur doit être connu, ce qui implique que les utilisateurs du tube
doivent être crée avant celle du tube.
Si TIMEOUT est égal à 0, les opérations avec le tube p1 ne sont pas bornées dans le temps.
p1 permet par la suite de référencer le tube.
L'écriture dans le tube est réalisé par :
p_write( p1, adresse_donnee, taille_donnee );
La lecture depuis le tube est réalisé par :
p_read( p1, adresse_donnee, taille_donnee );
p_write et p_read retournent TIM dans le cas d'opérations bornées avec dépassement de temps ou OK dans tous les
autres cas.
L'état du tube peut être testé par :
plein(p1); // retourne le nombre d'emplacements disponibles
vide(p1); // retourne le nombre d'éléments dans le tube
Ces primitives permettent de s'assurer qu'une tâche ne se suspendra pas en écriture sur un de tube plein, ou en lecture sur
un tube vide. Ces informations sont indispensables si les tubes sont utilisés dans les routines d'interruption.
En fin d'utilisation un tube est clos par :
p_close(p1);
Reprenons les deux exemples précédents avec Mtr86-68K
Systèmes multitâches et systèmes temps réel -28-
Systèmes multitâches et systèmes temps réel
// Pipe.cpp
#include <stdio.h>
#include "mtr86.h"
TUBE p1;
unsigned hCons, hProd;
TACHE Producteur(void)
{
for (int j=0; j < 25; j++)
p_write( p1, &j, sizeof(j) );
}
TACHE Consommateur(void)
{
int donnee;
for (;;) {
if ( p_read(p1, &donnee, sizeof(donnee)) == TIM )
break; // plus de données
cprintf(" %03dn", donnee);
}
p_close(p1);
mtr86exit(0);
}
TACHE init(void)
{
hCons = cree(Consommateur, 2, 1024);
hProd = cree(Producteur, 2, 512);
p1 = p_open(hProd, hCons, cvrtic(1000)); // timeout 1s sur les opérations
active(hCons);
active(hProd);
}
main()
{
clrscr();
start_mtr(init, 512);
}
Systèmes multitâches et systèmes temps réel -29-
Systèmes multitâches et systèmes temps réel
MODELE LECTEUR - REDACTEUR
Contrairement au modèle producteur - consommateur, le modèle lecteur - rédacteur fait appel à plusieurs rédacteurs et
plusieurs lecteurs simultanés. Le modèle lecteur - rédacteur est le modèles de base dans les applications de type client
serveur.
Il se conforme aux règles suivantes :
• Une donnée peut être lue simultanément par plusieurs lecteurs sans consommation de celle-ci.
• Un seul rédacteur à la fois peut modifier une donnée.
• Lorsqu'un rédacteur est actif, tous les lecteurs sont interdit de lecture.
• Lorsqu'un ou plusieurs lecteurs sont actifs tous les rédacteurs sont interdit d'écriture.
Le problème se trouve simplifié lorsque le nombre de lecteurs et de rédacteurs et connu par avance et que le noyau
autorise les opérations P et V avec un nombre différents de 1.
Mtr86 objet dans sa version actuelle n'autorise que les opérations V avec un nombre différents de 1, les autres versions
n'autorisant que les opérations P et V simples.
ELEMENTS DE SOLUTION
Hypothèses :
L'implémentation des sémaphores permet que les opérations P et V avec un incrément de 1.
Problème posé :
Un nombre de lecteurs et de rédacteurs connus par avance, lisent et écrivent de façon concurrente dans une zone de
mémoire commune :
Pour le rédacteur r :
for (j=0; j < MAX; j++) { // MAX = 1000
tampon[j]=r; // zône de mémoire à commune <- N° rédacteur
for ( int i=0; i < 1000; i++); // consommer du temps CPU pour faire apparaitre
} // des aléas éventuel
Pour un lecteur quelconque :
for (j=0; j< MAX; j++)
chk +=tampon[j];
affiche(chk);
Si lecteurs et rédacteurs sont en concurrence et sans protection particulière, chaque lecteur affichera une valeur
quelconque. En appliquant le modèle lecteur – rédacteurs, le rédacteur enregistre son numéro dans le tampon sans être
interrompu. Les lecteurs afficheront alors des valeurs égales à :
x0000 avec x = 0..nombre_max_de_rédacteurs
Le modèle lecteur – rédacteur est implémenté dans quatre fonctions : demande_ de_lecture, demande_ecriture,
fin_de_lecture et fin_ecriture.
• avant d'effectuer un accès à la donnée, le lecteur et le rédacteur appellent respectivement une fonction
demande_ de_lecture et demande_ecriture
• à la fin de l'accès à la donnée, le lecteur le lecteur et le rédacteur appellent respectivement une fonction
fin_de_lecture et fin_ecriture
Etude préalable :
1. Chaque lecteur et chaque rédacteur effectuant une tentative d'accès à la donnée alors qu'un rédacteur est actif se
bloque sur une opération P.
2. Si aucun rédacteur est actif, tout lecteur peut accéder à la donnée.
3. Si au moins un lecteur est actif, tout rédacteur effectuant une tentative d'accès à la donnée se bloque sur une
opération P.
4. Lorsqu'un rédacteur a terminé d'accéder à la donnée, il doit ou relancer chaque lecteurs bloqués par une
opération V (autant d'opérations que de lecteurs bloqués) ou un des rédacteurs bloqué par une opération V. Deux
algorithmes sont possibles : donner la priorité aux lecteurs ou aux rédacteurs.
5. Lorsque le dernier lecteur a terminé d'accéder à la donnée, il doit relancer un rédacteur si au moins un rédacteur
est bloqué.
ü Un sémaphore est attribué aux les lecteurs (sl) et rédacteurs (sr).
ü Des points 1 et 2 on en déduit qu'il faut connaître l'opération en cours : on utilisera une variable d'état r
signalant qu'un rédacteur est actif.
Systèmes multitâches et systèmes temps réel -30-
Systèmes multitâches et systèmes temps réel
ü Des points 3 et 5 on en déduit qu'il faut connaître le nombre de lecteurs actifs et le nombre de rédacteurs
bloqués afin de pouvoir appliquer une opération V valide. La variable d'état nl comptera le nombre de lecteurs
actifs et rb comptera le nombre de rédacteurs bloqués.
ü Du point 4 on en déduit qu'il faut compter les lecteurs bloqués : On utilisera une variable d'état lb.
Sans tenir compte des sections critiques on en déduit l'algorithme de demande_de_lecture :
void demande_de_lecture(void)
{
if (r==TRUE) { /* Si écriture en cours */
lb++; /* un lecteur bloqué de plus */
P(sl); /* bloquer le lecteur */
}
nl++; /* Un lecteur de plus */
}
Cet algorithme présente des sections critiques : r, lb , nl sont des variables globales modifiés de façon
concurrente.
En verrouillant les sections critiques avec le gestionnaire de ressource, l'algorithme précédent devient :
void demande_de_lecture(void)
{
r_wait(mutex); /* Début section critique */
if (r==TRUE) { /* Si écriture en cours */
lb++; /* un lecteur bloqué de plus */
r_signal(mutex); /* Fin section critique */
P(sl); /* bloquer le lecteur */
r_wait(mutex); /* Début section critique */
}
nl++; /* Un lecteur de plus */
r_signal(mutex); /* fin section critique */
}
Cet algorithme présente une erreur subtile: il existe une zone de section critique non verrouillée :
il faut un temps non nul entre la fin de l'opération P et le verrouillage effectif de la section critique :
P(sl); /* bloquer le lecteur */
r_wait(mutex); /* Début section critique */
Durant ce temps un rédacteur peut s'y glisser si la commutation de tâche a lieu juste à ce moment là. Cet aléa se produit
rapidement avec un noyau temps réel dur et une durée de quantum très courte. Par exemple avec Mtr86 Dos réglé avec
un quantum d'une milliseconde l'aléa se produit en moins d'une seconde ( 1000 commutations de tâches ). Pour un
noyau temps réel mou il faut parfois des heures, voire des jours pour que cet aléa se manifeste mais il finira toujours par
se manifester.
On voit ici qu'il peut être très difficile d'écrire des applications multitâches ou temps réel fiables !
Finalement l'algorithme correct est donné ci-dessous :
void demande_de_lecture(void)
{
r_wait(mutex); /* Début section critique */
while (r==TRUE) { /* Si écriture en cours */
lb++; /* un lecteur bloqué de plus */
r_signal(mutex); /* Fin section critique */
P(sl); /* bloquer le lecteur */
r_wait(mutex); /* Début section critique */
}
nl++; /* Un lecteur de plus */
r_signal(mutex); /* fin section critique */
}
Si un rédacteur est actif réel entre P et r_wait il aura positionné la variable d'état r à vrai et le lecteurs se bloquera à
nouveau.
Systèmes multitâches et systèmes temps réel -31-
Systèmes multitâches et systèmes temps réel
Fonction fin_de_ lecture
void fin_de_lecture(void)
{
r_wait(mutex); /* Début section critique */
nl--; /* un lecteur de moins */
if (nl==0 && rb>0) { /* Si dernier lecteur et rédacteurs bloqués */
rb--; /* un rédacteur bloqué en moins */
s_signal(sr); /* un rédacteur relancé */
}
r_signal(mutex); /* Fin section critique */
}
Fonction demande_ecriture
void demande_ecriture(void)
{
r_wait(mutex); /* Début section critique */
while (r==TRUE || nl>0) { /* Tant qu'‚criture ou lecture en cours */
rb++; /* Un rédacteur bloqué de plus */
r_signal(mutex); /* Fin section critique */
s_wait(sr, 0); /* Bloquer le rédacteur */
r_wait(mutex); /* Début section critique */
}
r=TRUE; /* Ecriture en cours */
r_signal(mutex); /* Fin section critique */
}
Fonction fin_ecriture
#ifdef PRIORITE_LECTEURS
void fin_ecriture(void)
{
r_wait(mutex); /* Début section critique */
r=FALSE; /* Fin écriture */
if ( lb >0 ) /* Si lecteur bloqué */
do {
lb--; /* Un lecteur bloqué en moins */
s_signal(sl); /* lecteur relancé */
} while (lb > 0); /* tant qu'il reste des lecteurs bloqués*/
else
if ( rb > 0) { /* Si rédacteur bloqué */
rb--;
s_signal(sr); /* relancer un rédacteur */
}
r_signal(mutex); /* Fin section critique */
}
#else
void fin_ecriture(void)
{
r_wait(mutex);
r=FALSE;
if ( rb != 0) {
rb--; s_signal(sr);
}
else
if ( lb != 0 )
do {
lb--; s_signal(sl);
} while (lb!=0);
r_signal(mutex);
}
#endif
Systèmes multitâches et systèmes temps réel -32-
Systèmes multitâches et systèmes temps réel
SYNCHRONISATIONS DES TACHES ET PROCESSUS
Problème posé
Il s’agit de synchroniser plusieurs tâches ou un processus entre eux.
Deux types de mécanisme peuvent être utilisés :
§ Mécanismes de synchronisation directs : ils désignent directement la tâche à synchroniser
§ Mécanismes de synchronisation indirects : désignent indirectement la tâche à synchroniser à travers un objet du
noyau.
MECANISMES DE SYNCHRONISATION DIRECTS
Ces mécanismes utilisent les primitives dort et réveille :
§ une tâche attend un signal de synchronisation en se suspendant (pour une durée bornée ou non) à l'aide le la
primitive dort,
§ une ou plusieurs autres tâches désignent la tâche à relancer à l'aide de la primitive reveille.
L'implémentation des primitives dort et reveille est fonction du type de noyau : dans les noyau temps réels "dur", les
signaux de réveil ne sont généralement pas mémorisés alors qu'il le sont toujours dans les noyaux "mou" .
Ce mécanisme très simple est généralement utilisé pour synchroniser une tâche logicielle sur une tâche matérielle.
Exemple :
1. Une tâche (logicielle) lance une opération E/S (par interruption) pour se suspend en appelant la primitive dort.
2. Lorsque l'opération E/S est terminée le périphérique concerné lance une tâche matérielle (programme
d'interruption) qui relance la tâche logicielle initiatrice le l'opération E/S à l'aide de la primitive reveille.
MECANISMES DE SYNCHRONISATION INDIRECTS
Ces mécanismes utilisent des objets du noyau par l'intermédiaire desquels s'effectue la synchronisation effective. Les
objets utilisable pour cet usage sont les sémaphores, les variables événements, les variables rendez-vous.
SYNCHRONISATION A L'AIDE DE SEMAPHORES
Les sémaphores peuvent êtres utilisés de la même manière que les primitives dort et reveille avec cependant moins
d'efficacité du fait de la gestion de la file d'attente du sémaphores.
Exemple :
1. Une tâche (logicielle) lance une opération E/S puis se suspend en appelant la primitive P(s) avec s initialisé à 0.
2. Lorsque l'opération E/S est terminée, le périphérique concerné lance une tâche matérielle (programme
d'interruption) qui effectue la primitive V(s). La tâche initiatrice le l'opération E/S se trouve relancée et récupère
la donnée.
Ce mécanisme est plus souple que le précédent : il n'est pas nécessaire de connaître la tâche à relancer, mais uniquement
le sémaphore concerné.
Exemple 1 : le canyon et les babouins.
Un étudiant en anthropologie participe à un projet de recherche visant à déterminer si l'on peut apprendre l’inter blocage
aux babouins. Il repère un canyon profond et suspend une corde en travers pour que les babouins puissent le traverser.
Plusieurs babouins peuvent traverser simultanément s'ils vont tous dans le même sens. Un inter blocage se produit si des
babouins qui se dirigent en sens opposé se retrouvent en même temps sur la corde, les babouins ne pouvant se croiser
au-dessus du canyon. Si un babouin veut traverser le canyon, il doit d'abord s'assurer qu'aucun autre babouin ne se
déplace dans la direction opposée. Avant de traverser coté nord un babouin fait appel à la procédure Entrer_N() puis en
sortant coté sud il fait appel à la procédure Sortir_S.
Travail demandé :
1. Ecrire le programme simulant l’arrivée simultanée de 10 babouins dans chaque sens. Afficher les traversées par
–>EN, SD–> ou –>ES SN–>. La solution trouvée devra permettre la traversée du canyon par un seul babouin
à la fois.
2. L’arrivée des babouins est simulée par la transition 1 vers 0 des bits 0 et 8 du port B (bit 0 pour les babouins
venant du nord). La traversée est simulée par le décalages des bits sur le port A toutes les 100 ms (7 -> 0 pour
les babouins venant du sud et 0 -> 7 pour les babouins venant du nord). Ecrire le programme sachant que la
solution trouvée devra permettre la traversée du canyon par un seul babouin à la fois..
3. Modifier le programme 1 de façon à permettre à 4 babouins de traverser simultanément dans le même sens.
Systèmes multitâches et systèmes temps réel -33-
Systèmes multitâches et systèmes temps réel
Exemple 1 : les babouins et le canyon, éléments de corrigé.
La question 1 s’apparente à une simple exclusion mutuelle. On utilise un sémaphore Sem initialisé à 1. Le premier
babouin faisant l’opération P(Sem), bloque le passage à tous les autres babouins.
// babouins1.c
#include "mtr86.h"
#define P(s) s_wait(s, 0)
#define V(s) s_signal(s)
#define MAX 10 // Nombre de babouins arrivant simultanément
SEMAPHORE Sem;
void entrer_S(void)
{
P(Sem);
printf("->ESn");
}
void entrer_N(void)
{
P(Sem);
printf("->ENn");
}
void sortir_N(void)
{
V(Sem);
printf("SN->n");
}
void sortir_S(void)
{
V(Sem);
printf("SS->n");
}
TACHE babouinN(void)
{
entrer_N();
dort(100);
sortir_S();
}
TACHE babouinS(void)
{
entrer_S();
dort(100);
sortir_N();
}
TACHE init(void)
{
int i;
Sem = s_cree(1);
cree(babouinN, 2, 1024);
cree(babouinS, 2, 512);
for (i=0; i< MAX; i++) active(num_tache(babouinN));
for (i=0; i< MAX; i++) active(num_tache(babouinS));
}
Systèmes multitâches et systèmes temps réel -34-
Systèmes multitâches et systèmes temps réel
main()
{
cputs("Resolution du probleme des babouinsn");
InitSimulateur();
start_mtr(init,512);
}
La question 2 est similaire à la question 1 sauf que c’est la fonction traverse simulant la traversée qui fait appel aux
fonctions sortir. Comme il n’y a qu’un baboin à la fois qui traverse une utilisera qu’une tâche par sens, chaque
transitions sur les interruptions du port B activera la tâche correspondante. On notera ici que les signaux d’activation
sont mémorisés, il en résulte, par exemple, que 10 transitions de 1 vers 0 du bit 0 du port B activeront 10 fois la tâche
baboinN.
// babouins2.c
#include "mtr86.h"
#define P(s) s_wait(s, 0)
#define V(s) s_signal(s)
SEMAPHORE Sem;
int bN, bS; // compteurs de babouins actifs
void printBab(unsigned char image, int sens)
{
int i;
for(i=0; i<9; i++)
{
EcritPortA(image);
if (sens==0)
image <<=1;
else
image >>=1;
dort(100);
}
}
void Entrer_S(void)
{
P(Sem); // voie libre
bS++; // un babouin de plus
printf("->ESn");
}
void sortir_S(void)
{
V(Sem); // libérer la voie
printf("SS->n");
}
void Entrer_N(void)
{
P(Sem);
bN++;
printf("->ENn");
}
void sortir_N(void)
{
V(Sem);
printf("SN->n");
}
Systèmes multitâches et systèmes temps réel -35-
Systèmes multitâches et systèmes temps réel
TACHE babouinN(void)
{
Entrer_N();
}
TACHE babouinS(void)
{
Entrer_S();
}
TACHE traverse(void)
{
for(;;)
{
if(bN) // s’il y a un babouin coté nord
{
printBab(1, 0); // traverser
bN--; // plus de baboin coté nord
sortir_S();
}
if(bS)
{
printBab(0x80, 1);
bS--;
sortir_N();
}
dort(10); // dégager du temps CPU
}
}
TACHE acquisition(void)
{
unsigned char port=0;
unsigned fn=0;
unsigned fs=0;
for (;;)
{
port = LirePortB();
if(port&0x1 && fn==0) // bit à 1
fn=1; // le signaler
if(port==0 && fn) // bit passant à 0
{
fn=0;
active(num_tache(babouinN));
}
if(port&0x80 && fs==0)
fs=1;
if(port==0 && fs==1)
{
fs=0;
active(num_tache(babouinS));
}
dort(10); // dégaer du temps CPU
}
}
Systèmes multitâches et systèmes temps réel -36-
Systèmes multitâches et systèmes temps réel
TACHE init(void)
{
Sem = s_cree(1);
bS=0; bN=0;
cree(babouinN, 2, 512);
cree(babouinS, 2, 512);
active(cree(traverse, 1, 512));
active(cree(acquisition,1, 512));
}
main()
{
cputs("Resolution du probleme des baboinsn");
InitSimulateur();
start_mtr(init,512);
}
Systèmes multitâches et systèmes temps réel -37-
Systèmes multitâches et systèmes temps réel
Exemple 2 : Le repas des philosophes.
On ne peut pas clore les mécanismes de synchronisation à l'aide de sémaphores sans parler du classique problème des
philosophes.
Ce problème de synchronisation a été posé et résolu par Dijkstra en 1965.
Cinq philosophes sont réunis pour dîner et pour philosopher. Cinq
couverts composés d'une assiette et d'une fourchette sont disposés autour
d'une table circulaire. Chaque convive a devant lui un plat de spaghetti
tellement glissant qu'il faut deux fourchettes pour les manger.
Après réflexion les philosophes décident du protocole suivant :
• Chaque philosophe reste à une place fixe,
• Pour manger, un philosophe ne peut emprunter que la fourchette de
droite ou de gauche,
• Initialement tous les philosophes pensent,
• Un philosophe est dans une des trois situations suivantes : il pense, il a
faim, ou il mange.
• Un philosophe mange durant un temps fini.
La solution proposée doit permettre à deux philosophes de manger en
même temps.
Le repas des philosophes.
Un philosophe i réalise les opérations suivantes :
• penser();
• prendre_fourchette(gauche);
• prendre_fourchette(droite);
• manger();
• poser_fourchette(gauche);
• poser_fourchette(droite);
ü La procédure prendre_fourchette attend que la fourchette désignée soit libre, puis la prend.
ü La procédure poser_fourchette(i) signale la libération de la fourchette désignée.
ü Les procédures penser() et manger() sont des procédures vides.
Les philosophes étant simulés par des tâches, il faut trouver une solution permettant à deux philosophes de manger en
même temps.
Le code ci-dessous présente une solution immédiate :
1. TACHE philosophe0( void )
2. {
3. while (1) {
4. penser();
5. prendre_fourchette(gauche);
6. prendre_fourchette(droite);
7. manger();
8. poser_fourchette(gauche);
9. poser_fourchette(droite);
10. }
11. }
Solution fausse du repas des philosophes.
Cette solution est fausse :
o Supposons que chaque convive prenne la fourchette de gauche en même temps. Aucun ne peut alors prendre la
fourchette de droite. Cette situation s'appelle un inter blocage.
o En supposant que chaque convive vérifie, avant de prendre la fourchette de droite, que celle-ci est libre, le
problème échoue pour d'autres raisons : les philosophes prennent en même temps la fourchette de gauche, puis
la reposent en voyant que la fourchette de droite n'est pas disponible et recommencent. Il n'y a pas ici d'inter
blocage mais les tâches n'évoluent pas. Cette situation s'appelle une famine (au propre et au figuré !).
Il existe une solution ne conduisant ni à un inter blocage, ni à une famine : elle consiste à protéger le code se trouvant
entre les lignes 5 et 9 par un mécanisme d'exclusion mutuelle. Cette solution ne répond pas au cahier des charges car
elle permet à un seul philosophe de manger à la fois.
Solution du problème
Systèmes multitâches et systèmes temps réel -38-
Systèmes multitâches et systèmes temps réel
Les philosophes étant simulés par de tâches, associons à chaque philosophe i une variable d'état etat[i] pouvant prendre
une des valeurs suivantes en fonction de l'état du philosophe :
q PENSE
q AFFAME
q MANGE
Le passage de l'état PENSE à l'état MANGE n'est possible que si :
etat[gauche] != MANGE && etat[droit] != MANGE
avec gauche et droite définis par :
#define gauche ( (i+4) % N ) /* N = nombre de philosophes */
#define droite ( (i+1) % N )
Cette ligne signifie que ni le philosophe assis à droite, ni le philosophe assis à gauche du philosophe i n'a pris de
fourchette.
Ce test est une séquence critique et doit être protégé à l'aide d'un mécanisme d'exclusion mutuel.
Si ce test est faux le philosophe passe à l'état AFFAME (la tâche est suspendue) à l'aide d'une opération P(semp[i]),
(avec semp[i] initialisé à 0), semp étant un tableau regroupant les sémaphores associés à chaque philosophe.
Pour prendre les fourchettes, un philosophe i exécute la séquence suivante :
1. r_wait(mutex); // début de section critique
2. if (etat[gauche] != MANGE && etat[droite] != MANGE {
3. etat[i] = MANGE;
4. V(semp[i]); // semp[i] passe de 0 à 1 ce qui évitera la suspension ligne 9
5. }
6. else
7. etat[i] = AFFAME;
8. r_signal(mutex); // fin section critique
9. P(semp[I]); // bloque si lignes 3 et 4 ne sont pas exécutées
Le passage d'un philosophe i de l'état MANGE à l'état PENSE entraîne le réveil des philosophes (tâches) à gauche et à
droite si les conditions suivantes sont remplies :
ü leur état doit être affamé,
ü ils doivent disposer de l'autre fourchette.
Ces conditions peuvent êtres vérifiées par un appel du type test(gauche) et test(droite) avec test :
void test( int i )
{
if ( etat[i] == AFFAME && etat[gauche] != MANGE && etat[droite] != MANGE ) {
etat[i} = MANGE;
V(semp[i]); // evite de bloquer sur P(semp[i])
}
}
Comme cette fonction teste des variables d'état à accès concurrent, l'appel de cette fonction doit être verrouillée.
Finalement le programme complet pour Mtr86-68K est donné dessous. Ce programme en plus de l’affichage, affiche sur
le Port A de la carte eid210 les philosophes qui mangent :
/* Diner des philosophes */
#include <stdio.h>
#include "mtr86.h"
#include "eid210.h"
#define N 5 /* nombre de philosophes */
#define P(s) s_wait(s, 0)
#define V(s) s_signal(s)
#define gauche ( (i+4) % N )
#define droite ( (i+1) % N )
#define PENSER 0
#define AFFAMER 1
#define MANGER 2
Systèmes multitâches et systèmes temps réel -39-
Systèmes multitâches et systèmes temps réel
char *p[3] = {"PENSE n","FIN n", "MANGE *n"};
unsigned char status; // états des bits sur le portA du eid210
unsigned etat[N];
RESSOURCE mutex;
SEMAPHORE s[N];
void etat_philo( void )
{
int j, i=0;
gotoxy(1,16);
r_wait(mutex); /* Section critique */
for ( j = 0; j < N; j++ ) {
gotoxy(45, 10 + j);
cputs(p[etat[j]]);
}
EcritPortA(status); /* met à jour les bits */
r_signal(mutex); /* Liberer section critique */
}
void test( int i )
{
if (etat[i]== AFFAMER && etat[gauche] != MANGER && etat[droite] != MANGER) {
etat[i] = MANGER;
V(s[i]);
}
}
void pense( void ) { dort((rand() & 0x7F) + 10); }
void mange( void ) { dort((rand() & 0x7F) + 10); }
void prend_fourchette( int i )
{
r_wait(mutex); /* Section critique */
etat[i] = AFFAMER; /* PENSER -> AFFAME */
test(i); /* Voir si fourchette de gauche et de droite sont libre*/
r_signal(mutex); /* Liberer section critique */
P(s[i]); /* Bloquer le philosophe i si test n'a pas incrémenté le sém. */
}
void pose_fourchette( int i )
{
r_wait(mutex); /* Section critique */
etat[i] = PENSER;
test( gauche );
test( droite );
r_signal(mutex); /* Liberer section critique */
}
TACHE philosophes(void)
{
int i = tache_c()-2;
while (1) {
pense();
prend_fourchette(i);
status = (1 << i) | status; // met le bit du philosophe i à 1
mange();
pose_fourchette (i); // met le bit du philosophe i à 0
status = ~(1 << i) & status;
}
}
Systèmes multitâches et systèmes temps réel -40-
Systèmes multitâches et systèmes temps réel
TACHE init()
{
int j;
gotoxy(30,3);
printf("Diner des philosophesn");
mutex = r_cree();
for (j=0; j<N; j++)
s[j] = s_cree(0);
for (j=0; j<N; j++)
active(cree(philosophes,2,1024));
gotoxy(1,10);
printf( " philosophe 1 = nr"
" philosophe 2 = nr"
" philosophe 3 = nr"
" philosophe 4 = nr"
" philosophe 5 = n");
gotoxy(26,22);
printf("Fin du repas par reset!!!n");
for ( ;; ) {
etat_philo();
dort(cvrtic(500L));
}
}
main()
{
int j;
clrscr();
InitSimulateur();
gotoxy(10,1);
printf("RESOLUTION DU PROBLEME DU DINER DES PHILOSOPHES [Dijkstra 65]n");
start_mtr(init, 1024);
for (j=0; j< 5; j++ ) s_close( s[j] );
}

Weitere ähnliche Inhalte

Was ist angesagt?

Was ist angesagt? (20)

Ftp server
Ftp serverFtp server
Ftp server
 
5 & 6
5 & 65 & 6
5 & 6
 
http://www.allstreamvf.net http://www.allstreamvf.com http://www.wastreaming....
http://www.allstreamvf.net http://www.allstreamvf.com http://www.wastreaming....http://www.allstreamvf.net http://www.allstreamvf.com http://www.wastreaming....
http://www.allstreamvf.net http://www.allstreamvf.com http://www.wastreaming....
 
Laporan praktikum 3 - Sistem Operasi Internal & External Command (Lanjutan)
Laporan praktikum 3 - Sistem Operasi Internal & External Command (Lanjutan)Laporan praktikum 3 - Sistem Operasi Internal & External Command (Lanjutan)
Laporan praktikum 3 - Sistem Operasi Internal & External Command (Lanjutan)
 
Bagian komputer dan fungsinya
Bagian komputer dan fungsinyaBagian komputer dan fungsinya
Bagian komputer dan fungsinya
 
Kinerja komputer
Kinerja komputer Kinerja komputer
Kinerja komputer
 
Dasar dasar komputer
Dasar dasar komputerDasar dasar komputer
Dasar dasar komputer
 
Herramientas case
Herramientas caseHerramientas case
Herramientas case
 
Install windows 7
Install windows 7Install windows 7
Install windows 7
 
Teknik pengolahan citra visual c++ dengan mfc
Teknik pengolahan citra visual c++ dengan mfcTeknik pengolahan citra visual c++ dengan mfc
Teknik pengolahan citra visual c++ dengan mfc
 
Browsing Linux Kernel Source
Browsing Linux Kernel SourceBrowsing Linux Kernel Source
Browsing Linux Kernel Source
 
Makalah perangkat keras
Makalah perangkat kerasMakalah perangkat keras
Makalah perangkat keras
 
Instalasi WINDOWS 11.pptx
Instalasi WINDOWS 11.pptxInstalasi WINDOWS 11.pptx
Instalasi WINDOWS 11.pptx
 
系統程式 -- 附錄
系統程式 -- 附錄系統程式 -- 附錄
系統程式 -- 附錄
 
Лекція №10
Лекція №10Лекція №10
Лекція №10
 
Tp2 matlab
Tp2 matlab Tp2 matlab
Tp2 matlab
 
membuat desain sistem keamanan jaringan
membuat desain sistem keamanan jaringanmembuat desain sistem keamanan jaringan
membuat desain sistem keamanan jaringan
 
Makalah java
Makalah javaMakalah java
Makalah java
 
Өгөгдлийн бүтэц 1
Өгөгдлийн бүтэц 1Өгөгдлийн бүтэц 1
Өгөгдлийн бүтэц 1
 
Modul Laravel
Modul Laravel Modul Laravel
Modul Laravel
 

Ähnlich wie Support systemes multitaches-tempsreel

Restructuration d applications Java Temps réel
Restructuration d applications Java Temps réelRestructuration d applications Java Temps réel
Restructuration d applications Java Temps réelCédric Mouats
 
algo et complexité .pptx
algo et complexité  .pptxalgo et complexité  .pptx
algo et complexité .pptxtarekjedidi
 
rt-intro.pdf
rt-intro.pdfrt-intro.pdf
rt-intro.pdfSaid Ech
 
Conception d-un-equipement
Conception d-un-equipementConception d-un-equipement
Conception d-un-equipementbenzid dziri
 
ALT.NET Modéliser Parallèle avec C# 4.0
ALT.NET Modéliser Parallèle avec C# 4.0ALT.NET Modéliser Parallèle avec C# 4.0
ALT.NET Modéliser Parallèle avec C# 4.0Bruno Boucard
 
"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014
"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014
"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014Benoît de CHATEAUVIEUX
 
Atelier d'Optimisation de Contrôleurs en simulation Dynamique
Atelier d'Optimisation de Contrôleurs en simulation DynamiqueAtelier d'Optimisation de Contrôleurs en simulation Dynamique
Atelier d'Optimisation de Contrôleurs en simulation DynamiqueSergio Joao
 
Cours systèmes temps réel partie 2 Prof. Khalifa MANSOURI
Cours  systèmes temps réel partie 2 Prof. Khalifa MANSOURICours  systèmes temps réel partie 2 Prof. Khalifa MANSOURI
Cours systèmes temps réel partie 2 Prof. Khalifa MANSOURIMansouri Khalifa
 
Travaux pratiques corrigé logiciel ARENA
Travaux pratiques corrigé logiciel ARENATravaux pratiques corrigé logiciel ARENA
Travaux pratiques corrigé logiciel ARENASandraKobe
 
Guide technique regulation 11
Guide technique regulation 11Guide technique regulation 11
Guide technique regulation 11Abir Khriss
 
API_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptx
API_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptxAPI_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptx
API_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptxazilalfree
 
Real Time Meta Events
Real Time Meta EventsReal Time Meta Events
Real Time Meta Eventskerush
 

Ähnlich wie Support systemes multitaches-tempsreel (20)

La gestion des processus
La gestion des processusLa gestion des processus
La gestion des processus
 
Restructuration d applications Java Temps réel
Restructuration d applications Java Temps réelRestructuration d applications Java Temps réel
Restructuration d applications Java Temps réel
 
algo et complexité .pptx
algo et complexité  .pptxalgo et complexité  .pptx
algo et complexité .pptx
 
rt-intro.pdf
rt-intro.pdfrt-intro.pdf
rt-intro.pdf
 
Conception d-un-equipement
Conception d-un-equipementConception d-un-equipement
Conception d-un-equipement
 
Cours api
Cours apiCours api
Cours api
 
Systèmes Automatisés
Systèmes AutomatisésSystèmes Automatisés
Systèmes Automatisés
 
ALT.NET Modéliser Parallèle avec C# 4.0
ALT.NET Modéliser Parallèle avec C# 4.0ALT.NET Modéliser Parallèle avec C# 4.0
ALT.NET Modéliser Parallèle avec C# 4.0
 
"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014
"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014
"La Performance en Continue" à JMaghreb 3.0 - 05/11/2014
 
Atelier d'Optimisation de Contrôleurs en simulation Dynamique
Atelier d'Optimisation de Contrôleurs en simulation DynamiqueAtelier d'Optimisation de Contrôleurs en simulation Dynamique
Atelier d'Optimisation de Contrôleurs en simulation Dynamique
 
Api
ApiApi
Api
 
Cours systèmes temps réel partie 2 Prof. Khalifa MANSOURI
Cours  systèmes temps réel partie 2 Prof. Khalifa MANSOURICours  systèmes temps réel partie 2 Prof. Khalifa MANSOURI
Cours systèmes temps réel partie 2 Prof. Khalifa MANSOURI
 
grafcet
grafcetgrafcet
grafcet
 
Travaux pratiques corrigé logiciel ARENA
Travaux pratiques corrigé logiciel ARENATravaux pratiques corrigé logiciel ARENA
Travaux pratiques corrigé logiciel ARENA
 
Guide technique regulation 11
Guide technique regulation 11Guide technique regulation 11
Guide technique regulation 11
 
API_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptx
API_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptxAPI_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptx
API_kjkjkjkjkjkjkjkjkjkjkjkjkjhkjhjGrafcet (1).pptx
 
Real Time Meta Events
Real Time Meta EventsReal Time Meta Events
Real Time Meta Events
 
Chap1 intro 1pp
Chap1 intro 1ppChap1 intro 1pp
Chap1 intro 1pp
 
Ordonnancement
OrdonnancementOrdonnancement
Ordonnancement
 
Mpc@Cb Presentation Fr V2009 02 24
Mpc@Cb Presentation Fr V2009 02 24Mpc@Cb Presentation Fr V2009 02 24
Mpc@Cb Presentation Fr V2009 02 24
 

Support systemes multitaches-tempsreel

  • 1. Systèmes multitâches et systèmes temps réel Support de cours
  • 2. Systèmes multitâches et systèmes temps réel -2- Systèmes multitâches et systèmes temps réel TABLE DES MATIERES Table des matières_______________________________________________________________2 Systèmes multitâches et systèmes temps réel _______________________________________3 Quelques définitions _____________________________________________________________3 Algorithmes adaptées aux contraintes de temps______________________________________3 Détermination de contraintes de temps __________________________________________________ 4 Contraintes de temps faibles avec quelques événements contraignants ______________________ 5 Conclusion partielle__________________________________________________________________ 11 Systèmes multitâches et noyau temps réel _________________________________________12 Etats d’une tâche ___________________________________________________________________ 12 Non créé __________________________________________________________________________________ 12 Dormant ou créé ____________________________________________________________________________ 12 Prêt ou éligible _____________________________________________________________________________ 13 En exécution _______________________________________________________________________________ 13 Bloqué ou Suspendu ________________________________________________________________________ 13 Transitions entre états d'une tâche ____________________________________________________ 13 Création d'une tâche (crée) ___________________________________________________________________ 13 Activation d'une tâche (active) _________________________________________________________________ 13 Attribution du processeur à la tâche _____________________________________________________________ 13 Suspension d'une tâche ______________________________________________________________________ 13 Reprise d'une tâche bloquée __________________________________________________________________ 13 Fin d'une tâche ou destruction d'une tâche _______________________________________________________ 13 Suppression d'une tâche _____________________________________________________________________ 13 Attribution du processeur réel__________________________________________________________________ 13 Attribution du processeur virtuel ________________________________________________________________ 13 Politiques d'ordonnancement_________________________________________________________ 14 Systèmes avec ou sans réquisition du processeur _______________________________________ 15 Services offerts Mtr86-68K _______________________________________________________16 Structure d’une application Mtr86-68K _________________________________________________ 16 Mise en evidence de la politique d’ordonnancement dans Mtr86-68K___________________________________ 17 Création et activation des d’instances multiples d’une même tâche ____________________________________ 18 Problèmes posés par l’accès concurrent aux fonctions et aux données partagées_____________ 19 Réentrance des fonctions_____________________________________________________________________ 19 Sections critiques ___________________________________________________________________________ 20 Verrouillage des sections critiques dans un même processus_________________________________________ 20 Modèles standard d'échanges de données entre taches et processus _______________________ 20 Modèle producteur-consommateur______________________________________________________________ 20 Implémentation du modèle producteur consommateur dans les systèmes multitâches temps réel___________________________________________________________________________27 Implémentation des tubes dans Mtr86 Win32________________________________________27 Modèle lecteur - rédacteur ____________________________________________________________________ 29 Synchronisations des tâches et processus _____________________________________________ 32 Mécanismes de synchronisation directs__________________________________________________________ 32 Mécanismes de synchronisation indirects ________________________________________________________ 32 Exemples _________________________________________________________________________________ 32
  • 3. Systèmes multitâches et systèmes temps réel -3- Systèmes multitâches et systèmes temps réel SYSTEMES MULTITACHES ET SYSTEMES TEMPS REEL QUELQUES DEFINITIONS Multitâche = exécution de fonctions de façon concurrente. Multi processus = exécution de processus de façon concurrente. Tâches = fonctions ou procédures exécutées de façon concurrente. Temps Réel = Chaque tâche d'un procédé de type contrôle-commande, est exécuté en réponse à des sollicitations externes ou internes avec des contraintes de temps fixées par l'évolution du procédé à contrôler, par les dialogues avec l'opérateur ou par l'écoulement du temps. Pour chaque événement Ei (modification de l'état d'un capteur ou de toute information liée au processus...), il est nécessaire de calculer un ensemble de fonctions Fj dépendant de l'état Q de la commande. La durée du calcul t est fonction des algorithmes utilisés et de la puissance du processeur. Le procédé contrôlé fixe une échéance de temps Te pour chaque calcul. La présence de cette échéance caractérise un système temps réel. Pour des procédés industriels, Te est compris entre quelques dizaines de microsecondes et quelques centaines de millisecondes, voire quelques heures. Suivant la nature des contraintes de temps et la complexité des traitements, diverses solutions peuvent être retenues. On distingue les systèmes à : • Contraintes de temps faible et variées (t très inférieur à Te). • Contraintes de temps faible avec quelques événements contraignants, • Contraintes de temps fortes (t voisin de Te). Dans le premier cas on parlera de temps réel mou, dans les deux derniers cas, de temps réel dur. Un système temps réel est un système qui respecte les contraintes de temps. ALGORITHMES ADAPTEES AUX CONTRAINTES DE TEMPS CONTRAINTES DE TEMPS FAIBLES Ce type de contrainte peut être solutionné par une programmation en mode bouclé appelée également gestion des entrées - sorties par scrutation. Les bouclages en arrière en attente d'événements sont interdits, car ils allongent la durée du cycle (ty) de façon indéterminée. Le programme doit pouvoir scruter en permanence les entrées avec un temps ty < Te (figure 1.). Ce mode de programmation simple à mettre en œuvre, est difficile à maintenir et à faire évoluer. Lors de modifications ou d'ajouts de fonctions, les contraintes de temps peuvent ne plus être respectées. Pour éviter alors la réécriture complète du logiciel, la seule solution consiste remplacer le calculateur par un modèle plus puissant. L’inconvénient majeur de ce mode de programmation est qu’il fait cohabiter des contraintes de temps faible avec contrainte de temps fortes, ces dernières imposant le temps de cycle. Ce mode de programmation est très utilisé essentiellement du fait de sa simplicité. lire les entrées détecter les événements traiter les événements affecter les sorties ty < Te Figure 1 : gestion des entrées/sorties par scrutation.
  • 4. Systèmes multitâches et systèmes temps réel -4- Systèmes multitâches et systèmes temps réel Exemple : programmation d'un terminal. Cet exemple permet d'illustrer les points précédents. Un terminal est un périphérique de dialogue opérateur permettant d'afficher sur sa sortie de dialogue (généralement constituée d'un tube cathodique), les caractères arrivant sur son entrée de communication (figure 4). Simultanément tout caractère frappé au clavier (entrée de dialogue) est transmis sur sa sortie de communication. Les ports de communication sont généralement constitués de ports série (RS232, RS422, boucle de courant). Le cahier des charges fixe les contraintes de temps suivantes : • Vitesse de frappe maximale : 10 caractères / secondes ; • Vitesse d'affichage constatée sur l'écran : 1000 caractères / secondes ; • Vitesse de réception ou d'émission des caractères sur la ligne: 50..38400 Bauds, format 8 bits, 1 bit de start, 1 bit de stop (10 bits au total). Sur le diagramme de la figure 6 on fait apparaître deux tâches : • Une tâche affichant le caractère présent sur le port série. Cette tâche est déclenchée par la réception d'un caractère (tâche lire). • Une tâche effectuant l'acquisition et l'envoi des caractères frappés au clavier. Cette tâche est déclenchée par la frappe d'un caractère (tâche afficher). caractère touche car. reçu caractère et attributs car. frappé caractère ascii Clavier Port série Port série Ecran LIRE AFFICHER Figure 4 : terminal fonctionnant par scrutation. L'algorithme simplifié du programme terminal est donné à la figure 5. On supposera qu’un caractère a eu le temps d’être transmis entre deux frappes successives au clavier. caractère frappé Lire le caractère. Emettre le caractère caractère reçu Lire le car. sur la ligne Afficher le caractère ty Tâche LIRE Tâche AFFICHER Figure 5 : terminal fonctionnant par scrutation. Détermination des contraintes de temps
  • 5. Systèmes multitâches et systèmes temps réel -5- Systèmes multitâches et systèmes temps réel Pour une vitesse de réception de 600 Bauds. • tâche acquisition Te1 = 0,1s (indépendant de la vitesse de réception), • tâche réception Te2 = (1+8+1)/600 = 16,7 10 -3 s Vérification du respect des contraintes de temps Pour déterminer le temps de scrutation il est nécessaire de prendre en compte le cas le plus défavorable, c'est à dire lorsque qu'un caractère doit être affiché sur l'écran et qu'un caractère est lu au clavier. Nous supposerons pour simplifier, que le temps scrutation (ty) est essentiellement liée à la vitesse d'affichage (1000 caractères/secondes). Le temps de cycle est donc de : ty = 10-3 s La contrainte de temps la plus forte est : Te2 = (1 + 8 + 1) / 600 = 16,7 10-3 s. Ce qui vérifie ty << Te2 << Te1. Si la vitesse de réception des caractères est 19200 Bauds, la contrainte sera de : Te2 = 10 / 19200 = 0,52 10 -3 s; Te < ty Ce qui ne vérifie plus ty << Te2, tout en satisfaisant Te1. La contrainte Te2 n'étant pas satisfaite, le terminal perdra des caractères reçus sur la ligne série mais par contre ne perdra aucun caractère frappé au clavier ! La programmation par scrutation ne convient pas dans les cas de contraintes de temps hétérogènes. On en conclut qu'il faut dissocier les tâches à fortes contraintes de temps, de celles à faibles contraintes de temps, par un traitement hors de la boucle scrutation. CONTRAINTES DE TEMPS FAIBLES AVEC QUELQUES EVENEMENTS CONTRAIGNANTS Si le temps de cycle de la boucle de scrutation ne permet pas de satisfaire un nombre réduit de contraintes, on associe les fonctions concernées par ces contraintes à des interruptions matérielles. Chaque interruption peut être liée à un ou plusieurs événements. Lorsque l'événement se produit, l'interruption arrête l'exécution de la boucle de scrutation et lance un programme appelé tâche matérielle. Le calculateur sera ainsi synchronisé sur ces événements et exécutera les traitements liés aux interruptions en suspendant le programme en exécution ( voir figure 7 ). Cette solution présente plusieurs avantages : • Les événements liés aux interruptions ne nécessitent aucune attention dans la boucle de scrutation. La durée de la boucle n'a pas d'importance par rapport à l'événement considéré si elle ne gère pas un procédé possédant des contraintes de temps. • La boucle de scrutation n'est plus ralentie par le test systématique de présence d'événements asynchrones, l'efficacité du traitement réalisé s'en trouve accru. • Le rythme d'échange des informations est fixé par le dispositif lié aux interruptions en non par des contraintes de programmation de la boucle de scrutation. Tâche de fond (boucle de scrutation) Traitement P1 evénement 1 Traitement P2 evénement 2ty0 ty1 ty2 I1 I2 Figure 6 : gestion des entrées/sorties avec quelques événements contraignants.
  • 6. Systèmes multitâches et systèmes temps réel -6- Systèmes multitâches et systèmes temps réel On doit avoir à l’esprit que : • Lorsque survient une interruption, la boucle de scrutation est stoppée et peut même s'arrêter complètement si plusieurs interruptions surviennent à un rythme très rapide. Ce phénomène peut être mis très facilement évidence en pilotant une ligne d'interruption à l'aide d'un générateur de signaux périodique. • La programmation des échanges de données entre la boucle de scrutation et le(s) programme(s) d'interruption peut s'avérer délicate. Il n'est trivial de réaliser des échanges fiables entre des programmes fonctionnant de façon asynchrone. Ce point sera particulièrement abordé par la suite. Vérification des contraintes de temps Le traitement d'une interruption allonge le temps de cycle du programme interrompu. Les contraintes temporelles devront donc être analysées avec soin. Plusieurs cas doivent êtres envisagés selon que les interruptions sont emboîtées ou non. 1. Cas d'une seule interruption (I1) de récurrence f1=1/T1 (figure 6) Soient deux procédés possédant des contraintes de temps hétérogènes. Le traitement du procédé à forte contrainte (P1) est géré par interruption, le traitement du procédé à faible contrainte de temps (P0) est géré dans la boucle de scrutation : On connaît : • T1 période de l’interruption I1. Ce temps correspond à l’échéance de temps fixé par le procédé à contrôler : Te1 • ty1 temps de calcul (continu) de la fonction P1. • Te0 échéance de temps du procédé géré par P0 Pour ne pas perdre des signaux d'interruptions ou pour éviter une récursion infinie ont doit avoir : ty1 < T1 La durée du traitement de l'interruption doit être plus faible que sa périodicité ! I1 I1 t Code exécuté Boucle de scrutation code d'interruption ty1 T1 P0 P1 Figure 7 : exécution d’une interruption périodique. La durée de traitement de P0 se trouve multiplié par un facteur K égal environ à : 1 1 1 T K T ty ≈ − Pour satisfaire la contrainte Te0 on doit avoir : K ty ty Te* = ' <0 0 0 Lorsque le programme doit gérer plusieurs interruptions simultanées, deux cas sont à envisager : les interruptions sont traitées séquentiellement ou sont traitées parallèlement. On parlera dans ce dernier cas d'interruptions emboîtées. 2. Cas de plusieurs interruptions traités de façon séquentielles : Soient : T1 , période de l’interruption I1, fréquence f1 = 1 / T1 ty1, temps de calcul (continu) de la fonction P1. T2 , période de l’interruption I2 , fréquence f2 = 1 / T2 , et T2 < T1. ty2 , temps de calcul (continu) de la fonction P2. Te0 , échéance de temps du procédé géré par P0
  • 7. Systèmes multitâches et systèmes temps réel -7- Systèmes multitâches et systèmes temps réel I1Code exécuté Boucle de scrutation code d'interruption P0 P1 I2 I1 I2 ty2 T1 ty1 code d'interruption P2 retard dans le traitement de P2 du au mécanisme d'it non emboîté t T2 Figure 8 : exécution d’interruptions périodiques non emboîtées. Pour ne pas perdre des signaux d'interruptions on doit avoir : ty ty T T T1 2 2 2 1+ < ( < ) La durée de traitement de P0 se trouve multiplié par un facteur K égal environ à : 2 2 1 2-( + ) T K T ty ty ≈ Pour satisfaire la contrainte Te0 on doit avoir : ty K Te0 0 0* < 3. Cas d'interruptions emboîtées Dans ce modèle (figure 9), une interruption peut être exécutée durant l'exécution d'une interruption moins prioritaire. L'interruption la plus prioritaire doit être celle de récurrence la plus élevée : Soient : T1 , période de l’interruption I1, ty1 , temps de calcul (continu) de la fonction P1. T2 , période de l’interruption I2, et T2 < T1 ty2 , temps de calcul (continu) de la fonction P2. Te0 , échéance de temps du procédé géré par P0
  • 8. Systèmes multitâches et systèmes temps réel -8- Systèmes multitâches et systèmes temps réel I1 t Code exécuté P0 P1 P2 I2 I2 I2 I2 I2I2 I1 T2 T1 ty2 Figure 9 : exécution d’interruptions emboîtées. Pour ne pas perdre de signaux d'interruptions et pour éviter une récursion infinie ont doit avoir : ty2 < T2 Le traitement de l'interruption P2 emboîtée dans T1 allonge le temps de traitement ty1 d'un facteur K2 égal à : 2 2 2 2 - T K T ty ≈ Pour satisfaire la contrainte T1 doit avoir : 1 2 1* <ty K T Le traitement de l'interruption P1 allonge à son tour le temps de traitement de la boucle P0 d'un facteur K1 : ( ) 1 1 1 1 2- * T K T ty K ≈ Pour satisfaire la contrainte Te0 doit avoir : 0 1 0* <ty K T
  • 9. Systèmes multitâches et systèmes temps réel -9- Systèmes multitâches et systèmes temps réel Exemple : terminal avec réception de caractères à vitesse élevée Le diagramme figure 10 comporte trois tâches : • Une tâche déclenchée par l'interruption liée à la réception d'un caractère (tâche lire_ligne). Celle-ci place le caractère reçu dans un tampon (FIFO). Le débordement du tampon n'est pas envisagé. • Une tâche, dont l'exécution dépend d'une condition interne (taux de remplissage du tampon) : afficher. • Une tâche dont l'exécution dépend de conditions externes non liées à une interruption (caractère frappé) : lire. caractère touche Interruption caractère et attributs status caractère Clavier Port série Port série Ecran LIRE AFFICHERLIRE ligne Tampon caractère caractère Figure 10 : diagramme de flux de données d'un terminal fonctionnant par interruption. L'algorithme simplifié du programme terminal est donné à la figure 11. Hypothèses : • Temps de cycle cumulé des taches afficher et lire : ty0 = 20 10-3s ; • Echéance de temps fixée par la tâche lire : Te0 = 0,1s ; • Durée du programme d'interruption : ty1 = 0,255 10-3s. Pour une réception à 9600 bauds : f1 = 9600 / 10 bits = 960 interruptions/secondes. Les contraintes de temps sont : T1 = 10 / 9600 = 1,04 10-3s ; ty1 vérifie la contrainte T1 (0,255 10-3 s < 1,04 10-3 s) ; ty0 * T1/(T1-ty1) = 2 10-3 * 1,04 10-3/(1,04 10-3 - 0,255 10-3)= 2,65 10-3s Vérifie la contrainte Te0 (0,265 10-3 s < 0,1 s) ;
  • 10. Systèmes multitâches et systèmes temps réel -10- Systèmes multitâches et systèmes temps réel caractère frappé Lire le caractère. Emettre le caractère tampon # vide Afficher le caractère ty0 Tâche LIRE Tâche AFFICHER Caractère reçu Lire le caractère et gérer le tampon Tâche matérielle LIRE ligne Extraire le car. gérer le tampon ty1 Restituer les registres utilisés Acquitter l'interruption Sauvegarder les registres utilisés Figure 11 : terminal fonctionnant par interruption. Contraintes de temps pour une réception à 38400 bauds : Te1 = 10 / 38400 = 0,26 10-3 s ; ty1 vérifie la contrainte Te1 ( 0,255 10-3 s < 0,26 10-3 s ) ; ty0 * T1/(T1-ty1) = 2 10-3 * 0,26 10-3/(0.26 10-3 - 0,255 10-3) = 1.04 s 1.04 s > 0,1 s La contrainte Te0 n'est pas satisfaite : il faut alors lier la frappe d'un caractère à une interruption (de priorité inférieure à l'interruption de réception). La figure 12 représente le diagramme de flots de données correspondant. Celui-ci fait apparaître quatre tâches : • Une tâche matérielle déclenchée par l'interruption due à la réception d'un caractère (tâche lire ligne). Celle-ci place le caractère reçu dans un tampon (FIFO). Le débordement du tampon n'est pas envisagé. • Une tâche matérielle déclenchée par l'interruption due à la frappe d'un caractère clavier (tâche lire caractère). Celle-ci place le caractère reçu dans un autre tampon. Le débordement du tampon n'est pas envisagé. • Deux tâches logicielles dont l'exécution dépend de conditions internes ( taux de remplissage des tampons d'émission et de réception ) : tâche afficher et émettre.
  • 11. Systèmes multitâches et systèmes temps réel -11- Systèmes multitâches et systèmes temps réel caractère caractère Interruption caractère et attributs Interruption caractère Clavier Port série Port série Ecran LIRE EMETTRE LIRE ligne Tampon caractère caractère Tampon caractère AFFICHER Figure 12 : diagramme de flux de données d'un terminal fonctionnant par interruption. L'algorithme de programmation du programme est donné figure 13. // Boucle de scrutation Faire Si Tampon de réception # vide Alors extraire, afficher le caractère en tête du tampon Si Tampon clavier # vide Alors extraire, émettre le caractère en tête du tampon Fin // Programme d'interruption de réception d'un caractère Sauvegarder les registres utilisés Lire le caractère, le mémoriser dans le tampon de réception Restituer les registres utilisés Acquitter l'interruption Fin // Programme d'interruption de lecture du clavier Sauvegarder les registres utilisés Lire le code clavier et le transformer en code ASCII Mémoriser le code ASCII dans le tampon d'émission Restituer les registres sauvegardés Acquitter l'interruption Fin Figure 13 : algorithme de principe d'un terminal d'un terminal fonctionnant par interruption. Pour assurer une gestion correcte des tampons, le code barré d’un trait vertical, doit être ininterruptible. Au lecteur de vérifier si les contraintes de temps sont respectées. CONCLUSION PARTIELLE Lorsque les contraintes de temps sont nombreuses et variées, la programmation de ce type d'application peut devenir très complexe. On préfère alors contrôler les tâches à l’aide un programme spécial appelé moniteur temps réel. Celui-ci se charge de lancer, de synchroniser et d’arrêter les tâches. Il fournit en outre de puissants moyen de communication entre les tâches concurrentes.
  • 12. Systèmes multitâches et systèmes temps réel -12- Systèmes multitâches et systèmes temps réel SYSTEMES MULTITACHES ET NOYAU TEMPS REEL Dans les exemples précédant est apparu la notion de tâches : tâche matérielle (tâche liée aux interruptions) et tâche logicielle (tâche de fond). Un moniteur temps réel et un système multitâche permettent d’exécuter des fonctions de façon concurrente en leur attribuant : • Un niveau de priorité, • Pour les systèmes monoprocesseur : un quantum de temps processeur • Pour les systèmes multiprocesseur : un processeur. ETATS D’UNE TACHE A un instant donné, chaque tâche se trouve dans des états suivants : (CREE) activer créer suspendre reveiller physique Non CREE supprimer détruire détruire détruire supprimer supprimer supprimer δt δt BLOQUE EXEC virtuelle PRETDORMANT Le passage d'un état à un autre s'effectue à l'aide d’appel de requêtes programmées dans le noyau. L'ensemble de ces requêtes forme les primitives de l'exécutif temps réel ou de l'exécutif multitâche. Les différents états possèdent les propriétés suivantes : NON CREE Tâche inconnue de l'exécutif. DORMANT OU CREE Tâche connue de l'exécutif. Un identificateur, ainsi qu'une zone de pile est attribué à la tâche. La tâche reste dans cet état tant qu'une requête d’activation ne la fait pas évoluer.
  • 13. Systèmes multitâches et systèmes temps réel -13- Systèmes multitâches et systèmes temps réel PRET OU ELIGIBLE Une tâche à l'état prêt est candidate pour l'exécution. Son lancement ne dépend que de sa priorité par rapport aux autres tâches éligibles ou en cours d'exécution. Lorsque celle-ci devient prioritaire, l'ordonnanceur lui attribue le processeur. L'exécution de la tâche commence alors à son début. EN EXECUTION Une tâche en exécution est une tâche en possession du processeur. Dans un système monoprocesseur, plusieurs tâches de même priorité peuvent être en exécution mais une seule tâche est en possession du processeur réel, chaque tâche se voyant attribuer un quantum de temps processeur. Une tâche reste dans cet état tant qu'elle est prioritaire et qu'elle ne n’exécute pas une requête de suspension directe ou indirecte. BLOQUE OU SUSPENDU Une tâche à l'état bloqué ou à l'état suspendu ne possède plus le processeur et son état est sauvegardé en vue de sa reprise ultérieure.. TRANSITIONS ENTRE ETATS D'UNE TACHE Le franchissement des transitions et la conséquence d'appels directs ou indirects aux primitives de l'exécutif. CREATION D'UNE TACHE (CREE) Créer une tâche consiste à la faire connaître de l'exécutif : fournir une adresse de début, une taille de pile et un niveau de priorité. En retour le noyau fournit un identificateur permettant de référencer la tâche. La détermination de la taille de pile n'est pas chose aisée. La pile est utilisée pour loger les variables locales et les adresses de retour lors des appels de fonctions. Elle sert également à mémoriser le contexte du processeur lors des commutations de tâche. Une pile insuffisante est souvent la cause de dysfonctionnement mystérieux. En marquant la pile de la tâche, lors de la création, il est possible de déterminer en fin de programme la quantité de pile consommée et ainsi optimiser la taille de la pile. Ce mécanisme est utilisé dans plusieurs noyaux temps réels dont MTR86® DOS. Certains systèmes utilisent les mécanismes d'exceptions du processeur pour déterminer les débordements de pile et réallouer automatiquement une pile supplémentaire. Cette opération est cependant très dispendieuse en temps processeur. ACTIVATION D'UNE TACHE (ACTIVE) L'activation d'une tâche correspond à une demande d'exécution. L'activation peut être immédiate, différée ou cyclique. L'activation immédiate consiste à faire passer une tâche de l'état dormant à l'état prêt. En aucun cas elle ne correspond à l'appel direct de la fonction tâche : le lancement d'une tâche reste dévolu à l'ordonnanceur. Les demandes d'activation sont généralement mémorisées. Ainsi à trois demandes successives d'activations correspondent trois cycles ( on suppose que la tâche de termine ) : DORMANT → PRET → EXEC → DORMANT. Une activation différée consiste à lancer une tâche après un délai précis. Une activation cyclique consiste à relancer avec une période précise. Ces dernières requêtes ne sont pas toujours disponibles dans tous les noyaux. ATTRIBUTION DU PROCESSEUR A LA TACHE La tâche est lancée depuis son début. Son début d’exécution n'est pas forcément immédiat : si d'autres tâches (de même priorité) sont déjà dans cet état, la tâche attendra son tour suivant le mécanisme du tourniquet. Cette transition n'est pas maîtrisée par le programmeur, mais par l'ordonnanceur. SUSPENSION D'UNE TACHE La tâche est stoppée et son contexte est enregistré de façon à pouvoir reprendre exactement à l'endroit ou elle s’est arrêtée. La suspension de la tâche est obtenu par un appel direct (dort) ou indirect à travers une primitive et sur des conditions particulières (voir par la suite). REPRISE D'UNE TACHE BLOQUEE Cette transition n'est pas contrôlée directement. Lorsque toutes les conditions sont réunies pour continuer une tâche (priorité maximale de la tâche par rapport aux autres, disparition des conditions de blocage) le contexte de la tâche est rechargé et celle-ci est relancée à l'endroit où elle s'était arrêtée. FIN D'UNE TACHE OU DESTRUCTION D'UNE TACHE Une tâche terminée ou détruite est replacée à l'état prêt. La fin d'une tâche peut être obtenue par un appel explicite au noyau ou implicite lors de la fin de la tâche. SUPPRESSION D'UNE TACHE Pour optimiser l'usage des ressources, une requête supprime remet une tâche à l'état non créé. ATTRIBUTION DU PROCESSEUR REEL Cette transition a lieu de façon tous les quantum pour une tâche à l'état EXEC. Elle n'est pas maîtrisable directement. ATTRIBUTION DU PROCESSEUR VIRTUEL Cette transition a lieu chaque fois qu'une tâche a utilisé le processeur réel durant un quantum.
  • 14. Systèmes multitâches et systèmes temps réel -14- Systèmes multitâches et systèmes temps réel POLITIQUES D'ORDONNANCEMENT La politique d'ordonnancement définit le critère utilisé par le noyau pour élire une tâche. Diverses politiques d'ordonnancement sont utilisées : • Ordonnancement circulaire ou tourniquet : Chaque tâche possède la même priorité. Les tâches se partagent le processeur sans affinité particulière. Convient rarement pour les systèmes temps réel. 16 0609 CPU tâches actives tâches éligibles tâches suspendues tâches terminées file des tâches actives 02 04 03 0810 file des tâches suspendues • Ordonnancement à priorité fixe : Chaque tâche possède un niveau de priorité différent. La tâche exécutée est toujours la plus prioritaire. Bien adapté aux systèmes temps réels. 08 04 10 07 03 0609 CPU tâches actives tâches éligibles tâches suspendues tâches terminées filedestâchesactives file des tâches suspendues Priorités • Ordonnancement à classe de priorités : Combinaison des deux politiques précédentes. Les tâches sont groupées par classe de priorité au sein des quelles l'ordonnancement est de type circulaire. Ce système présente le maximum de souplesse. 16 0609 CPU tâches actives tâches éligibles tâches suspendues tâches terminées file des tâches actives 02 04 03 12 05 11 07 13 0810 file des tâches suspendues Priorité
  • 15. Systèmes multitâches et systèmes temps réel -15- Systèmes multitâches et systèmes temps réel • Ordonnancement par priorité avec modification dynamique de la priorité en fonction de l'age de la tâche. Cette politique fait perdre progressivement la priorité aux tâches : les tâches de courtes durée sont toujours exécutées en premier. Cette politique substitue au contrôle des tâches exercées par le programmeur, par celui exercé par le noyau. Permet de faire fonctionner (mal) des programmes présentant des erreurs de conception. A déconseiller pour la commande temps réel. SYSTEMES AVEC OU SANS REQUISITION DU PROCESSEUR Les systèmes multitâches et les noyaux temps réels se divisent en deux catégories : • Noyaux sans réquisition du processeur (no préemptive scheduling) ou encore appelés systèmes coopératifs, • Noyaux avec réquisition du processeur (préemptive scheduling). Noyau coopératif Avec un noyau coopératif, le programmeur décide de l’instant de commutation par un appel explicite. Selon la situation présente lors de l'appel, l'ordonnanceur décide, en fonction des priorités des tâches en cours, si celle-ci doit ou non se poursuivre. Si la priorité des tâches éligibles est supérieure à la tâche courante, le noyau la suspend et lance la tâche de priorité maximale. Ce type de noyau, très efficace mais moins tolérant aux erreurs de conception des applications, est généralement utilisé dans les applications à très forte contrainte de temps. Si pour une raison quelconque une tâche se bloque (erreur de programmation ou erreur matérielle), l'ensemble des tâches se bloque également. Windows 3.1 est un exemple typique de système coopératif. Noyau préemptif Un noyau avec réquisition du processeur alloue celui-ci de façon transparente aux tâches ( toujours en fonction des priorités en cours ). La tâche qui se trouve préemptée n'a aucune possibilité de le savoir. Ce mécanisme est réalisé par un programme d'interruption lancé par une interruption périodique à forte priorité. Ce programme d'interruption explore les files de tâches prêtes ou suspendues et attribue le processeur à l'une d'elle suivant une politique d'ordonnancement spécifique à chaque noyau. Temps de latence Les sections de code non interruptibles perturbent le fonctionnement des systèmes coopératifs et préemtif. Ces sections doivent être très courtes de façon à diminuer le temps s'écoulant en apparition d'une interruption et son traitement effectif. Ce temps, généralement appelé temps de latence, est des critères qui distinguent les noyaux temps réels mou (systèmes multitâches), des noyaux temps réel durs. Temps réel dur, temps réel mou Un noyau temps réel mou ne spécifie ni les temps de commutation de contexte, ni les temps de latence. Un noyau temps réel dur garanti un temps maximal de commutation de contexte et un temps de retard maximal entre l'apparition d'une d'interruption et son début de traitement. Les temps de commutation de contexte sont de l'ordre de 1 à 100 microsecondes (en fonction du noyau et du processeur), soit 10000 commutations / secondes dans le cas le plus défavorable. Le temps de latence est généralement inférieur à 50 microsecondes. Nota : la notion de temps réel dur et mou est très subjective : un noyau mou exécuté sur un processeur récent est plus performant qu'un noyau dur exécuté sur un processeur plus ancien. La puissance actuelle des processeurs permet d'utiliser des noyaux temps réel mou de nombreux cas d'applications de type contrôle-commande.
  • 16. Systèmes multitâches et systèmes temps réel -16- Systèmes multitâches et systèmes temps réel SERVICES OFFERTS MTR86-68K Ce chapitre sera consacré aux mécanismes de gestions des tâches, aux problèmes posés par l’accès concurrent aux données partagées, aux mécanismes de communications et aux mécanismes de synchronisation. Le support utilisé est le noyau Mtr86. Mtr86 existe sous différentes formes : • Mtr86 Dos : noyau temps réel dur. • Mtr86 68K pour EID210 : noyau temps réel dur. • Mtr86 Win32 : noyau temps réel mou (dans sa forme actuelle) • Mtr86 Win32 Objet : noyau temps réel mou (dans sa forme actuelle) STRUCTURE D’UNE APPLICATION MTR86-68K // ex1dos.c // Les fichiers à inclure dépendent de la cible #include "mtr86.h" TACHE init(void); TACHE T(void); // Tâche d’initialisation. Possède la priorité maximale. Aucune autre tâche ne peut // s’exécuter tant que celle ci n’est pas terminée ou suspendue TACHE init(void) { // créer les sémaphores, tubes, et autres mécanismes //... // créer et éventuellement activer les tâches active(cree(T, 1, 512)); // crée et active la tâche T. priorité 1, 512 octets de pile. // suspendre ou terminer la tâche. La fin de l’application est obtenu par un // appel à la primitive mtr86exit(0) dans une tâche quelconque; } TACHE T(void) { // ... } main() { // Lance la tâche init avec une taille de pile de 512 octets start_mtr(init, 512); } NOTA La taille de la pile est fonction des variables locales des tâches et contient le contexte de la tâche après sa commutation. Certaines fonctions de la bibliothèque C comme printf nécessitent 1Ko de pile. Il est prudent de déclarer 1Ko de pile au départ, puis la réduire jusqu’à ce que l’application se « plante », on ajoute ensuite environ 20% de pile.
  • 17. Systèmes multitâches et systèmes temps réel -17- Systèmes multitâches et systèmes temps réel MISE EN EVIDENCE DE LA POLITIQUE D’ORDONNANCEMENT DANS MTR86-68K // CEx1.c #include "mtr86.h" TACHE T1(void) { long j, i; // Durant cette séquence T2 et T3 sont arrêtés printf("T1: actif (priorite 1) et bloque T2 et T3 (priorite 2)n"); for (j=0; j < 1000000; j++); // remarque : cette séquence alloue le processeur pour un travail inutile (pour peut // être effectuer une temporisation). Cette façon de procéder gaspille le temps CPU // et ne doit jamais être utilisée. // Durant la séquence suivante T2, T3 sont actifs à chaque appel de dort durant 1 quantum. printf("T1: se suspend périodiquement et libère du temps processeur pour T2 et T3nn"); for ( j=0; j < 250; j++) { for (i=0; i < 10000; i++); // simuler l'activité de la tâche dort(1); // T2 et T3 actifs durant 1 quantum } MONITOR // accès exclusif de T1 à la ressource écran ( voir par la suite ) gotoxy(1,8); printf("T1 : fin -> accélération de T2 et T3nn"); ENDM // fin accès } // <- T1 est arrêté, ce qui alloue le processeur à T2 et T3 : l'affichage est accéléré TACHE T2(void) { // T2 est exécuté de façon concurrente avec T3 unsigned j, i; for (j=0 ; j < 300 ; j++) { MONITOR // accès exclusif de T2 à la ressource écran ( voir par la suite ) gotoxy(1,6); cprintf("T2 : j=%dn",j); ENDM // fin accès for (i=0; i < 1000; i++); // simuler l'activité de la tâche } } // <- T2 est arrêté TACHE T3(void) { unsigned j,i; // T3 est exécuté de façon concurrente avec T2 for (j=0 ; j < 500 ; j++) { MONITOR // accès exclusif de T2 à la ressource écran ( voir par la suite ) gotoxy(1,7); cprintf("T3 : j=%dn",j); ENDM // fin accès for (i=0; i < 300; i++); // simuler l'activité de la tâche } mtr86exit(0) ; // le noyau est stoppé. Sortie après start_mtr() // jamais exécuté } // Tâche d'initialisation. Possède la priorité maximale. Aucune autre tâche ne peut // s'exécuter tant que celle ci n'est pas terminée ou suspendue // La tâche init crée et active 3 tâches. // La tâche T1 est la plus prioritaire et préempte le processeur au détriment des // tâches T2 et T3. TACHE init(void) { printf("Init debutn"); active(cree(T1, 1, 1024)); // crée et active la tâche T. priorité 1, 512 octets de pile. active(cree(T2, 2, 1024)); // crée et active la tâche T. priorité 2, 512 octets de pile. active(cree(T3, 2, 1024)); // crée et active la tâche T. priorité 2, 512 octets de pile. printf("Init finn"); } // -> A partir d'ici la tâche T1 est lancée main() { // Lance la tâche init avec une taille de pile de 1024 octets clsscr(); printf("----------------Exemple ex1--------------------n"); start_mtr(init,1024); }
  • 18. Systèmes multitâches et systèmes temps réel -18- Systèmes multitâches et systèmes temps réel CREATION ET ACTIVATION DES D’INSTANCES MULTIPLES D’UNE MEME TACHE Lorsque les tâches effectuent un travail similaire, il est souhaitable de créer des instances multiples d’une même tâche. Le problème qui se pose alors à la tâche est de pouvoir s’identifier. L’exemple suivant crée et active 8 instances d’une même tâche, chacune affichant la valeur d’un compteur sur un emplacement différent de l’écran. EXEMPLE CEX2.C #include "mtr86.h" TACHE init(void); /* PROTOTYPES DES TACHES */ TACHE T(void); TACHE init(void) { int j; // dsp_stk(); // visualise les consummations en fin de prog for (j=0;j<8;j++) active(cree(T, 2, 1024)); /* créer et lance 60 tache */ dort(cvrtic(10000)); mtr86exit(0); } TACHE T(void) { int j=0; int x, y,tache; for (;;) { tache=tache_c()-2; /* lit le num‚ro de la tƒche courante (1er Nø= 2)*/ x=((tache%10)+1)*7; y=(tache/10)+1; MONITOR gotoxy(x,y+10); printf("%dn", j++); ENDM dort(10); } } /* ** M A I N */ main() { clsscr(); printf(" Exemple de creation d'instances multiples d'une meme tache.rn" "init() creee 8 taches T() identiques, chacune incremente un compteurrn" "et l'affiche sur l'ecran.rn" "Fin dau bout de 10s.rn"); start_mtr(init, 1024); }
  • 19. Systèmes multitâches et systèmes temps réel -19- Systèmes multitâches et systèmes temps réel PROBLEMES POSES PAR L’ACCES CONCURRENT AUX FONCTIONS ET AUX DONNEES PARTAGEES REENTRANCE DES FONCTIONS Une fonction est réentrante si elle s'exécute correctement lorsqu'elle est appelée simultanément par plusieurs tâches ou par un programme d'interruption. Une fonction effectuant des récursions directes ou indirectes doit être réentrante. Une fonction réentrante ne peut modifier des variables situées à des emplacements fixe de la mémoire (variables statiques en 'C' ou ‘C++’). Toutes les variables utilisées et modifiées par une fonction réentrante doivent être situées dans la pile ou les registres du processeur. Voici un exemple de fonction non réentrante: int fac; int factorielle( int n ) { fac = 1; while ( n != 0 ) { fac = fac * n; n--; } return ( fac ); } TACHE A(void)1 { ... factorielle(5); ... } TACHE B(void) { ... factorielle(3); ... } Deux taches A et B effectuent simultanément l'appel à factorielle. La fonction factorielle n'est pas réentrante du fait que le variable fac est de type static, donc situé à un emplacement fixe de la mémoire. Supposons que les tâches A et B soient lancés simultanément et que la tâche B s'exécute en premier et effectue fac = 1. Supposons que la tâche A prend possession du processeur juste après cette affectation et réalise le calcul factorielle(5). Lorsque la tâche A se termine et le calcul de factorielle(5) a donné à fac la valeur 120 (!5). Le calcul reprend ensuite dans B avec fac = 120 et fournit évidemment un résultat faux pour factorielle(3). Pour rendre la fonction factorielle réentrante il suffit de déclarer fac dans le corps de la fonction (avec le type auto ou register). L'appel de factorielle par une tâche créé une nouvelle variable fac dans la pile de la tâche. Comme chaque tâche possède sa propre pile, la fonction factorielle peut travailler simultanément avec plusieurs tâches. L'exécution d'une fonction non réentrante n'a ici pour conséquence qu'une erreur de calcul. Il en serait tout autrement si la fonction doit prendre des décisions (test sur la valeur d’une variable à accès concurrent). Une fonction réentrante ne doit pas appeler une fonction non réentrante sous peine de devenir à son tour non réentrante. Une fonction doit être réentrante si elle est susceptible d'être appelée simultanément par plusieurs tâches. Utilisation de la librairie C et C++ MsDos et GNU Un certain nombre de fonctions de la librairie C ne sont pas réentrantes. La bibliothèque mathématique utilisant les flottants n'est jamais réentrante. Cela est dû au procédé utilisé pour retourner les résultats. Pour Borland C++ par exemple, les flottants sont retournés dans la pile du NPD (émulée ou non). Lorsque le NPD est émulée le résultat est placé en mémoire statique. Dans le cas contraire (présence effective du coprocesseur), le résultat est situé dans la pile du processeur mathématique et la réentrance est assurée à condition d'autoriser les commutations de contexte du coprocesseur. Les fonctions d'allocation mémoire (malloc, calloc, free, sbrk ...) ne sont pas réentrantes car elles utilisent une variable globale pour gérer l'extension du tas. Les fonctions E/S fichier utilisant des tampons (fopen, fclose, fwrite, fread, printf, fprintf ...) ne sont pas réentrantes car elles utilisent les fonctions d'allocation de mémoire dynamique (malloc, ...) pour créer les tampons. Les fonctions d'une l'application compilée avec l'option de vérification de pile ne sont pas réentrantes. La plupart des appels bios ne sont pas réentrant particulièrement les E/S clavier et console.
  • 20. Systèmes multitâches et systèmes temps réel -20- Systèmes multitâches et systèmes temps réel SECTIONS CRITIQUES Une section critique est une zone de programme où se produit un accès concurrent et dont le résultat des traitements et fonction de l’ordonnancement des tâches ou des processus. Un programme multitâche ou multiprocessus absolument verrouiller l’accès aux sections critiques en en autorisant l’accès à une seule tâche à la fois. La multiplicité des sections critiques produit à un code peu efficient du fait des appels au noyau destiné à verrouiller les sections critiques. VERROUILLAGE DES SECTIONS CRITIQUES DANS UN MEME PROCESSUS Une section critique peut être verrouillée par différents moyen : moniteurs, gestionnaire de ressource, mutex, sémaphores. VERROUILLAGE DES SECTIONS CRITIQUES PAR MONITEUR ET PAR GESTIONNAIRE DE RESSOURCES Mtr86-68K Mtr86 offre un seul moniteur pour permettre le verrouillage rapide à une seule ressource généralement l’écran : MONITOR //instructions d’accès à l’écran ENDM Mtr86 Dos et Mtr86Win32 disposent également d’une méthode plus générale constitué par le gestionnaire de ressources. Une ressource (utilisable uniquement au sein du même processus) est crée par la requête suivante : RESSOURCE r1 ; // doit être global ... r1 = r_cree(); Une section critique est verrouillée par les appels suivant : r_wait(r1); // verouillage par le verrou r1 ... // section critique r_signal(r1); // déverrouillage de la section critique Le gestionnaire de ressource s'assure que seule la tâche ayant pris possession de la ressource par r_wait peut la libérer. MODELES STANDARD D'ECHANGES DE DONNEES ENTRE TACHES ET PROCESSUS L'exclusion mutuelle des sections critiques est un mécanisme rudimentaire de verrouillage lorsqu'il est utilisé pour échanger des données. Dans ce type d'échange, il n'y a aucune synchronisation entre les protagonistes. Le seul service fourni par l'exclusion mutuelle est la garantie qu'une donnée est lue ou écrite de façon atomique, ou que dans l'intervalle de temps entre la lecture de la donnée et son test, celle-ci n'a pas été modifiée. Lorsqu'on désire synchroniser les échanges entre protagonistes deux modèles sont utilisés : • Le modèle producteur-consommateur • Le modèle lecteur-rédacteur MODELE PRODUCTEUR-CONSOMMATEUR Ce modèle est la base de nombreux échanges de données que ce soit au cœur d'un système d'exploitation ou au sein d'applications. Son importance est telle que la plupart des noyaux fournissent directement ce type d'échange (tubes, messages, ...). Les règles de production et de consommation sont les suivantes : • Une donnée ne peut être consommée que si a été produite, • Une donnée ne peut être produite que si la donnée précédente a été consommée, Il est résulte qu'une même donnée ne peut être consommée ou produite plusieurs fois. L'implémentation de ce mécanisme est basé sur la propriété des sémaphores. LES SEMAPHORES Les sémaphores ont été proposés pour la première fois en 1965 par E. W. Dijkstra. Dijkstra propose de compter le nombre de tâches endormies ou les réveils en attente à l'aide d'une variable appelée sémaphore. Un sémaphore est constitué d'un entier s signé pouvant prendre des valeurs positives et négatives ou nulles et d'une file d'attente mémorisant (indirectement) les contextes des tâches ou processus en attente. L'accès à la variable s est effectué dans une section critique.
  • 21. Systèmes multitâches et systèmes temps réel -21- Systèmes multitâches et systèmes temps réel Deux primitives permettent de manipuler le sémaphore : P(s) et V(s). P(s) : s <- s-1 Si s < 0 Alors bloquer la tâche courante mémoriser l'identificateur de la tâche courante dans la file f(s) Fin V(s) : s <- s+1 Si s ≥ 0 Alors relancer de la tâche en tête de la file f(s) Fin La modification et le test de s (zone en gras) constituent une section critique protégé par un mécanisme d'exclusion mutuel non représenté. Implémentation des sémaphores dans Mtr86 Création d'un sémaphore SEMAPHORE s1; // variable globale ... s1 = s_cree(VALEUR_INITIALE); P(s) s_wait(s1, TIMEOUT); Pour éviter tout risque d'interbloquage, la durée d'attente peut être bornée par une valeur TIMEOUT. Si sa valeur est 0, l'attente n'est pas bornée et s_wait retourne toujours OK. Si l'opération est bornée la valeur retournée est TIM si le délai d'attente est dépassé. V(s) s_signal (s1); // incrémente le sémaphore Exemples d'utilisation Création // Instanciation d'un tableau de sémaphores, puis création Semaphore s[10]; ... for (int j=0; j<10; j++) s[j].cree(1); // valeur initiale 1, valeur max 2048 // Instanciation d'un sémaphore local Semaphore s1(1); // valeur initiale 1, valeur max 2048 // Instanciation d'un sémaphore inter processus Semaphore s2(1, VALEUR_MAX, "Semaphore2" ); // Instanciation d'un sémaphore inter processus déjà existant Semaphore s2(1, NB_TACHES, "Semaphore2", OPEN ); P(s) s1.P(); V(s) s1.V();
  • 22. Systèmes multitâches et systèmes temps réel -22- Systèmes multitâches et systèmes temps réel IMPLEMENTATION DU MODELE PRODUCTEUR-CONSOMMATEUR Modèle producteur consommateur simple Mtr86-68K SEMAPHORE sCons, sProd; DONNEE d; ... TACHE init() { ... sCOns = s_cree(0); sProd = s_cree(1); ... } TACHE consommateur() { ... s_wait(sCons,0); // P(s) consommer(d); s_signal(sProd); // V(s) ... } TACHE producteur() { ... s_wait(sProd,0); // P(s) produire(&d); s_signal(sCons); // V(s) ... } Le sémaphore sCons est crée avec la valeur initiale 0, sProd avec la valeur initiale 1. Si la tâche consommateur s'exécute en premier elle se bloque par s_wait(sCons,0). Quand la tâche producteur s'exécute elle franchit la requête s_wait(sProd,0) puisque sProd est initialisé à 1. Le producteur produit la donnée et exécute V(s) ce qui relance le consommateur. Si le producteur exécute P(s) avant que le consommateur exécute V(s) il se bloque à son tour.
  • 23. Systèmes multitâches et systèmes temps réel -23- Systèmes multitâches et systèmes temps réel Dans les exemples suivants, la donnée est constituée d'un entier 0..255. Exemple C complet pour Mtr86-68K // ProdConsS.c #include "mtr86.h" #define P(s) s_wait (s,1000) #define V(s) s_signal(s) unsigned donnee; SEMAPHORE sProd, sCons; TACHE Producteur(void) { int j; for (j=0; j < 256; j++) { P(sProd); donnee = j; V(sCons); } } TACHE Consommateur(void) { for (;;) { if ( P(sCons) == TIM ) // sortie si attente de plus de 1000 tics break; // plus de données cprintf(" %3dn", donnee); V(sProd); } s_close(sProd); s_close(sCons); mtr86exit(0); } TACHE init(void) { sProd = s_cree(1); sCons = s_cree(0); active(cree(Producteur, 1, 256)); active(cree(Consommateur, 1, 1024)); } main() { clrscr(); start_mtr(init, 512); }
  • 24. Systèmes multitâches et systèmes temps réel -24- Systèmes multitâches et systèmes temps réel Modèle producteur consommateur avec stocks Le modèle producteur consommateur simple est très peut utilisé puisqu'il synchronise son exécution sur l'acteur le plus lent. En mémorisant les données produites dans une file fifo, il devient possible de produire et de consommer les données à des vitesses différentes. Cependant : • lorsque la file est pleine le producteur se synchronise sur le consommateur, • lorsque la file est vide le consommateur se synchronise sur le producteur, La taille du stock fixe la valeur valeur maximale du sémaphore producteur et la taille de la fifo. Exemple C complet pour Mtr86-68K // ProdConsStock.c #include <stdio.h> #include "mtr86.h" #define P(s) s_wait (s,cvrtic(1000)) #define V(s) s_signal(s) #define TAILLE_FIFO 10 /* * Déclaration de la fifo */ typedef struct { int stock[TAILLE_FIFO]; unsigned ie, is; } Fifo; void fifo_init(Fifo* fifo) { fifo->ie=fifo->is=0; } void Add(Fifo* fifo, int val) { fifo->stock[fifo->ie++ % TAILLE_FIFO] = val; } int Get(Fifo* fifo) { return fifo->stock[fifo->is++ % TAILLE_FIFO]; } SEMAPHORE sProd, sCons; RESSOURCE r1; Fifo q1; TACHE Producteur(void) { int j; for (j=0; j<TAILLE_FIFO; j++) { P(sProd); Add(&q1, j); V(sCons); } } TACHE Consommateur(void) { for (;;) { if ( P(sCons) == TIM ) break; // plus de données cprintf("%03dn", Get(&q1)); V(sProd); } s_close(sProd); s_close(sCons); mtr86exit(0); } TACHE init(void) { fifo_init(&q1); sProd = s_cree(TAILLE_FIFO); sCons = s_cree(0); active(cree(Consommateur, 2, 1024)); active(cree(Producteur, 2, 512)); }
  • 25. Systèmes multitâches et systèmes temps réel -25- Systèmes multitâches et systèmes temps réel main() { clrscr(); start_mtr(init, 512); } La valeur initiale du sémaphore sProd doit être égale à la quantité de la file fifo que l'on désire utiliser. Il faut noter ici que la file fifo est un objet global dont l'accès est concurrent, mais l’accès ne porte pas sur les mêmes données. Il n’est donc nécessaire de verrouiller l’accès à la fifo. Exemple C++ complet pour Mtr86-68K En écrivant le programme en C++, il se simplifie notablement. La gestion de la fifo est encapsulée dans une classe Fifo comme le montre l’exemple ci-dessous. // ProdConsStock.cpp #include <stdio.h> #include "mtr86.h" #define P(s) s_wait (s,cvrtic(1000)) #define V(s) s_signal(s) #define TAILLE_FIFO 10 class Fifo { int stock[TAILLE_FIFO]; unsigned ie, is; public : Fifo() { ie=0; is=0; } void Add(int val) { stock[ie++ % TAILLE_FIFO] = val; } int Get() { return stock[is++ % TAILLE_FIFO]; } }; SEMAPHORE sProd, sCons; Fifo q1; TACHE Producteur(void) { int j; for (j=0; j<TAILLE_FIFO; j++) { P(sProd); q1.Add(j); V(sCons); } } TACHE Consommateur(void) { for (;;) { if ( P(sCons) == TIM ) break; // plus de données cprintf("%03dn", q1.Get()); V(sProd); } s_close(sProd); s_close(sCons); mtr86exit(0); } TACHE init(void) { sProd = s_cree(TAILLE_FIFO); sCons = s_cree(0); active(cree(Consommateur, 2, 1024)); active(cree(Producteur, 2, 512)); } main() { clrscr(); start_mtr(init, 512); }
  • 26. Systèmes multitâches et systèmes temps réel -26- Systèmes multitâches et systèmes temps réel Utilisation de la stl avec Mtr86-68K Exemple C++ complet pour Mtr86-68K La fifo peut être un conteneur de la STL. La fifo de la STL possède une taille auto adaptative : elle s'agrandit au fur est à mesure des besoins, tout dépend de la vitesse de consommation par rapport à la production, mais en aucun cas la taille de la file ne peut dépasser la valeur initiale du sémaphore sProd ( TAILLE_FIFO ). La STL standard n’est pas multithread il faut donc verrouiller l’accès au conteneur, à moins de d’analyser le code d’accès au conteneur queue pour vérifier qu’il n’y a pas de section critique. // ProdConsStockSTL.cpp #include <iostream> #include <queue> #include "mtr86.h" using namespace std; #define P(s) s_wait (s,cvrtic(1000)) #define V(s) s_signal(s) #define TAILLE_FIFO 10 SEMAPHORE sProd, sCons; RESSOURCE r1; // Définir le conteneur de la stl utilisé typedef queue<int> Fifo; Fifo q1; TACHE Producteur(void) { int j; for (j=0; j<TAILLE_FIFO; j++) { P(sProd); r_wait(r1); q1.push(j); r_signal(r1); V(sCons); } } TACHE Consommateur(void) { for (;;) { if ( P(sCons) == TIM ) break; // plus de données r_wait(r1); cout << q1.front() << endl; q1.pop(); r_signal(r1); V(sProd); } s_close(sProd); s_close(sCons); mtr86exit(0); } TACHE init(void) { sProd = s_cree(TAILLE_FIFO); sCons = s_cree(0); r1 = r_cree(); //utilisé pour l'exclusion mutuelle des sections critiques dans la STL active(cree(Consommateur, 2, 1024)); active(cree(Producteur, 2, 1024)); } main() { clrscr(); start_mtr(init, 512); }
  • 27. Systèmes multitâches et systèmes temps réel -27- Systèmes multitâches et systèmes temps rée IMPLEMENTATION DU MODELE PRODUCTEUR CONSOMMATEUR DANS LES SYSTEMES MULTITACHES TEMPS REEL Vu l'importance du modèle producteur consommateur, celui-ci est directement implémenté dans la plupart systèmes multitâches et les noyaux temps réel. Cette implémentation porte le nom de tube, pipe, ..., messages. IMPLEMENTATION DES TUBES DANS MTR86-68K Un tube ou pipe peut être considéré comme ayant une taille infinie. La quantité de données pouvant passer par le tube n’est pas limitée par la taille du tube. Un pipe est crée par l'appel suivant : Tube p1; ... p1 = p_open( id_producteur, id_consommateur, TIMEOUT ); On remarque que l'id du producteur et du consommateur doit être connu, ce qui implique que les utilisateurs du tube doivent être crée avant celle du tube. Si TIMEOUT est égal à 0, les opérations avec le tube p1 ne sont pas bornées dans le temps. p1 permet par la suite de référencer le tube. L'écriture dans le tube est réalisé par : p_write( p1, adresse_donnee, taille_donnee ); La lecture depuis le tube est réalisé par : p_read( p1, adresse_donnee, taille_donnee ); p_write et p_read retournent TIM dans le cas d'opérations bornées avec dépassement de temps ou OK dans tous les autres cas. L'état du tube peut être testé par : plein(p1); // retourne le nombre d'emplacements disponibles vide(p1); // retourne le nombre d'éléments dans le tube Ces primitives permettent de s'assurer qu'une tâche ne se suspendra pas en écriture sur un de tube plein, ou en lecture sur un tube vide. Ces informations sont indispensables si les tubes sont utilisés dans les routines d'interruption. En fin d'utilisation un tube est clos par : p_close(p1); Reprenons les deux exemples précédents avec Mtr86-68K
  • 28. Systèmes multitâches et systèmes temps réel -28- Systèmes multitâches et systèmes temps réel // Pipe.cpp #include <stdio.h> #include "mtr86.h" TUBE p1; unsigned hCons, hProd; TACHE Producteur(void) { for (int j=0; j < 25; j++) p_write( p1, &j, sizeof(j) ); } TACHE Consommateur(void) { int donnee; for (;;) { if ( p_read(p1, &donnee, sizeof(donnee)) == TIM ) break; // plus de données cprintf(" %03dn", donnee); } p_close(p1); mtr86exit(0); } TACHE init(void) { hCons = cree(Consommateur, 2, 1024); hProd = cree(Producteur, 2, 512); p1 = p_open(hProd, hCons, cvrtic(1000)); // timeout 1s sur les opérations active(hCons); active(hProd); } main() { clrscr(); start_mtr(init, 512); }
  • 29. Systèmes multitâches et systèmes temps réel -29- Systèmes multitâches et systèmes temps réel MODELE LECTEUR - REDACTEUR Contrairement au modèle producteur - consommateur, le modèle lecteur - rédacteur fait appel à plusieurs rédacteurs et plusieurs lecteurs simultanés. Le modèle lecteur - rédacteur est le modèles de base dans les applications de type client serveur. Il se conforme aux règles suivantes : • Une donnée peut être lue simultanément par plusieurs lecteurs sans consommation de celle-ci. • Un seul rédacteur à la fois peut modifier une donnée. • Lorsqu'un rédacteur est actif, tous les lecteurs sont interdit de lecture. • Lorsqu'un ou plusieurs lecteurs sont actifs tous les rédacteurs sont interdit d'écriture. Le problème se trouve simplifié lorsque le nombre de lecteurs et de rédacteurs et connu par avance et que le noyau autorise les opérations P et V avec un nombre différents de 1. Mtr86 objet dans sa version actuelle n'autorise que les opérations V avec un nombre différents de 1, les autres versions n'autorisant que les opérations P et V simples. ELEMENTS DE SOLUTION Hypothèses : L'implémentation des sémaphores permet que les opérations P et V avec un incrément de 1. Problème posé : Un nombre de lecteurs et de rédacteurs connus par avance, lisent et écrivent de façon concurrente dans une zone de mémoire commune : Pour le rédacteur r : for (j=0; j < MAX; j++) { // MAX = 1000 tampon[j]=r; // zône de mémoire à commune <- N° rédacteur for ( int i=0; i < 1000; i++); // consommer du temps CPU pour faire apparaitre } // des aléas éventuel Pour un lecteur quelconque : for (j=0; j< MAX; j++) chk +=tampon[j]; affiche(chk); Si lecteurs et rédacteurs sont en concurrence et sans protection particulière, chaque lecteur affichera une valeur quelconque. En appliquant le modèle lecteur – rédacteurs, le rédacteur enregistre son numéro dans le tampon sans être interrompu. Les lecteurs afficheront alors des valeurs égales à : x0000 avec x = 0..nombre_max_de_rédacteurs Le modèle lecteur – rédacteur est implémenté dans quatre fonctions : demande_ de_lecture, demande_ecriture, fin_de_lecture et fin_ecriture. • avant d'effectuer un accès à la donnée, le lecteur et le rédacteur appellent respectivement une fonction demande_ de_lecture et demande_ecriture • à la fin de l'accès à la donnée, le lecteur le lecteur et le rédacteur appellent respectivement une fonction fin_de_lecture et fin_ecriture Etude préalable : 1. Chaque lecteur et chaque rédacteur effectuant une tentative d'accès à la donnée alors qu'un rédacteur est actif se bloque sur une opération P. 2. Si aucun rédacteur est actif, tout lecteur peut accéder à la donnée. 3. Si au moins un lecteur est actif, tout rédacteur effectuant une tentative d'accès à la donnée se bloque sur une opération P. 4. Lorsqu'un rédacteur a terminé d'accéder à la donnée, il doit ou relancer chaque lecteurs bloqués par une opération V (autant d'opérations que de lecteurs bloqués) ou un des rédacteurs bloqué par une opération V. Deux algorithmes sont possibles : donner la priorité aux lecteurs ou aux rédacteurs. 5. Lorsque le dernier lecteur a terminé d'accéder à la donnée, il doit relancer un rédacteur si au moins un rédacteur est bloqué. ü Un sémaphore est attribué aux les lecteurs (sl) et rédacteurs (sr). ü Des points 1 et 2 on en déduit qu'il faut connaître l'opération en cours : on utilisera une variable d'état r signalant qu'un rédacteur est actif.
  • 30. Systèmes multitâches et systèmes temps réel -30- Systèmes multitâches et systèmes temps réel ü Des points 3 et 5 on en déduit qu'il faut connaître le nombre de lecteurs actifs et le nombre de rédacteurs bloqués afin de pouvoir appliquer une opération V valide. La variable d'état nl comptera le nombre de lecteurs actifs et rb comptera le nombre de rédacteurs bloqués. ü Du point 4 on en déduit qu'il faut compter les lecteurs bloqués : On utilisera une variable d'état lb. Sans tenir compte des sections critiques on en déduit l'algorithme de demande_de_lecture : void demande_de_lecture(void) { if (r==TRUE) { /* Si écriture en cours */ lb++; /* un lecteur bloqué de plus */ P(sl); /* bloquer le lecteur */ } nl++; /* Un lecteur de plus */ } Cet algorithme présente des sections critiques : r, lb , nl sont des variables globales modifiés de façon concurrente. En verrouillant les sections critiques avec le gestionnaire de ressource, l'algorithme précédent devient : void demande_de_lecture(void) { r_wait(mutex); /* Début section critique */ if (r==TRUE) { /* Si écriture en cours */ lb++; /* un lecteur bloqué de plus */ r_signal(mutex); /* Fin section critique */ P(sl); /* bloquer le lecteur */ r_wait(mutex); /* Début section critique */ } nl++; /* Un lecteur de plus */ r_signal(mutex); /* fin section critique */ } Cet algorithme présente une erreur subtile: il existe une zone de section critique non verrouillée : il faut un temps non nul entre la fin de l'opération P et le verrouillage effectif de la section critique : P(sl); /* bloquer le lecteur */ r_wait(mutex); /* Début section critique */ Durant ce temps un rédacteur peut s'y glisser si la commutation de tâche a lieu juste à ce moment là. Cet aléa se produit rapidement avec un noyau temps réel dur et une durée de quantum très courte. Par exemple avec Mtr86 Dos réglé avec un quantum d'une milliseconde l'aléa se produit en moins d'une seconde ( 1000 commutations de tâches ). Pour un noyau temps réel mou il faut parfois des heures, voire des jours pour que cet aléa se manifeste mais il finira toujours par se manifester. On voit ici qu'il peut être très difficile d'écrire des applications multitâches ou temps réel fiables ! Finalement l'algorithme correct est donné ci-dessous : void demande_de_lecture(void) { r_wait(mutex); /* Début section critique */ while (r==TRUE) { /* Si écriture en cours */ lb++; /* un lecteur bloqué de plus */ r_signal(mutex); /* Fin section critique */ P(sl); /* bloquer le lecteur */ r_wait(mutex); /* Début section critique */ } nl++; /* Un lecteur de plus */ r_signal(mutex); /* fin section critique */ } Si un rédacteur est actif réel entre P et r_wait il aura positionné la variable d'état r à vrai et le lecteurs se bloquera à nouveau.
  • 31. Systèmes multitâches et systèmes temps réel -31- Systèmes multitâches et systèmes temps réel Fonction fin_de_ lecture void fin_de_lecture(void) { r_wait(mutex); /* Début section critique */ nl--; /* un lecteur de moins */ if (nl==0 && rb>0) { /* Si dernier lecteur et rédacteurs bloqués */ rb--; /* un rédacteur bloqué en moins */ s_signal(sr); /* un rédacteur relancé */ } r_signal(mutex); /* Fin section critique */ } Fonction demande_ecriture void demande_ecriture(void) { r_wait(mutex); /* Début section critique */ while (r==TRUE || nl>0) { /* Tant qu'‚criture ou lecture en cours */ rb++; /* Un rédacteur bloqué de plus */ r_signal(mutex); /* Fin section critique */ s_wait(sr, 0); /* Bloquer le rédacteur */ r_wait(mutex); /* Début section critique */ } r=TRUE; /* Ecriture en cours */ r_signal(mutex); /* Fin section critique */ } Fonction fin_ecriture #ifdef PRIORITE_LECTEURS void fin_ecriture(void) { r_wait(mutex); /* Début section critique */ r=FALSE; /* Fin écriture */ if ( lb >0 ) /* Si lecteur bloqué */ do { lb--; /* Un lecteur bloqué en moins */ s_signal(sl); /* lecteur relancé */ } while (lb > 0); /* tant qu'il reste des lecteurs bloqués*/ else if ( rb > 0) { /* Si rédacteur bloqué */ rb--; s_signal(sr); /* relancer un rédacteur */ } r_signal(mutex); /* Fin section critique */ } #else void fin_ecriture(void) { r_wait(mutex); r=FALSE; if ( rb != 0) { rb--; s_signal(sr); } else if ( lb != 0 ) do { lb--; s_signal(sl); } while (lb!=0); r_signal(mutex); } #endif
  • 32. Systèmes multitâches et systèmes temps réel -32- Systèmes multitâches et systèmes temps réel SYNCHRONISATIONS DES TACHES ET PROCESSUS Problème posé Il s’agit de synchroniser plusieurs tâches ou un processus entre eux. Deux types de mécanisme peuvent être utilisés : § Mécanismes de synchronisation directs : ils désignent directement la tâche à synchroniser § Mécanismes de synchronisation indirects : désignent indirectement la tâche à synchroniser à travers un objet du noyau. MECANISMES DE SYNCHRONISATION DIRECTS Ces mécanismes utilisent les primitives dort et réveille : § une tâche attend un signal de synchronisation en se suspendant (pour une durée bornée ou non) à l'aide le la primitive dort, § une ou plusieurs autres tâches désignent la tâche à relancer à l'aide de la primitive reveille. L'implémentation des primitives dort et reveille est fonction du type de noyau : dans les noyau temps réels "dur", les signaux de réveil ne sont généralement pas mémorisés alors qu'il le sont toujours dans les noyaux "mou" . Ce mécanisme très simple est généralement utilisé pour synchroniser une tâche logicielle sur une tâche matérielle. Exemple : 1. Une tâche (logicielle) lance une opération E/S (par interruption) pour se suspend en appelant la primitive dort. 2. Lorsque l'opération E/S est terminée le périphérique concerné lance une tâche matérielle (programme d'interruption) qui relance la tâche logicielle initiatrice le l'opération E/S à l'aide de la primitive reveille. MECANISMES DE SYNCHRONISATION INDIRECTS Ces mécanismes utilisent des objets du noyau par l'intermédiaire desquels s'effectue la synchronisation effective. Les objets utilisable pour cet usage sont les sémaphores, les variables événements, les variables rendez-vous. SYNCHRONISATION A L'AIDE DE SEMAPHORES Les sémaphores peuvent êtres utilisés de la même manière que les primitives dort et reveille avec cependant moins d'efficacité du fait de la gestion de la file d'attente du sémaphores. Exemple : 1. Une tâche (logicielle) lance une opération E/S puis se suspend en appelant la primitive P(s) avec s initialisé à 0. 2. Lorsque l'opération E/S est terminée, le périphérique concerné lance une tâche matérielle (programme d'interruption) qui effectue la primitive V(s). La tâche initiatrice le l'opération E/S se trouve relancée et récupère la donnée. Ce mécanisme est plus souple que le précédent : il n'est pas nécessaire de connaître la tâche à relancer, mais uniquement le sémaphore concerné. Exemple 1 : le canyon et les babouins. Un étudiant en anthropologie participe à un projet de recherche visant à déterminer si l'on peut apprendre l’inter blocage aux babouins. Il repère un canyon profond et suspend une corde en travers pour que les babouins puissent le traverser. Plusieurs babouins peuvent traverser simultanément s'ils vont tous dans le même sens. Un inter blocage se produit si des babouins qui se dirigent en sens opposé se retrouvent en même temps sur la corde, les babouins ne pouvant se croiser au-dessus du canyon. Si un babouin veut traverser le canyon, il doit d'abord s'assurer qu'aucun autre babouin ne se déplace dans la direction opposée. Avant de traverser coté nord un babouin fait appel à la procédure Entrer_N() puis en sortant coté sud il fait appel à la procédure Sortir_S. Travail demandé : 1. Ecrire le programme simulant l’arrivée simultanée de 10 babouins dans chaque sens. Afficher les traversées par –>EN, SD–> ou –>ES SN–>. La solution trouvée devra permettre la traversée du canyon par un seul babouin à la fois. 2. L’arrivée des babouins est simulée par la transition 1 vers 0 des bits 0 et 8 du port B (bit 0 pour les babouins venant du nord). La traversée est simulée par le décalages des bits sur le port A toutes les 100 ms (7 -> 0 pour les babouins venant du sud et 0 -> 7 pour les babouins venant du nord). Ecrire le programme sachant que la solution trouvée devra permettre la traversée du canyon par un seul babouin à la fois.. 3. Modifier le programme 1 de façon à permettre à 4 babouins de traverser simultanément dans le même sens.
  • 33. Systèmes multitâches et systèmes temps réel -33- Systèmes multitâches et systèmes temps réel Exemple 1 : les babouins et le canyon, éléments de corrigé. La question 1 s’apparente à une simple exclusion mutuelle. On utilise un sémaphore Sem initialisé à 1. Le premier babouin faisant l’opération P(Sem), bloque le passage à tous les autres babouins. // babouins1.c #include "mtr86.h" #define P(s) s_wait(s, 0) #define V(s) s_signal(s) #define MAX 10 // Nombre de babouins arrivant simultanément SEMAPHORE Sem; void entrer_S(void) { P(Sem); printf("->ESn"); } void entrer_N(void) { P(Sem); printf("->ENn"); } void sortir_N(void) { V(Sem); printf("SN->n"); } void sortir_S(void) { V(Sem); printf("SS->n"); } TACHE babouinN(void) { entrer_N(); dort(100); sortir_S(); } TACHE babouinS(void) { entrer_S(); dort(100); sortir_N(); } TACHE init(void) { int i; Sem = s_cree(1); cree(babouinN, 2, 1024); cree(babouinS, 2, 512); for (i=0; i< MAX; i++) active(num_tache(babouinN)); for (i=0; i< MAX; i++) active(num_tache(babouinS)); }
  • 34. Systèmes multitâches et systèmes temps réel -34- Systèmes multitâches et systèmes temps réel main() { cputs("Resolution du probleme des babouinsn"); InitSimulateur(); start_mtr(init,512); } La question 2 est similaire à la question 1 sauf que c’est la fonction traverse simulant la traversée qui fait appel aux fonctions sortir. Comme il n’y a qu’un baboin à la fois qui traverse une utilisera qu’une tâche par sens, chaque transitions sur les interruptions du port B activera la tâche correspondante. On notera ici que les signaux d’activation sont mémorisés, il en résulte, par exemple, que 10 transitions de 1 vers 0 du bit 0 du port B activeront 10 fois la tâche baboinN. // babouins2.c #include "mtr86.h" #define P(s) s_wait(s, 0) #define V(s) s_signal(s) SEMAPHORE Sem; int bN, bS; // compteurs de babouins actifs void printBab(unsigned char image, int sens) { int i; for(i=0; i<9; i++) { EcritPortA(image); if (sens==0) image <<=1; else image >>=1; dort(100); } } void Entrer_S(void) { P(Sem); // voie libre bS++; // un babouin de plus printf("->ESn"); } void sortir_S(void) { V(Sem); // libérer la voie printf("SS->n"); } void Entrer_N(void) { P(Sem); bN++; printf("->ENn"); } void sortir_N(void) { V(Sem); printf("SN->n"); }
  • 35. Systèmes multitâches et systèmes temps réel -35- Systèmes multitâches et systèmes temps réel TACHE babouinN(void) { Entrer_N(); } TACHE babouinS(void) { Entrer_S(); } TACHE traverse(void) { for(;;) { if(bN) // s’il y a un babouin coté nord { printBab(1, 0); // traverser bN--; // plus de baboin coté nord sortir_S(); } if(bS) { printBab(0x80, 1); bS--; sortir_N(); } dort(10); // dégager du temps CPU } } TACHE acquisition(void) { unsigned char port=0; unsigned fn=0; unsigned fs=0; for (;;) { port = LirePortB(); if(port&0x1 && fn==0) // bit à 1 fn=1; // le signaler if(port==0 && fn) // bit passant à 0 { fn=0; active(num_tache(babouinN)); } if(port&0x80 && fs==0) fs=1; if(port==0 && fs==1) { fs=0; active(num_tache(babouinS)); } dort(10); // dégaer du temps CPU } }
  • 36. Systèmes multitâches et systèmes temps réel -36- Systèmes multitâches et systèmes temps réel TACHE init(void) { Sem = s_cree(1); bS=0; bN=0; cree(babouinN, 2, 512); cree(babouinS, 2, 512); active(cree(traverse, 1, 512)); active(cree(acquisition,1, 512)); } main() { cputs("Resolution du probleme des baboinsn"); InitSimulateur(); start_mtr(init,512); }
  • 37. Systèmes multitâches et systèmes temps réel -37- Systèmes multitâches et systèmes temps réel Exemple 2 : Le repas des philosophes. On ne peut pas clore les mécanismes de synchronisation à l'aide de sémaphores sans parler du classique problème des philosophes. Ce problème de synchronisation a été posé et résolu par Dijkstra en 1965. Cinq philosophes sont réunis pour dîner et pour philosopher. Cinq couverts composés d'une assiette et d'une fourchette sont disposés autour d'une table circulaire. Chaque convive a devant lui un plat de spaghetti tellement glissant qu'il faut deux fourchettes pour les manger. Après réflexion les philosophes décident du protocole suivant : • Chaque philosophe reste à une place fixe, • Pour manger, un philosophe ne peut emprunter que la fourchette de droite ou de gauche, • Initialement tous les philosophes pensent, • Un philosophe est dans une des trois situations suivantes : il pense, il a faim, ou il mange. • Un philosophe mange durant un temps fini. La solution proposée doit permettre à deux philosophes de manger en même temps. Le repas des philosophes. Un philosophe i réalise les opérations suivantes : • penser(); • prendre_fourchette(gauche); • prendre_fourchette(droite); • manger(); • poser_fourchette(gauche); • poser_fourchette(droite); ü La procédure prendre_fourchette attend que la fourchette désignée soit libre, puis la prend. ü La procédure poser_fourchette(i) signale la libération de la fourchette désignée. ü Les procédures penser() et manger() sont des procédures vides. Les philosophes étant simulés par des tâches, il faut trouver une solution permettant à deux philosophes de manger en même temps. Le code ci-dessous présente une solution immédiate : 1. TACHE philosophe0( void ) 2. { 3. while (1) { 4. penser(); 5. prendre_fourchette(gauche); 6. prendre_fourchette(droite); 7. manger(); 8. poser_fourchette(gauche); 9. poser_fourchette(droite); 10. } 11. } Solution fausse du repas des philosophes. Cette solution est fausse : o Supposons que chaque convive prenne la fourchette de gauche en même temps. Aucun ne peut alors prendre la fourchette de droite. Cette situation s'appelle un inter blocage. o En supposant que chaque convive vérifie, avant de prendre la fourchette de droite, que celle-ci est libre, le problème échoue pour d'autres raisons : les philosophes prennent en même temps la fourchette de gauche, puis la reposent en voyant que la fourchette de droite n'est pas disponible et recommencent. Il n'y a pas ici d'inter blocage mais les tâches n'évoluent pas. Cette situation s'appelle une famine (au propre et au figuré !). Il existe une solution ne conduisant ni à un inter blocage, ni à une famine : elle consiste à protéger le code se trouvant entre les lignes 5 et 9 par un mécanisme d'exclusion mutuelle. Cette solution ne répond pas au cahier des charges car elle permet à un seul philosophe de manger à la fois. Solution du problème
  • 38. Systèmes multitâches et systèmes temps réel -38- Systèmes multitâches et systèmes temps réel Les philosophes étant simulés par de tâches, associons à chaque philosophe i une variable d'état etat[i] pouvant prendre une des valeurs suivantes en fonction de l'état du philosophe : q PENSE q AFFAME q MANGE Le passage de l'état PENSE à l'état MANGE n'est possible que si : etat[gauche] != MANGE && etat[droit] != MANGE avec gauche et droite définis par : #define gauche ( (i+4) % N ) /* N = nombre de philosophes */ #define droite ( (i+1) % N ) Cette ligne signifie que ni le philosophe assis à droite, ni le philosophe assis à gauche du philosophe i n'a pris de fourchette. Ce test est une séquence critique et doit être protégé à l'aide d'un mécanisme d'exclusion mutuel. Si ce test est faux le philosophe passe à l'état AFFAME (la tâche est suspendue) à l'aide d'une opération P(semp[i]), (avec semp[i] initialisé à 0), semp étant un tableau regroupant les sémaphores associés à chaque philosophe. Pour prendre les fourchettes, un philosophe i exécute la séquence suivante : 1. r_wait(mutex); // début de section critique 2. if (etat[gauche] != MANGE && etat[droite] != MANGE { 3. etat[i] = MANGE; 4. V(semp[i]); // semp[i] passe de 0 à 1 ce qui évitera la suspension ligne 9 5. } 6. else 7. etat[i] = AFFAME; 8. r_signal(mutex); // fin section critique 9. P(semp[I]); // bloque si lignes 3 et 4 ne sont pas exécutées Le passage d'un philosophe i de l'état MANGE à l'état PENSE entraîne le réveil des philosophes (tâches) à gauche et à droite si les conditions suivantes sont remplies : ü leur état doit être affamé, ü ils doivent disposer de l'autre fourchette. Ces conditions peuvent êtres vérifiées par un appel du type test(gauche) et test(droite) avec test : void test( int i ) { if ( etat[i] == AFFAME && etat[gauche] != MANGE && etat[droite] != MANGE ) { etat[i} = MANGE; V(semp[i]); // evite de bloquer sur P(semp[i]) } } Comme cette fonction teste des variables d'état à accès concurrent, l'appel de cette fonction doit être verrouillée. Finalement le programme complet pour Mtr86-68K est donné dessous. Ce programme en plus de l’affichage, affiche sur le Port A de la carte eid210 les philosophes qui mangent : /* Diner des philosophes */ #include <stdio.h> #include "mtr86.h" #include "eid210.h" #define N 5 /* nombre de philosophes */ #define P(s) s_wait(s, 0) #define V(s) s_signal(s) #define gauche ( (i+4) % N ) #define droite ( (i+1) % N ) #define PENSER 0 #define AFFAMER 1 #define MANGER 2
  • 39. Systèmes multitâches et systèmes temps réel -39- Systèmes multitâches et systèmes temps réel char *p[3] = {"PENSE n","FIN n", "MANGE *n"}; unsigned char status; // états des bits sur le portA du eid210 unsigned etat[N]; RESSOURCE mutex; SEMAPHORE s[N]; void etat_philo( void ) { int j, i=0; gotoxy(1,16); r_wait(mutex); /* Section critique */ for ( j = 0; j < N; j++ ) { gotoxy(45, 10 + j); cputs(p[etat[j]]); } EcritPortA(status); /* met à jour les bits */ r_signal(mutex); /* Liberer section critique */ } void test( int i ) { if (etat[i]== AFFAMER && etat[gauche] != MANGER && etat[droite] != MANGER) { etat[i] = MANGER; V(s[i]); } } void pense( void ) { dort((rand() & 0x7F) + 10); } void mange( void ) { dort((rand() & 0x7F) + 10); } void prend_fourchette( int i ) { r_wait(mutex); /* Section critique */ etat[i] = AFFAMER; /* PENSER -> AFFAME */ test(i); /* Voir si fourchette de gauche et de droite sont libre*/ r_signal(mutex); /* Liberer section critique */ P(s[i]); /* Bloquer le philosophe i si test n'a pas incrémenté le sém. */ } void pose_fourchette( int i ) { r_wait(mutex); /* Section critique */ etat[i] = PENSER; test( gauche ); test( droite ); r_signal(mutex); /* Liberer section critique */ } TACHE philosophes(void) { int i = tache_c()-2; while (1) { pense(); prend_fourchette(i); status = (1 << i) | status; // met le bit du philosophe i à 1 mange(); pose_fourchette (i); // met le bit du philosophe i à 0 status = ~(1 << i) & status; } }
  • 40. Systèmes multitâches et systèmes temps réel -40- Systèmes multitâches et systèmes temps réel TACHE init() { int j; gotoxy(30,3); printf("Diner des philosophesn"); mutex = r_cree(); for (j=0; j<N; j++) s[j] = s_cree(0); for (j=0; j<N; j++) active(cree(philosophes,2,1024)); gotoxy(1,10); printf( " philosophe 1 = nr" " philosophe 2 = nr" " philosophe 3 = nr" " philosophe 4 = nr" " philosophe 5 = n"); gotoxy(26,22); printf("Fin du repas par reset!!!n"); for ( ;; ) { etat_philo(); dort(cvrtic(500L)); } } main() { int j; clrscr(); InitSimulateur(); gotoxy(10,1); printf("RESOLUTION DU PROBLEME DU DINER DES PHILOSOPHES [Dijkstra 65]n"); start_mtr(init, 1024); for (j=0; j< 5; j++ ) s_close( s[j] ); }