Vous êtes sur la page 1sur 19

CHAPITRE 6 

: LES AUTRES OBJETS DU NOYAU.

En plus des taches, sémaphores et FAM, les noyaux fournissent d’autres objets importants. Tous les
noyaux ne fournissent pas ces objets supplémentaires systématiquement. Ces objets sont présentés à la
suite.
1. Les Tuyaux ou Pipes.
Ils fournissent des moyens d’échanges de données non structurées entre les taches, et facilitent
la synchronisation entre les taches. Ils sont unidirectionnels comme présenté ci-dessous.

Deux descripteurs, un pour chaque extrémité du tuyau (un pour la lecture et un autre pour
l’écriture), sont retournés lors de la création du pipe. Les données sont écrites via un
descripteur et lues via un autre. Les données sont conservées dans le pipe comme un flux de
données non structurées. Les données sont lues selon l’ordre FIFO.
Un pipe fournit permet le transfert de données de manière simple, de telle sorte que le lecteur
devient bloqué lorsque le pipe est vide et l’émetteur devient bloqué lorsque le pipe est plein.
Typiquement, le pipe est utilisé lors des échanges entre une tache dite producteur et une autre
tache dite consommateur comme indiqué ci-dessous.

Il est possible d’avoir plusieurs émetteurs et plusieurs lecteurs sur le même pipe.
Notez que le pipe est conceptuellement identique à la FAM, mais avec certaines différences.
Par exemple, contrairement à la FAM, un pipe ne conserve pas plusieurs messages. Au lieu de
cela, les données sont stockées sous forme non structurées, il s’agit d’un flux d’octets. De
plus, les données dans le pipe ne peuvent pas avoir de priorité. Le flux de données est de type
FIFO. Enfin, les pipes possèdent des opérations puissantes de sélection, contrairement aux
FAM . Ces opérations sont présentées à la suite.

1
1.1 Les blocs de contrôle du pipe.
Les pipes peuvent être dynamiquement créés ou détruits. Le noyau crée et maintient des
informations spécifiques sur le pipe dans une structure de données interne appelée bloc de
contrôle ( pipe control block ou PCB). La structure du PCB varie selon les
implémentations.
D’une manière générale, le PCB contient :

 une zone mémoire tampon (data buffer) allouée par le noyau pour les opérations
d’entrées et de sorties. La taille de ce buffer est maintenue dans le bloc de
contrôle et est fixée lors de la création du pipe et ne peut pas être changée lors de
l’exécution.
 Le compteur courant de données en octets (data byte count), ainsi que les valeurs
courantes des indicateurs de position en entrée et en sorties (input position et
output positions) font parties du PCB. Le compteur courant d’octets de
données (data byte count) indique la quantité de données que l’on encore lire
dans le pipe.

 La position d’entrée (input position) spécifie l’endroit où la prochaine opération


d’écriture aura lieu. La position de sortie (output position) spécifie l’endroit où la
prochaine opération de lecture aura lieu.
Le noyau crée deux descripteurs qui sont unique dans l’espace d’E/S du système et
retourne cers descripteurs à la tâche qui les a créés.
Deux listes d’attente des taches (task-waiting lists) sont associées pour chaque pipe. Une
première enregistre les taches en attente d’écriture dans le pipe plein. Une seconde
accumulent les taches en attente de lecture dans le pipe vide.

1.2 Les états du pipe.


Le pipe possède un nombre d’états limités. Chaque état correspond à un état de transfert de
données entre l’émetteur (writer) et le récepteur (reader). Les états sont présentés ci-
dessous.

2
1.3 Les pipes nommés et non nommés.
Le noyau supporte typiquement deux types d’objets pipe :

 Les pipes nommées (named pipes) : également appelés FIFO, ils ont un nom
identique à un nom de fichier et apparaissent dans le système de fichiers comme si
ils étaient un fichier ou un dispositif. N’importe quelle tache ou ISR qui a besoin
d’utiliser ce type de pipe peut le faire en utilisant son nom.

 Les pipes non nommés (unnamed pipes) : ils n’ont pas de nom, n’apparaissent
pas dans le système de fichiers. Ils doivent être référencés par les descripteurs que
le noyau retourne lors de la création du pipe. Cela sera expliqué en détail dans la
suite de ce cours.

1.4 Les opérations typiques sur les pipes.


Les opérations typiques sont :

 La création et la destruction du pipe.


On dispose des opérations suivantes.

Opération Description
Pipe Crée un pipe.
Open Ouvre un pipe
Close Supprime ou ferme un pipe

L’opération Pipe produit un pipe non nommé. Cette opération retourne deux
descripteurs pour la tache appelante, ainsi que pour les appels des taches
suivantes. Un descripteur est utilisé uniquement pour l’écriture, un autre
uniquement pour la lecture.
Pour créer un pipe nommé, on procède comme pour la création de fichier. Ces
appels sont dépendants de l’implémentation. Certains noms utilisés lors des
appels, par exemple, mkmod ou mkfifo. Comme le un pipe nommé a un nom
reconnaissable dans le système de fichiers après sa création, le pipe peut être
ouvert en utilisant l’opération Open. La tâche appelante doit spécifier si

3
l’ouverture du pipe est faite pour une opération de lecture, ou une opération
d’écriture. Il n’est pas possible pour la tache appelante de spécifier une opération
d’ouverture en même temps qu’une opération de fermeture.
L’opération Close est la contrepartie de l’opération Open. De même, l’opération
Close ne peut pas être réalisée sur un pipe nommé. Certaines implémentations
vont supprimer définitivement le pipe nommé une fois l’opération Close terminé.

 La lecture et l’écriture dans le pipe.


On dispose des opérations suivantes.

Opération Description
Read Lecture depuis un pipe.
Write Ecriture dans un pipe

L’opération de lecture (read) renvoie la donnée depuis le pipe vers la tache


appelante. La tache spécifie le nombre de données à lire. La tache peut choisir de
passer en état bloqué en attendant que les données restantes arrivent si la taille
spécifiée est supérieure à celle du pipe.
Notez que l’opération de lecture sur un pipe est une opération destructrice parce
que les données sont retirées du pipe durant cette opération, les rendant
indisponibles pour d’autres lecteurs. Contrairement à une FAM, un pipe ne peut
être utilisé pour diffuser des données vers de multiples lecteurs (taches). En
revanche, une tache peut consommer un bloc de données provenant de multiples
taches émettrices (writers) durant une seule opération de lecture.
L’opération d’écriture (write) accole la nouvelle donnée au flux d’octets présent
dans le pipe. La tache appelante spécifie la quantité de données à écrire dans le
pipe. La tache peut choisir de passer en état bloqué en attendant que de l’espace
mémoire soit disponible quand la quantité de données à écrire dépasse l’espace
disponible dans le pipe.
Aucun message indiquant des limites existe dans un pipe car les données présente
dans le pipe ne sont pas structurées. Ce problème caractérise la principale
différence structurelle entre un pipe et une FAM. Comme il n’y a pas d’en tête
pour le message, il est impossible de déterminer le producteur qui est à
l’origine du flux d’octets.
Une autre différence entre un pipe et une FAM réside dans le fait que les données
écrites dans le pipe ne peuvent pas être priorisées. Comme tous les octets dans un
pipe ont la même priorité, un pipe ne doit pas être utilisé lorsque des données
urgentes doivent être échangées entre les taches.

 Des opérations de contrôle sur le pipe.


On dispose de l’opération suivante.

Opération Description
Fcntl Fourniture un contrôle sur le descripteur du pipe.

4
L’opération Fcntl fournit un contrôle générique sur le descripteur du pipe en
utilisant plusieurs commandes, qui contrôle le comportement des opérations sur le
pipe.
Par exemple, une commande communément implémentée est la commande non
bloquante (non-blocking command). La commande contrôle si la tache appelante
est bloquée lors d’une opération de lecture sur un pipe vide ou une opération
d’écriture sur un pipe plein.
Une autre commande courante est la commande flush. Elle retire toutes les
données du pipe et efface toutes les autres conditions dans le pipe pour le
replacer dans un état identique à son état initial lors de sa création. Parfois
une tache peut être préemptée trop longtemps, et lorsqu’elle est finalement prête à
lire des données du pipe, les données peuvent être devenues inutiles. Dans ce cas,
l’opération flush devient utile pour vider le pipe et de le ramener à son état initial.

 La sélection de pipes.
On dispose de l’opération suivante.

Opération Description
Select Attendre que des conditions apparaissent dans le pipe.

Cette opération Select permet à une tache de se bloquer et d’attendre qu’une


condition spécifiée apparaisse dans un ou plusieurs pipes.
La condition d’attente peut être l’attente que des données deviennent disponibles
ou l’attente que des données soient vidées dans un ou plusieurs pipes.
La figure suivante illustre le scénario dans lequel une unique tâche est en attente
de lecture de données issues de deux pipes et d’écriture dans un troisième pipe.

Dans ce cas, l’appel Select retourne le fait que des données deviennent
disponibles dans un de deux pipes du haut. La même opération l’appel Select
retourne aussi le fait que de l’espace devient disponible le pipe du bas.

5
En général, une tache qui fait une lecture sur plusieurs pipes peut réaliser une
opération Select sur ces pipes, et l’opération Select indique quand un des pipes a
des données disponibles.
De manière identique, une tache écrivant dans plusieurs pipes peut réaliser
l’opération Select sur des pipes, et l’opération Select indique quand de l’espace
devient disponible dans l’un d’entre eux.
Contrairement aux pipes, les FAM ne supportent pas l’opération Select. Donc,
lorsqu’une tache a accès à de multiples FAM (un groupe de FAM), elle ne peut
pas se bloquer en attente que des données arrivent sur n’importe quelle FAM du
groupe des FAM . La même restriction s’applique en écriture. Dans ce cas, une
tache peut écrire dans plusieurs FAM, mais elle ne peut pas se bloquer en attente
sur un groupe de FAM pleines, en attendant que de l’espace deviennent disponible
dans l’une d’entre elles.
Il devient évident que l’avantage principal de l’utilisation des pipes plutôt que des
FAM dans la communication entre taches est la possibilité d’employer l’opération
Select.

1.5. Utilisations typiques des pipes.


Comme le pipe est un simple canal de données, il est principalement utilisé pour
des transferts de données entre deux taches ou entre ISR vers une tache, comme
indiqué ci-dessous.

Une autre utilisation est la synchronisation entre taches. Cette synchronisation inter
taches peut être réalisée de manière asynchrone pour deux taches en utilisant
l’opération Select.
Dans la figure suivante, la tache A et la tache B ouvrent chacune un pipe pour
réaliser une communication inter tache. Le premier pipe est ouvert pour transmettre
des données de la tache A vers la tache B. Le second est ouvert pour les
acquittements (acknowledgement) ou un autre transfert de la tache B vers la tache
A. Les deux taches utilisent l’opération Select sur les pipes.

6
La tache A attend de manière asynchrone de la place dans le pipe pour pouvoir y
écrire données (la tache B a alors lu des données depuis le pipe). La tache A peut
faire un appel non bloquant en écriture dans le pipe et ainsi réaliser d’autres
opérations tant que le pipe ne dispose pas d’un place libre.
La tache A peut aussi attendre de manière asynchrone, sur le second pipe, l’arrivée
de l’acquittement du transfert en provenance de la tache B.
De la même manière, la tache B peut attendre de manière asynchrone, sur le
premier pipe, l’arrivée des données en provenance de la tache A et attendre que
l’autre pipe devienne disponible pour pouvoir renvoyer un acquittement.

2. Les registres d’évènements (Event registers).


Certains noyaux fournissent des registres spéciaux qui sont intégrés dans les TCB. Ces
registres, appelés registres d’évènements (event register), sont des objets appartenant
à une tache et consistent en un groupe de drapeaux binaires (flags) utilisés pour
indiquer l’occurrence d’évènements spécifiques.

En fonction des noyaux, l’implémentation de ces registres diffère, pouvant avoir une
taille de 8, 16 ou 32 bits, parfois même plus. Chaque bit de registre d’évènements sont
traités comme des drapeaux binaires (event flag) qui peuvent être mis à 1 ou à 0.

7
A travers ce registre d’événements, une tache peut vérifier la présence d’évènements
particuliers qui peuvent déterminer la suite de son exécution. Une source externe,
comme une autre tache ou ISR, peut mettre à 1 des bits du registre d’événements pour
informer la tache de l’occurrence d’un certain événement.
Les applications définissent l’événement associé à un drapeau (event flag). Cette
définition doit être en accord avec l’émetteur de l’événement et le récepteur utilisant le
registre d’événement.

2.1 Les blocs de contrôle du registre d’événement ( Event Register Control Blocs).
Typiquement lorsque le noyau supporte le registre d’événement, le noyau crée un bloc de
contrôle du registre d’événement comme étant une partie du TCB lors de la création de
la tâche, comme indiqué ci-dessous.

La tache spécifie le groupe d’événements qu’elle souhaite recevoir. Cet ensemble


d’événements est maintenu dans le registre d’évènements souhaités (wanted event
register). De manière identique, les événements apparus sont conservés dans le registre
d’événements reçus (received event register). La tache indique une durée (timeout) pour
préciser combien de temps elle souhaite attendre l’arrivée de certains événements. Le
noyau réveille la tache lorsque le timeout est expiré si aucun événement n’est arrivé à la
tache entre temps.
En utilisant les conditions de notifications (notification conditions), la tache indique au
noyau quand elle souhaite être notifiée (awaked) lors des arrivées d’événements. Par
exemple, la rache peut spécifier les conditions de notifications comme « envoi d’une
notification lorsque à la fois les événements de type1 et type 3 arrivent ou bien lorsque
l’événement de type 2 arrive ». Cette option fournit le la souplesse pour définir des
conditions complexes si besoin.

2.2 Les opérations typiques sur les registres d’événements.


On dispose des opérations suivantes pour agir sur le registre d’événement.

Opération Description
Send Envoi des événements à une tache.
Receive Reçoit des événements

8
L’opération de réception (receive) permet à la tache appelante de recevoir des événements
en provenance de sources externes. La tache peut spécifier si elle souhaite attendre, et
dans ce cas combien de temps elle attendra l’arrivée des évènements souhaités avant
d’abandonner. La tache peut attendre indéfiniment ou pendant une durée limitée.
Spécifier un ensemble d’événements lors de l’opération de réception (receive) permet à
une tache de passer en mode bloqué (block-wait) en attente d’arrivée d’événements
multiples, bien que ces événements puissent ne pas tous arriver simultanément. Le noyau
traduit ce groupe d’événements en des conditions de notifications.
L’opération Receive renvoi un résultat soit lorsque la notification est satisfaite, soit
lorsque le timout est expiré. Tout événement reçu qui n’est pas indiqué dans l’opération
Receive est laissé (pending) dans le registre d’événement. L’opération Receive renvoi
immédiatement un résultat si l’événement désiré était déjà présent dans le registre
(pending).
L’ensemble des événements est construit en utilisant les opérations logiques AND/OR.
Avec l’opération AND, la tache reprend son exécution seulement après que tous les bits
sont à 1. Une tache peut aussi passer en mode bloqué en attente de l’arrivée d’un unique
événement appartenant à un ensemble d’événements, en utilisant l’opération OR. Dans ce
cas, la tache reprend son exécution lorsqu’un n’importe quel bit associé aux événements
attendus passe à 1.
L’opération send permet à une source externe, soit une tache soit une ISR, d’envoyer des
événements à une autre tâche. L’émetteur peut envoyer des événements multiples à une
tacher précise à l’aide d’une seule opération send. Les événements qui ont été envoyés et
qui restent dans le registre (sous forme de flags) mais qui n’ont pas été choisi pour la
réception par une tache restent (pending) dans le registre d’événements.
Les événements dans le registre d’événements ne sont pas empilés. Un registre
d’événements ne peut pas compter les occurrences du même événement alors qu’il est déjà
présent (pending) dans le registre. Les futures occurrences sont perdues.
Par exemple, si une ISR envoi un événement à une tache et que l’événement reste présent
(pending) ; un peu plus tard une autre tâche envoi le même événement à la même tache
alors que l’événement précédent est toujours présent (pending), la première occurrence de
l’événement sera perdue.

2.3 Utilisations typiques des registres d’événements.


Ils sont utilisés pour la synchronisation d’activité unidirectionnelle. Unidirectionnelle car
l’émetteur de l’opération receive détermine le moment ou la synchronisation doit avoir
lieu. Les évènements présent (pending) dans le registre d’événement ne change pas l’état
d’exécution de la tache réceptrice.
Dans la figure suivante, au moment où la tache 1 envoie l’évènement X à la tache 2, rien
ne se passe concernant l’exécution de la tache 2 si la tache 2 n’a pas encore tenté de
recevoir l’événement.

9
Aucune donnée n’est associée avec un événement lorsque les événements sont envoyés
vers le registre d’événements. D’autres mécanismes doivent être utilisés lorsque des
données doivent être envoyés avec des événements. Ce manque de possibilité d’envoyer
des données avec des évènements peut parfois créer des difficultés à cause de la nature
non cumulative des événements du registre d’événements. Le registre d’événements par
nature est un mécanisme inefficace s’il est utilisé au-delà de la simple synchronisation.

Une autre difficulté liée à l’utilisation des registres d’événements est qu’il ne possède pas
un mécanisme natif pour identifier la source d’un événement dans le cas où de multiples
sources sont possibles. Une façon de résoudre ce problème est que la tache divise
l’ensemble des bits du registre d’événements en sous ensemble de bits. Une tache peut
associer chaque sous ensemble avec une source connue. De cette manière, la tache peut
identifier la source de l’événement si chaque position relative des bits pour chaque sous
ensemble est associé au même type d’événement, comme indiqué ci-dessous.

La figure précédente est divisée en 4 groupes de bits. Chaque groupe est assigné à une
source, indépendamment du fait que ce soit une tache où une ISR. Chaque bit du
groupe est associé à un type d’événement.

3. Les signaux.
Un signal est une interruption software qui est généré lorsqu’un événement apparait. Elle
détourne le récepteur du signal de son exécution, normale et déclenche un traitement
asynchrone associé.

10
Essentiellement, les signaux notifient les taches d’événements apparaissant durant
l’exécution d’autres taches ou ISR. Lors d’une interruption normale, ces événements sont
notifiés aux taches de manière asynchrone et n’apparaissent pas à un point prédéterminé
de l’exécution de la tâche.
La différence entre un signal et une interruption normale est que le signal est une
interruption d’origine logicielle, elle est générée via l’exécution de parties du programme
dans le système. En revanche, l’interruption normale est généralement générée par
l’arrivée d’un signal d’interruption sur une des broches externes de la CPU. Elles ne sont
pas générées par le software dans le système mais par des dispositifs externes. Un
prochain chapitre présentera les interruptions en détail.
La figure suivante présente les signaux.

Le nombre et types des signaux définis dépend du système et du RTOS.


Une manière simple de comprendre les signaux est de se souvenir que chaque signal est
associé à un événement. L’événement peut être soit intentionnel, comme l’exécution d’une
instruction illégale, ou intentionnel, comme la notification d’une tache en train de se
terminer vers une autre. Alors qu’une tache peut spécifier une action particulière à réaliser
lors de l’arrivée d’un signal, les tâches n’ont aucun contrôle sur le moment d’arrivée du
signal. En conséquence, les arrivés des signaux sont aléatoires.
Lorsqu’un signal arrive, la tache est détournée de son chemin d’exécution normal, et une
routine correspondante est appelée. Les termes routine, signal routine, signal handler,
asynchronous event handler, et asynchronous signal routine sont interchangeables. Ce
document utilise le terme asynchronous signal routine (ASR). Chaque signal est identifié
par une valeur entière appelé vecteur d’interruption (signal number, vector number).

3.1. Le bloc de contrôle des signaux (signal control block ou SCB).


Lorsque le noyau le permet, il crée un bloc de contrôle du signal dans le TCB, comme
indiqué ci- dessous.

11
Le SCB contient un ensemble de signaux (les signaux désirés ou wanted signals) pour
lesquels les taches sont préparées à réaliser une action (handle) ; on parle de taches prêtes à
attraper le signal (ready to catch signal). Lorsqu’un signal interrompt une tache on dit qu’il a
été levé dans la tache (raised). La tache peut fournir une routine d’interruption (handler) pour
chaque signal qui doit être traité ou elle peut exécuter une routine par défaut (fournie par le
noyau). Il est possible d’avoir une seule routine pour des types de signaux différents.
Les signaux peuvent être :

 ignorés,
 en attente (pending),
 en traitement (handled),
 ou bloqués.
Les signaux qui sont ignorés par la tache sont maintenus dans un groupe de signaux ignorés
(ignored state).
D’autres signaux peuvent arriver alors que la tâche est au milieu d’un traitement associé à un
autre signal. Dans ce cas, les nouveaux signaux sont maintenus dans un groupe de signaux en
attente (pending state). Les signaux de cet ensemble seront levés par la tache dès que la tache
aura terminé le traitement en cours du signal précédent. Les signaux dans le groupe pending
est un sous ensemble des signaux du groupe wanted.
Pour traiter un signal particulier, la tâche, pour effectuer le traitement, peut soit utiliser une
routine d’interruption, soit utiliser une routine par défaut fournie par le noyau. Il est aussi
possible que la tache utilise dans un premier temps sa propre routine puis passe la suite du
traitement à la routine par défaut, celle fournie par le noyau).
Un quatrième type de réponse au signal est possible. Dans ce cas, la tâche n’ignore pas le
signal mais bloque (block) la délivrance du signal durant certaines étapes de l’exécution de la
tâche, lorsque lorsqu’il est critique que la tâche ne soit pas interrompue.
Bloquer un signal est similaire au concept d’entrer dans une section critique, cela sera
expliqué dans un autre chapitre. La tâche peut demander au noyau de bloquer certains signaux
en les plaçant dans le groupe des signaux bloqués (blocked signal set). Le noyau ne délivrera
aucun signal de cet ensemble jusqu’à ce que le signal soit supprimé de cet ensemble.

12
3.2. Opérations typique sur les signaux.
On dispose des opérations suivantes.

Opération Description
Catch Installe une routine pour un signal (signal handler)
Release Supprime une routine (signal handler) précédemment installée.
Send Envoie un signal à une autre tâche.
Ignore Empêche un signal d’être délivré
Block Bloque un ensemble de signaux d’être délivrés.
Unblock Débloque des signaux pour qu’ils puissent être délivrés

Une tache peut attraper après que la tache ait spécifié une routine (ASR) pour ce signal.
L’opération Catch installe une routine (handler) pour un signal particulier. Le noyau
interrompt l’exécution de la tache lors de l’arrivée du signal, et la routine (handler) est
invoquée. Une tache peut installer une routine par défaut fournie par le noyau (default
actions ou default handler), pour n’importe quel signal. La routine installée dans la tache a
l’option de soit de traiter le signal et de retourner le contrôle au noyau, soit de traiter le signal
et de passer le contrôle au default handler pour un traitement supplémentaire. L’utilisation des
signaux est identique à celle des interruptions hardware, et la nature de l’ASR est similaire à
celle d’une routine d’interruption.
Après qu’une routine (handler) ait été installée, pour un signal particulier, la routine est
invoquée si le même type de signal est reçu par n’importe quelle tache, et pas seulement par
celle qui l’a installé. De plus, n’importe quelle tache peut changer la routine précédemment
installée par une tache pour un signal particulier. C’est donc une bonne pratique pour une
tache de sauver sa routine1 précédemment installée avant l’installation d’une nouvelle
routine2, puis de la (routine1) restaurer après le traitement de la routine2.
La figure suivante montre la table des vecteurs des signaux (vector table), qui est maintenue
par le noyau.

Chaque élément de cette table des vecteurs est un pointeur ou un offset vers une ASR. Pour les
signaux qui n’ont pas de handler dédiée, l’élément correspondant dans la table des vecteurs
est NULL. L’exemple montre l’état d’une table après que trois opérations Catch aient été
réalisées. Ces opérations Catch signifient que trois installations d’ASR, en écrivant un
pointeur ou un offset vers une ASR dans les éléments de la table des vecteurs.
13
L’opération release désinstalle un handler de signal. Une bonne pratique consiste pour une
tache en la restauration l’handler précédemment installée après une opération release.
L’opération send permet à une tache d’envoyer un signal vers une autre tâche. Les signaux
sont habituellement associés à des événements hardwares qui apparaissent durant l’exécution
de la tâche, ce qui provoque une exception liée un défaut d’alignement mémoire ou en virgule
flottante. De tels signaux sont automatiquement générés quand les événements correspondants
apparaissent. L’opération send, par contraste, permet à une tache de générer explicitement un
signal.
L’opération ignore permet à une tache d’indiquer au noyau qu’un ensemble particulier de
signaux ne doivent jamais parvenir à certaines tâches. Certains signaux, en revanche, ne
peuvent pas être ignorés ; lorsque ces signaux sont générés, le noyau appelle la routine par
défaut (default handler).
L’opération block n’implique pas que les signaux seront ignorés, mais empêche
temporairement leur délivrance à la tâche. L’opération block protège les sections critiques du
code des interruptions. Une autre raison de bloquer un signal est d’empêcher un conflit quand
une handler associé à un signal est déjà en exécution et est au milieu du traitement du même
signal. Le signal reste en attente (pending) tant qu’il est bloqué.
L’opération unblock permet à un signal précédemment bloqué d’être transmis. Le signal est
immédiatement délivré s’il est déjà en attente (pending).

3.3. Utilisations typiques des signaux.


Certains signaux sont associés à des évènements hardware et sont donc usuellement envoyé
par des ISR hardware. L’ISR est responsable de la réponse immédiate correspondante à ces
évènements. L’ISR peut aussi envoyer un signal qui demandera aux taches concernées par ces
évènements hardware de réaliser un traitement spécifique à la tâche.
Comme décrit à la figure suivante, les signaux peuvent aussi être utilisés pour la
synchronisation entre les taches.

14
Mais les signaux doivent être utilisés avec prudence pour les raisons suivantes :

 L’utilisation des signaux peut être couteuse en raison de la complexité d’installation


du signal dans la communication inter tâche. Un signal altère l’état d’exécution de la
tache destinée. En raison de l’apparition asynchrone des signaux, la tache réceptrice
devient non déterministe, ce qui peut être non désiré dans un système temps réel.

 De nombreuses implémentations ne supportent pas la mise en file d’attente des


signaux ou le comptage des occurrences des signaux. Dans ce cas, les occurrences du
même signal provoquent une réécriture du précédent (écrasement). Par exemple, un
signal délivré plusieurs fois à une tache avant que son handler soit invoqué a le même
effet que l’apparition unique d’un signal. La tâche n’a aucun moyen de vérifier si le
signal est arrivé plusieurs fois.

 De nombreuses implémentations ne supportent pas la délivrance de signaux qui


transportent des informations, donc les données ne peuvent pas être rattachées au
signal durant sa génération.

 De nombreuses implémentations ne supportent pas un ordre de délivrance des


signaux, et les signaux de type variés sont traités comme ayant la même priorité, ce
qui n’est pas idéal. Certains signaux peuvent avoir des importances différentes, mais
ces implémentations ne le prennent pas en compte !

 De nombreuses implémentations ne garantissent pas le moment où un signal en attente


(pending) qui devient débloqué sera délivré à la tache destinataire.

Certains noyaux implémentent des extensions temps réel pour la prise en compte des signaux,
comme :

 Un ordre de priorité de délivrance du signal basé sur le numéro du signal.


 La possibilité de transporter une information avec le signal.
 La mise en file d’attente des occurrences multiples du même signal.

4. Les variables conditionnelles.

Les taches utilisent souvent des ressources partagées, comme des fichiers et des canaux de
communication. Lorsqu'une tache a besoin d’une telle ressource, elle peut avoir besoin que la
ressource soit dans un état particulier. La façon dont la ressource atteint cet état peut être
réalisé par l’action d’une autre tâche. Dans un tel scénario, une tache a besoin d’un moyen de
déterminer la condition de la ressource. Une manière pour les taches de communiquer et
déterminer la condition de la ressource partagée est d’utiliser une variable conditionnelle
(condition variable). Il s’agit d’un objet du noyau associé à une ressource partagée, qui permet
à une tache d’attendre qu’une ou plusieurs taches créent la condition de la ressource partagée.
Une variable conditionnelle (condition variable) peut être associée à des contions multiples.

15
La figure page suivante montre qu’une variable conditionnelle (condition variable)
implémente un prédicat.

Le prédicat est un ensemble d’expressions logiques concernant les conditions d’une ressource
partagée. Le prédicat, lorsqu’il est évalué, peut donner un résultat vrai ou faux. Une tache
évalue un prédicat. Si l’évaluation du prédicat retourne la valeur vrai, la tache assume que la
condition est satisfaite, et continue son exécution. Dans le cas contraire, la tache doit attendre
que les autres taches créent les conditions désirées.
Lorsqu’une tache examine une variable conditionnelle, la tache doit avoir un accès exclusif à
cette variable. Sans accès exclusif, une autre variable peut modifier la variable conditionnelle
au même moment, ce qui peut induire une indication erronée de l’état de la variable lors de la
lecture faite par la première tâche. En conséquence, un mutex assure qu’une tâche a un accès
exclusif à la variable conditionnelle jusqu’à ce que la tâche en ai terminé avec elle. Par
exemple, si une tache fait l’acquisition du mutex pour examiner une variable conditionnelle,
aucune autre tâche ne peut simultanément modifier la variable conditionnelle de la ressource
partagée.
Une tache doit d’abord acquérir le mutex avant d’évaluer le prédicat. Cette tache doit ensuite
relâcher le mutex, et si le prédicat est faux attend la création de la condition désirée. En
utilisant une variable conditionnelle, le noyau garantit que la tache peut relâcher le mutex et
ensuite être bloquée en attente de la condition de manière atomique. Une opération atomique
(atomic) est une opération qui ne peut pas être interrompue.
Retenez que les variables conditionnelles ne sont pas des mécanismes destinés à synchroniser
des accès à des ressources partagées. Les développeurs les utilisent plutôt pour permettre aux
taches en attente d’accès à une ressource partagée d’atteindre une valeur désirée ou un état.

16
4.1. Les blocs de contrôle de variable conditionnelle (Condition Variable Control Blocks
ou CVCB).
Le noyau maintient un ensemble d’informations associé avec la variable conditionnelle (VC)
lorsque la tâche est créée. Comme indiqué précédemment, les taches doivent se bloquer et
attendre quand le prédicat d’une VC est faux. Ces taches en attente sont maintenues dans une
liste des taches en attente (task-waiting list). Le noyau garantit pour chaque tache que
l’opération combinée de relâchement du mutex associé et le blocage avec mise en attente se
fera de manière atomique.
Après que les conditions désirées aient été créés, une des taches est réveillée et reprend son
exécution. Le critère pour sélectionner la tâche qui sera réveillée peut être basé sur la priorité
ou basé sur le mode FIFO, mais il est défini par le noyau. Le noyau garantit que la tache
sélectionnée est retiré de la liste des taches en attente (task-waiting list), re acquière le
mutex, and reprend son opération de manière atomique.
L’essence d’une VC est l’atomicité des opérations de déblocage et attente (unlock-and-wait)
et réactivation et blocage (resume-and-lock) fournies par le noyau. La figure suivante illustre
le CVBC.

Les taches qui coopèrent définissent les conditions à appliquer pour chaque ressource
partagée. Cette information n’est pas une partie de la VC car chaque tache possède un prédicat
différent ou une condition que la tache observe. La condition est spécifique à la tâche. Un
chapitre du cours présente des exemples d’utilisation des VC.

4.2. Opérations typiques sur variables conditionnelles.


On dispose des opérations suivantes.

Opération Description
Create Créé et initialise une VC
Wait Attend une valeur de VC
Signal Signale la VC en présence d’une condition
Broadcast Signale toutes les taches en attente de la présence d’une
condition.

L’opération Create crée une VC et initialise son CVBC.

17
L’opération Wait permet à une tache d’être bloquée et d’attendre l’arrivée d’une condition
désirée pour une ressource partagée. Pour invoquer cette opération, la tache doit d’abord
acquérir le mutex. L’opération Wait place la tache appelante dans la task-waiting-queue et
libère le mutex associé en une opération atomique.
L’opération signal permet à une tache de modifier la VC pour indiquer qu’une condition
particulière a été créée dans la ressource partagée. Pour invoquer cette opération, la tache doit
d’abord acquérir le mutex. L’opération signal débloque une des taches en attente de la VC. La
sélection de la tâche est basée sur un critère prédéfini, comme la priorité d’exécution ou un
système d’ordonnancement. A la fin de l’opération signal, le noyau récupère le mutex associé
à la VC pour le compte de la tache sélectionnée et débloque la tâche en une opération
atomique.
L’opération Broadcast réveille chaque tache de la task-waiting-list de la VC. Une de ces
taches est choisie par le noyau et récupère le mutex. Chacune des autres taches est retirée de la
task-waiting-list de la VC, et ces taches sont placées dans la de la task-waiting-list du mutex.

4.3. Utilisations typiques des variables conditionnelles.


Le listing ci-dessous illustre l’usage des opérations wait et signal.

On s’aidera pour la compréhension de ce code de la figure de la page suivante.


La tache 1 du côté gauche verrouille le mutex dans un premier temps. Elle examine alors l’état
de la ressource partagée et trouve alors la ressource dans l’état occupé (busy). Elle émet une
opération wait pour que la ressource devienne disponible ou libérée. La condition de libération
doit être crée par la tache 2 du côté droit après en avoir terminé avec la ressource partagée.
Pour créer la condition de libération, la tache 2 verrouille le mutex ; elle crée la condition en
marquant la ressource comme étant libre, et finalement, invoque l’opération signal, ce qui
informe la tache 1 que la condition libre est maintenant présente.

18
Un signal sur une VC est perdu lorsque personne n’est en attente sur elle. Donc, une tache doit
toujours vérifier la présence de la VC désirée avant de l’attendre. Une tache doit aussi toujours
vérifier la présence de la VC désirée après un réveil comme garde-fou contre des signaux
improprement générés sur une VC. Ce problème est la raison pour laquelle le pseudo code ci-
dessus inclut une boucle while pour vérifier la présence de la condition désirée

19

Vous aimerez peut-être aussi