mise en oeuvre: signaux, gestion du temps et multi-activités

64
ENSP Strasbourg (Edition 2009-2010) Les Systèmes Temps Réels - Ch. DOIGNON 77 Chapitre 3 Mise en œuvre : signaux, gestion du temps et multi-activités

Upload: danghuong

Post on 05-Jan-2017

213 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

77

Chapitre 3

Mise en œuvre : signaux, gestion du temps et multi-activités

Page 2: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

78

GENERALITES SUR LES SIGNAUX

Un signal est une information de contrôle (requête) transmise à un processus par unautre processus ou par le matériel durant l'exécution.Dans la gestion classique des signaux non temps réel, la nature de cette informationest très simple : une valeur entière positive constitue le signal. Par contre, lemécanisme de transmission est très sophistiqué. En effet, un signal peut êtredynamiquement associé à une fonction d'un programme (que l'on appellegénéralement, le gestionnaire du signal ou fonction associée ou encore fonction dedéroutement). Dans ce cas, lorsque le signal est pris en compte par le processusdestinataire, l'exécution du traitement en cours est alors suspendue et le contrôleest donné à la fonction de déroutement. Si la fonction effectue un retour, lecontrôle est redonnée au traitement qui avait été interrompu par la réception dusignal.

Pour les signaux temps réels, des données supplémentaires sont transmises d’unprocessus expéditeur à un processus destinataires. De plus, toutes les occurrencesdes signaux seront prises en compte.

Nous étudierons la gestion classique puis la gestion des signaux temps réel.

Page 3: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

79

GENERALITES SUR LES SIGNAUX

Selon la manière dont le signal est émis, on distingue l'interruption matérielle(signal émis par le matériel – un périphérique interne ou externe) de l'interruptionlogicielle (signal émis par un autre processus).Un signal peut être ignoré. Par défaut, s'il n'est ni ignoré, ni associé explicitement àune fonction, la réception du signal provoque la terminaison du processusdestinataire (exécution de l'appel système exit()). L'envoi d'un signal peut parexemple être provoqué par l'utilisateur au moyen de caractères spéciaux tapésdepuis le clavier du terminal. Il est important de distinguer, dans un environnementmultitâches, trois phases dans la production et le traitement d'un signal :

• l'envoi d'un signal (production d'un signal), qui est l'initialisation d'un signalassocié à un identificateur de processus destinataire,

• sa notification, qui est le positionnement dans le processus destinataire d'unidentificateur de réception,

• sa délivrance qui est la prise en compte du signal par le processus destinataire etle lancement de la fonction associée (appel d'une fonction de déroutement,terminaison anormale etc..., et la réinitialisation de l'indicateur (blocage/déblocage)au cas où un signal interviendrait pendant l'exécution de la fonction associée).

Page 4: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

80

GESTION CLASSIQUE DES SIGNAUX (sous linux)

• Production d’un signal

Les signaux sont produits par certaines conditions d'erreur, telles que des violationsd'espace mémoire, des erreurs arithmétiques émises par le processeur ou desinstructions illicites. Ils sont générés par l'interpréteur de commandes et lesgestionnaires de périphériques (terminaux, timer, clavier, carte E/S, liaison série,...).Ils peuvent aussi être explicitement envoyés depuis un processus vers un autre etconstituent alors un moyen de synchronisation pour la transmission d'informations.L'interface de programmation est à chaque fois la même. La fonction utilisée pourenvoyer un signal d'un processus vers un autre processus (de même propriétaire)est la fonction kill() :

#include <sys/type.h>

#include <signal.h>

int kill( pid_t pid , int signal );

Les noms des signaux classiques sont définis dans le fichier d'en-tête standardsignal.h. Les valeurs numériques des signaux peuvent changer d'uneimplémentation du système à un autre. La norme spécifie simplement des macro-constantes associées (#define). Elles commencent toutes génériquement par lepréfixe SIG.

Page 5: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

81

GESTION CLASSIQUE DES SIGNAUX (sous linux et systèmes Unix)

Noms des signaux (POSIX.1) Description

SIGHUP connexion interrompue (modem, ligne série)ou terminaison d'un processus

SIGINT interruption interactive (terminal)SIGQUIT terminaison interactive (terminal)SIGILL instruction illégaleSIGABRT terminaison anormale d'un processus (abort())SIGFPE erreur arithmétique : division par zéro, dépassement (processeur) ,SIGKILL signal provenant de (kill()) (non déroutable)SIGSEGV adresse mémoire incorrecte (MMU)SIGPIPE tube détruit (écriture dans le tube sans lecteur)SIGALRM alarme: expiration d'un délai (alarm(),pause()) ou du timerSIGTERM signal de terminaisonSIGUSR1/SIGIO premier signal réservé pour les applications (kill())SIGUSR2/SIGIO second signal réservé par les applications (kill())SIGCHLD processus enfant stoppé ou terminéSIGSTOP arrêt (suspension) d'un processus (non déroutable)SIGCONT reprise d'un processus (après suspension)SIGTSTP suspension d'un processus (provenant d'un terminal)SIGTTIN lecture du terminal pour un processus d'arrière-planSIGTTOU écriture vers le terminal pour un processus d'arrière-plan

Page 6: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

82

GESTION CLASSIQUE DES SIGNAUX (sous linux)

• Délivrance d’un signal

On dit qu'un signal a été délivré lorsque le processus destinataire l'a pris en compte.Lorsqu'un signal est produit, sa délivrance n'est pas immédiate. En particulier, il estpossible que le processus destinataire bloque temporairement la délivrance decertains signaux (il est possible de choisir lesquels). Lorsqu'un signal est envoyé à unprocessus qui le bloque, ce signal est dit pendant.Le mécanisme de blocage de signaux permet d'empêcher temporairement ladélivrance d'un signal, sans pour autant le perdre. Il peut s'avérer très utile,notamment lors de l'exécution d'une fonction de déroutement de signal, de bloquerla délivrance d'une nouvelle occurrence de ce signal pendant son traitement. Ilapparaît donc indispensable de pouvoir traiter de façon atomique (sans interruptionpossible) l'activation ou la désactivation d'un ensemble de signaux. Pour cela, lanorme POSIX propose un type de données appelé sigset_t pour représenter cetensemble de signaux :

#define _SIGSET_NWORDS 32

typedef struct{

unsigned long int __val[_SIGSET_NWORDS];

} __sigset_t;

typedef __sigset_t sigset_t;

Page 7: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

83

GESTION CLASSIQUE DES SIGNAUX (sous linux)

• Délivrance d’un signal (suite)

Cette structure est manipulable à partir de cinq fonctions :

#include <signal.h>

int sigemptyset( sigset_t *ens );

int sigfillset( sigset_t *ens );

int sigaddset( sigset_t *ens , int num_signal );

int sigdelset( sigset_t *ens , int num_signal );

int sigismember( sigset_t *ens , int num_signal );

Si ens est un objet de type sigset_t, l'appel de la fonction sigemptyset(&ens)

initialise l'ensemble ens de telle sorte qu'il ne contienne aucun des signaux de lanorme POSIX, et l'appel de la fonction sigfillset(&ens) de sorte qu'il lescontienne tous. L'une ou l'autre de ces fonctions doit être appelée au moins une foispour initialiser avant sa première utilisation, un objet de type sigset_t.

sigaddset(&ens,num_signal) et sigdelset(&ens,num_signal) ont desfonctions qui respectivement ajoutent et enlèvent à l'ensemble ens le signal spécifiépar le numéro du signal num_signal. Enfin, la fonctionsigismember(&ens,num_signal) teste l'appartenance d'un signal à l'ensembleens; elle retourne 1 si le signal est dans ens, 0 sinon. Les autres fonctions retournent0 en cas de succès et -1 en cas d'échec.

Page 8: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

84

GESTION CLASSIQUE DES SIGNAUX (sous linux)

• Configuration pour la délivrance

Un comportement spécifique est associé à chaque signal, indiquant la manière dontil doit être délivré. Ce comportement est représenté par une structure de donnéesstruct sigaction contenant (entre autres) les champs suivants :

struct sigaction

{

void (*_sa_handler)(int); /* signaux classiques */

sigset_t sa_mask;

unsigned long sa_flags;

};

• void (*_sa_handler)(int signal) : ce champ décrit la fonctionassociée à la délivrance du signal classique(fonction de déroutement) :SIG_IGN pour ignorer le signal, SIG_DFL pour le traitement par défaut(constantes symboliques), ou un pointeur vers une fonction de déroutementdu signal. Dans ce dernier cas, lorsque la fonction de déroutement est appelée,elle reçoit en paramètre le signal délivré.

Page 9: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

85

GESTION CLASSIQUE DES SIGNAUX (sous linux)

• Configuration pour la délivrance (suite)

• sigset_t sa_mask : ce champ (masque de signaux) permet de spécifier unensemble de signaux devant être bloqués durant l'exécution de la fonction dedéroutement du signal. La valeur de ce champ remplace temporairement lemasque de signaux du processus en cours.

• sa_flags : ce champ (drapeaux) permet de spécifier le comportement dusignal par rapport à l'environnement du processus (par exemple pour ne pasprendre en compte le signal délivré à la terminaison d'un processus enfant).

La fonction qui positionne la valeur de ces champs avec le signal spécifié s'appellesigaction().

Les signaux interceptés à l'aide de fonctions de déroutement définis parsigaction()ne sont pas par défaut réinstallés (après exécution de la fonctionassociée). Ainsi, le champ sa_flags doit être paramétré avec la valeur symboliqueSA_ONESHOT afin de retrouver le comportement par défaut (SIG_DFL) du signalavant le déroutement.

Page 10: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

86

GESTION CLASSIQUE DES SIGNAUX (sous linux)

• Remarque : lorsqu'un signal est ignoré, il est délivré et écarté. Lorsqu'un signal estbloqué, il est mis en attente, durant le temps du blocage. Dans ce cas, l'arrivéed'une nouvelle occurrence du signal peut, selon les implémentations du systèmed'exploitation, être empilée ou écartée. Ce comportement n'est pas spécifié dans lanorme POSIX.

Un processus peut consulter et/ou modifier sa signalerie au moyen des fonctionssigaction() et sigprocmask() :

int sigaction( int num_signal , struct sigaction *nouvelle_action ,

struct sigaction *ancienne_action );

La fonction sigaction() permet de consulter et/ou modifier le comportementassocié à la délivrance d'un signal spécifié. Le comportement par défaut des signauxSIGKILL et SIGSTOP ne peut être modifié. Si le résultat de l'exécution desigaction() est d'ignorer un signal pendant, ce signal est écarté. Le paramètrenouvelle_action décrit le comportement que l'on souhaite associer à ladélivrance du signal spécifié. Avant sa modification, le comportement courant estsauvegardé dans la structure référencée par ancienne_action, sauf si ce dernierest égal à à la constante symbolique NULL.

Page 11: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

87

BLOCAGE DES SIGNAUX (sous linux)

int sigprocmask( int methode , sigset_t *nouvel_ens , sigset_t

ancien_ens );

La fonction sigprocmask() permet de consulter et/ou modifier le masque designaux. Le traitement dépend de la valeur du paramètre methode, qui peut être :

• SIG_SETMASK : le masque de signaux reçoit l'ensemble spécifié par le paramètrenouvel_ens, qui est utilisé directement comme masque de blocage des signaux.

• SIG_BLOCK : on ajoute la liste des signaux contenus dans le paramètrenouvel_ens au masque de blocage des signaux. Cela revient à bloquer tous lessignaux (sauf SIGKILL et SIGSTOP) spécifiés par l'ensemble nouvel_ens sanschanger le comportement des autres signaux. Il s’agit d’une addition au masque encours.

• SIG_UNBLOCK : on retire les signaux contenus dans nouvel_ens au masque deblocage des signaux. Cela revient à débloquer tous les signaux spécifiés parnouvel_ens sans changer le comportement des autres signaux.

Si le paramètre ancien_ens est différent de NULL, il reçoit une copie de la valeurdu masque avant sa modification. Il est possible d'obtenir la valeur courante dumasque de signaux, sans le modifier, en utilisant la valeur NULL dans nouvel_ens.

Page 12: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

88

EXEMPLE (sous linux)

#include <signal.h>

#include <stdio.h>

#include <unistd.h>

void FonctionDeroutement( int sig )

{

printf("OUILLE ! - Signal reçu %d\n",sig);

return;

}

int main( void )

{

struct sigaction act;

act.sa_handler = FonctionDeroutement;

sigemptyset( &act.sa_mask );

/* act.sa_flags = SA_ONESHOT */

/* si decommenté, alors le comportement par défaut est réinstallé, pour le signal

concerné, à l’issue de l’invocation du gestionnaire du signal */

sigaction( SIGINT , &act , NULL );

while ( 1 )

{usleep(2000000L);/* on endort le processus pendant 2 s environ */

printf("Hello world !\n");}

exit(0);

}

Page 13: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

89

EXPLICATIONS DE L’EXEMPLE (sous linux)

L'exécution de ce programme entraîne l'affichage d'un message lors de l'appui surCtrl+C dans la mesure où act gère le signal SIGINT à plusieurs reprises. Pour mettrefin au programme, il faut appuyer sur Ctrl+\ générant le signal SIGQUIT par défaut,via le terminal.

ATTENTE DES SIGNAUX SUR FIN DE PROCESSUS (sous linux)

Un cas particulier est l'attente de la fin d'un processus enfant (envoie du signalSIGCHLD par le processus enfant). Il est possible de faire attendre un processusparent jusqu'à la terminaison d'un processus enfant en appelant les fonctionswait() ou waitpid() :

#include <sys/type.h>

#include <sys/wait.h>

pid_t wait( int *stat_loc );

pid_t waitpid( pid_t pid , int *stat_loc , int options );

L'argument pid permet de définir le processus fils à attendre. Les informations desortie du processus seront inscrites à l'adresse spécifiée par *stat_loc. L'optionpermet de savoir de quelle manière le processus enfant a été terminé.

Page 14: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

90

MONITORING

Un processus peut savoir s'il possède des signaux pendants (en attente dedélivrance) au moyen de la fonction sigpending() :

int sigpending( sigset_t *ens );

Cette fonction recopie le masque de notification dans le paramètre ens (ensemblede signaux) et retourne 0 en cas de succès et -1 en cas d'erreur.

ENDORMIR UN PROCESSUS : Un processus peut être endormi par un appel auxfonctions sleep() ou usleep().

unsigned int sleep (unsigned int secondes );

void usleep(unsigned int microsecondes );

Le processus ainsi endormi est réveillé soit à la fin du temps indiqué soit à l'arrivéed'un signal.

ATTENTE D’UN SIGNAL : Un processus peut se mettre en attente de la délivranced'un signal quelconque ou particulier au moyen de la fonction pause() :

int pause( void );

La fonction pause() endort le processus jusqu'à réception d'un signal provoquantla terminaison du programme ou l'appel d'une fonction de déroutement.

Page 15: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

91

PROBLEMATIQUE DE L’ ATTENTE D’UN SIGNAL : le problème qui se pose souventest d’encadrer correctement pause(), de façon à éviter de perdre des signaux.Imaginons que SIGUSR1 dispose d’un gestionnaire faisant passer à 0 une variableglobale appelée attente. On désire bloquer l’exécution du programme jusqu’à ceque cette variable ait changé. Une première version – naïve – de ce programmeserait celle-ci :

attente = 1;

while (attente != 0) pause();

La présence de la boucle while() est justifiée par le fait qu’il se peut que l’appel-système pause() soit interrompu par un autre signal qui ne modifie pas la variableattente. Le problème principal est que le signal peut arriver entre l’instruction dutest (attente != 0) et l’appel pause(). Si le signal modifie la variable attente

à ce moment-là, et si le programme ne reçoit plus d’autres signaux, le processusrestera bloqué indéfiniment dans pause().

Pour éviter cette situation, on pourrait vouloir bloquer le signal temporairement àl’aide de sigprocmask() ainsi :

Page 16: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

92

PROBLEMATIQUE DE L’ ATTENTE D’UN SIGNAL (suite) :

sigset_t ensemble, ancien;

sigemptyset( &ensemble );

sigaddset( &ensemble , SIGUSR1 );

sigprocmask( SIG_BLOCK , &ensemble , &ancien );

attente = 1;

while (attente != 0){

sigprocmask( SIG_UNBLOCK , &ensemble , NULL );

pause();

sigprocmask( SIG_BLOCK , &ensemble , NULL );

/* traitement des autres signaux s’il y a lieu */

}

sigprocmask( SIG_SETMASK , &ancien , NULL );

Malheureusement, un signal bloqué en attente est délivré avant le retour desigprocmask(), qui le débloque. Un blocage du processus dans pause() estdonc toujours possible. Tant que les deux opérations (modifier le masque designaux et attente) sont réalisées sans être sûr qu’une interruption survienne entreelles, il y aura toujours un risque de blocage. La solution est d’employer l’appel-système sigsuspend() qui permet de manière atomique de modifier le masquede signaux ET de bloquer en attente. Lorsqu’un signal non bloqué survientsigsuspend() restitue le masque original avant de se terminer.

Page 17: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

93

PROBLEMATIQUE DE L’ ATTENTE D’UN SIGNAL (fin) :

int sigsuspend( const sigset_t *ensemble );

L’ensemble transmis est celui des signaux à bloquer, pas celui des signaux attendus.

sigset_t ensemble, ancien;

sigemptyset( &ensemble );

sigaddset( &ensemble , SIGUSR1 );

sigprocmask( SIG_BLOCK , &ensemble , &ancien );

if ( sigismember( &ancien , SIGUSR1 )) {

sigdelset( &ancien , SIGUSR1 ):

sigusr1_dans_masque = 1;

}

attente = 1;

while (attente != 0){

sigsuspend( &ancien );

/* puis traitement pour les éventuels autres signaux */

}

if ( sigusr1_dans_masque )

sigaddset( &ancien , SIGUSR1 );

sigprocmask( SIG_SETMASK , &ancien , NULL );

On remarque qu’il est pris soin de restituer l’ancien masque de blocage des signauxen sortie de routine, et qu’en transmettant cet ancien masque à sigsuspend(),l’arrivée d’autres signaux que SIGUSR1 est permise.

Page 18: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

94

GESTION DES SIGNAUX CLASSIQUES (synthèse) :

Nous avons vu qu’avec une gestion correcte des blocages des signaux, il est possibled’accéder à n’importe quel type de données globales. Comme un signal non bloquépeut survenir alors qu’un gestionnaire de signal est en exécution, le champsa_mask de la structure sigaction doit être manipulé avec soin.

Par ailleurs, comme la plupart des fonctions des bibliothèques ne sont pasréentrantes, les seules opérations à effectuer dans un gestionnaire de signal sont :

- consulter/modifier des variables globales de type sig_atomic_t (défini danssignal.h. Il s’agit d’un type entier que le processeur peut traiter de manièreatomique. Il faut de plus utiliser l’indicateur volatile pour signaler au compilateurqu’elle peut être modifiée à tout moment, et pour qu’il ne se livre pas à desoptimisations (comme conserver la variable dans un des registres). Dans ce cas, legestionnaire ne fait que positionner l’état d’une variable globale, qui est ensuiteconsultée dans le corps du programme.

- effectuer des appels-systèmes réentrants. Il existe une liste, définie par la normePosix.1, des appels-systèmes réentrants qui peuvent être invoqués depuis ungestionnaire de signal. Le fait d’être réentrante permet à une fonction d’êtreutilisable sans danger dans un programme multithread, mais la réciproque n’est pastoujours vraie, comme pour malloc() qui est utilisable au sein de programmesmultithread mais ne doit pas être invoquée dans un gestionnaire de signal.

Page 19: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

95

GESTION DES SIGNAUX TEMPS REELS

Les signaux temps réels présentent les particularités suivantes vis-à-vis des signauxclassiques :• nombre plus important de signaux utilisateurs,• empilement es occurrences des signaux bloqués,• délivrance prioritaire des signaux,• informations supplémentaires fournies au gestionnaire de signal.

Les signaux temps réel n’ont pas de noms spécifiques, contrairement aux signauxclassiques et s’étendent de SIGRTMIN à SIGRTMAX compris (SIGRTMAX-SIGRTMIN=32 sous Linux). Linux par exemple associe à chaque signal temps réelune file d’attente qui lui permet de mémoriser l’ensemble de toutes lesoccurrences. Chaque occurrence présente dans la file d’attente donne lieu à unedélivrance spécifique. Le système Linux peut empiler jusqu’à 1024 signaux.

Lorsque plusieurs signaux temps réel doivent être délivrés à un processus, le noyaudélivre toujours les signaux temps réel de plus petit numéro. Ceci permetd’attribuer un ordre de priorité entre les signaux temps réel.

Note : Sous Linux, pour vérifier l’existence de ces signaux en cas de portage d’uneapplication, on peut tester à la compilation la présence de la constante symbolique_POSIX_REALTIME_SIGNALS dans le fichier <unistd.h>

Page 20: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

96

GESTION DES SIGNAUX TEMPS REELS

Envoi d’un signal temps réel

Pour pouvoir fournir des informations supplémentaire au gestionnaire de signal, ilfaut utiliser l’appel-système sigqueue() qui garantit que le signal sera empilé aulieu de kill() (qui peut néanmoins envoyer un signal temps réel). La syntaxe estla suivante :

#include <signal.h>

#include <unistd.h>

int sigqueue( pid_t pid , int num_sig , const union sigval info );

L’argument pid désigne le processus concerné destinataire du signal et l’argumentnum_sig identifie le signal envoyé. Le troisième argument , info, contientl’information supplémentaire associée au signal. C’est une valeur de type union

sigval (de la structure siginfo transmise au gestionnaire de signal – voir après)qui peut prendre deux formes :

• un entier int si le champ sigval_int de l’union est utilisé par lors de l’appel augestionnaire du signal,• un pointeur void * si le champ sigval_ptr de l’union est utilisé

Page 21: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

97

GESTION DES SIGNAUX TEMPS REELS

Syntaxe de l’appel au gestionnaire

Du fait de la transmission d’informations supplémentaires associées au signal tempsréel, l’attachement et la définition d’un gestionnaire de signal s’effectuentdifféremment que dans le cas d’un signal classique. Le gestionnaire prend la formesuivante (le dernier paramètre n’est pas défini par POSIX et n’est donc pas utilisé):

void gestionnaire( int num_sig , struct siginfo *info , void *rien )

La structure de type struct siginfo contient notamment les champs suivantsconforment à la norme POSIX (les autres ne sont pas évoqués ici) :

• int si_signo, le numéro du signal,• int si_code, qui indique l’origine du signal (si envoyé par le noyau - SI_KERNEL- , par un appel-système kill() - SI_USER - , par l’appel-système sigqueue() -SI_QUEUE - lors de la terminaison d’une opération d’entrée/sortie asynchrone -SI_ASYNCIO - , à l’expiration d’une temporisation temps-réel - SI_TIMER - ,….),

• int si_value.sigval_int correspond au champ sigval_int de l’appel àsigqueue(),

• void *si_value.sigval_ptr correspond au champ sigval_ptr de l’appel àsigqueue().

Page 22: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

98

GESTION DES SIGNAUX TEMPS REELS

Attachement du gestionnaire au signal

L’attachement du gestionnaire de signal temps réel à un signal s’effectue aussi avecsigaction() et la structure struct sigaction étendue et contenant (entreautres) les champs suivants :

struct sigaction

{

union {

sighandler_t void (*sa_handler)(int); /* signaux classiques */

void (*sa_sigaction)(int, struct siginfo * , void *);

/* signaux temps réel */

};

sigset_t sa_mask;

unsigned long sa_flags;

};

De plus, le champ sa_flags doit prendre la valeur SA_SIGINFO pour les signauxtemps réel.

Page 23: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

99

EXEMPLE DE GESTION DES SIGNAUX TEMPS REELS

#define MON_SIG_TR (SIGRTMIN+3)

void gestion_TR( int , struct siginfo * , void * );

main() {

union sigval val;

struct sigaction act_TR;

pid = fork()

if (pid == 0 ){

act_TR.sa_sigaction = gestion_TR;

sigemptyset( &act_TR.sa_mask);

act_TR.sa_flags = SA_SIGINFO;

sigaction( MON_SIG_TR , &act_TR , NULL );

pause();

exit(0);}

else {

printf(’’Introduisez un entier au clavier\n’’);

scanf(’’%d’’,&val.sigval_int);

printf(’’%d’’,val.sigval_int);

sigqueue( pid , MON_SIG_TR , val );

wait();

exit(0);}

}

void gestion_TR( int numero , struct siginfo *info , void *rien )

{

printf(’’signal temps reel recu , %d , %d\n’’, numero , info->si_signo);

printf(’’entier recu %d\n’’, info->si_value.sigval_int);

/* il est préférable de ne pas employer les fonctions E/S avec buffer dans un

gestionnaire ….*/

}

Page 24: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

100

GESTION DU TEMPS

Mesure du temps CPU : Chaque processus possède dans son contexte une mesuredu temps CPU qu'il a consommé, et du temps consommé cumulé de tous sesprocessus enfants terminés. Ces informations sont décrites par une structure dedonnées appelée struct tms dont les champs sont les suivants ;

tms_utime : temps CPU utilisateur (exécution d’instructions du programme utilisateur),tms_stime : temps CPU système (exécution des instructions système - mode noyau),tms_cutime : temps CPU utilisateur (comsommé des enfants terminés),

tms_cstime : temps CPU système (consommé des enfants terminés).

Tous les champs sont de type clock_t (équivalent à unsigned long) et lafonction times() qui a la syntaxe suivante :

#include <sys/times.h>

clock_t times( struct tms *t );

permet d'obtenir ces informations. Cette fonction retourne un temps absoluexprimé à partir d'une origine de temps arbitraire (il faut donc l'appeler deux foisafin d'estimer une différence de temps). Tous les temps sont exprimés en unitéd'horloge. Le nombre d'unités d'horloge par seconde est la valeur symboliqueCLK_TCK (égale à 100 ou à 1000):

Page 25: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

101

GESTION DU TEMPS

Exemple :

#include <sys/times.h>

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

int main( void )

{

struct tms t1,t2;

long i;

unsigned long et1,et2;

et1 = times( &t1 );

for ( i = 0 ; i < 200000L ; i++ )

ConsommeDuTemps(i);

et2 = times( &t2 );

printf("\n clicks par sec : %ld\n",CLK_TCK);

printf("\n\tnombre de top utilisateur :

%ld",t2.tms_utime-t1.tms_utime);

printf("\n\tnombre de top systeme :

%ld'',t2.tms_stime-t1.tms_stime);

printf("\n\tnombre de top d'horloge (temps reel) :

%ld",et2-et1);

exit(0);

}

void ConsommeDuTemps( long n )

{

void *p=NULL;

if ( n > 65535 ) return;

p = malloc( 1024*n );

if ( p ) free( p );

return;

}

Page 26: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

102

GESTION DU TEMPS

Il peut arriver cependant que l'on ait besoin de dater les événements avec uneprécision meilleure que 1/CLK_TCK. Pour cela, il existe un appel-système fournissantune meilleur résolution, gettimeofday() , dont la syntaxe est :

#include <sys/time.h>

int gettimeofday( struct timeval * , struct timezone * );

struct timeval {

long tv_sec; /* secondes */

long tv_usec; /* microsecondes */

};

et fournit le nombre de secondes et le nombre de microsecondes depuis le dernierchangement de la valeur du champ tv_sec.Le second argument de cette fonction n'est plus utilisé.

Page 27: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

103

GESTION DU TEMPS

Exemple (sur l’allocation de la mémoire) :

void ConsommeDuTemps( long n )

{

void *p=NULL;

if ( n > 65535 ) return;

p = (void *)malloc( 1024L*n );

if ( p ) free( p );

return;

}

int main( void )

{

long i;

gettimeofday( &timev1 , NULL );

for ( i = 0 ; i < 10 ; i++ ) ConsommeDuTemps( 65535L );

gettimeofday( &timev2 , NULL );

fprintf(stdout,"gettimeofday() : %6ld us\n",timev2.tv_usec-timev1.tv_usec);

/* display 000125 us on my computer */

exit(0);

}

Page 28: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

104

GESTION DU TEMPS

Exemple (sur le tri par sélection) :

#define TAILLE 16

#define SWAP(x,y,t) ((t)=(x),(x)=(y),(y)=(t))

struct timeval timev1,timev2;

long t0[TAILLE]={ 25,36,2,9,51,12,18,42,

23,6,28,13,44,38,21,5};

void SortBySelection( long *t , long left ,

long right )

{

long i,j,tmp,min;

if ( left >= right ) return;

for ( i = left ; i < right ; i++ )

{

min = i;

for ( j = i+1 ; j <= right ; j++ )

{

if ( t[j] < t[min] ) min = j;

}

SWAP(t[i],t[min],tmp);

}

return;

}

int main( void )

{

long i;

for ( i = 0 ; i < TAILLE ; i++ )

printf("%2ld - ",t0[i]);

printf("\n");

gettimeofday( &timev1 , NULL );

SortBySelection( t0 , 0 , TAILLE-1 );

gettimeofday( &timev2 , NULL );

for ( i = 0 ; i < TAILLE ; i++ )

printf("%2ld - ",t0[i]);

printf("\n");

fprintf(stdout,"gettimeofday(): %6ld us\n",

timev2.tv_usec-timev1.tv_usec);

/* display 4 us on my computer */

exit(0);

}

Page 29: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

105

GESTION DU TEMPS

La norme POSIX met à disposition pour chaque processus trois compteurs, chacundécrémentant de manière autonome. quand un compteur vient à expiration(passage à zéro), un signal (SIGALRM ou SIGVTALRM) est envoyé au processus.Selon le mode de fonctionnement de ce compteur, celui-ci peut redémarrer. Laprogrammation de ces compteurs est effectuée par l'initialisation des structures dedonnées itimerval et timeval et par l'emploi des fonctions getitimer() etsetitimer() :

#include <sys/time.h>

struct timeval {

long tv_sec; /* secondes */

long tv_usec; /* microsecondes */

};

struct itimerval {

struct timeval it_interval;/* prochaine valeur */

struct timeval it_value; /* valeur actuelle */

};

int getitimer( int compteur , struct itimerval *valeur );

int setitimer( int compteur , struct itimerval *valeur , struct itimerval

*anc_valeur );

Page 30: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

106

GESTION DU TEMPS

Le paramètre compteur spécifie le compteur à utiliser :

- ITIMER_REAL : ce compteur décrémente en temps réel et fourni le signalSIGALRM à l'expiration,

- ITIMER_VIRTUAL : ce compteur décrémente seulement quand le processus esten exécution et délivre le signal SIGVTALRM à l'expiration (non POSIX),

- ITIMER_PROF : ce compteur décrémente seulement quand le processus est enexécution, en mode noyau. Couplé avec le compteur ITIMER_VIRTUAL, cecompteur est utilisé habituellement pour connaitre le temps d'exécution en modeutilisateur et en mode noyau. Le signal SIGPROF est délivré à l'expiration (nonPOSIX).

La fonction getitimer() permet de connaitre la valeur d'un compteur à unmoment donné et remplit la structure à l'adresse indiquée par valeur. La fonctionsetitimer() permet d'initialiser un compteur avec le contenu de la structurecommençant à l'adresse valeur et de sauvegarder éventuellement l'ancienne valeurà l'adresse indiquée par anc_valeur (si différente de NULL).

Page 31: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

107

GESTION DU TEMPS

Exemple (initialiser le compteur temps réel pour l’envoi d’un signal toutes les 10 ms, après 2 s) :

#include <sys/time.h>

struct timerval t;

int main( void )

{

t.it_interval.tv_sec = 0;

t.it_interval.tv_usec = 10000;

t.it_value.tv_sec = 2;

t.it_value.tv_usec = 0;

setitimer( ITIMER_REAL , &t , NULL );

.....

exit(0);

}

Page 32: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

108

GESTION DU TEMPS

Remarques :

• Puisque le signal SIGALRM est délivré à l'expiration du comptage, on peut associerla délivrance du signal à l'appel d'une fonction de déroutement à intervallesréguliers.

• Il est formellement déconseillé d'utiliser simultanément les appels systèmesetitimer() , sleep() et la programmation de l'alarme avec la fonctionalarm() car elles utilisent le même signal SIGALRM.

• Par défaut, à la délivrance des signaux SIGALRM, SIGVTALRM et SIGPROF, leprocessus se termine.

Exercice : Concevoir un programme simple qui, s'appuyant sur le timer logicielITIMER_REAL, déclenche toutes les 100 ms, l'exécution d'une fonction associée,dont le rôle est de sauvegarder dans un tableau d'entiers (global), les valeurssuccessives d'un incrément d'une boucle exécutée dans la fonction principale. Al'issue de la boucle, les valeurs du tableau seront sauvegardées dans un fichier.

Page 33: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

109

INTRODUCTION AU MULTITHREADING

Le multithreading est l’alternative à la programmation multiprocessus; il offre unparallélisme plus léger à gérer pour le système. Le processus léger (ou thread)constitue une extension du modèle traditionnel de processus, appelé en lacirconstance processus lourd. Un processus classique est constitué d’un espaced’adressage avec un seul fil d’exécution, ce fil d’exécution étant représenté par unevaleur de compteur ordinal et une pile d’exécution.

Dans ce contexte, l’extension consiste à admettre plusieurs fils d’exécutionindépendants dans un même espace d’adressage, chacun de ces fils (ou threads ouactivités) étant caractérisé par une valeur de compteur ordinal propre et une piled’exécution privée. Un thread est donc une entité d’exécution, rattachée à unprocessus, et chargée d’exécuter une partie du code du processus.

Le principal avantage lié à la notion de processus léger est un allègement desopérations de commutations de contextes : en effet lorsque le processus estattribué d’un fil d’exécution à un autre, comme les deux fils appartiennent au mêmeespace d’adressage, la commutation consiste alors en pratique à seulement changerde pile et de valeur de compteur ordinal, le contexte mémoire restant le même. Deplus, l’opération de création dynamique d’un nouveau fil d’exécution estsensiblement plus courte que celle correspondant à la création dynamique d’unnouveau processus (fork()) puisqu’elle ne nécessite pas la duplication de l’espaced’adressage du processus père.

Page 34: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

110

Ci-dessus : Processus classique (une seule activité)

Ci-contre : Processus composé de deux activités

Page 35: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

111

INTRODUCTION AU MULTITHREADING

Comme les processus légers au sein d’un même processus partagent le mêmeespace d’adressage (en partie), il s’ensuit des problèmes de partage de ressources àgérer, mais des mécanismes spécifiques de synchronisation sont proposés.

Les processus légers peuvent être implémentés à deux niveaux différents, soit auniveau utilisateur, soit au niveau noyau, ce qui en fait une entité tout à fait adaptéepour représenter une tâche dans un système temps réel.

Implémentation au niveau utilisateur

Dans ce cas, le noyau ordonnance les processus comme s’ils étaient composés d’unseul fil d’exécution. Le noyau ignore donc les différents fils d’exécution d’un mêmeprocessus. Un exécutif système gère l’interface avec le noyau en prenant en chargela gestion des threads et en cachant ceux-ci du noyau. Cet exécutif (bibliothèque)est donc responsable de la commutation des threads au sein d’un même processus,lorsque ceux-ci en font explicitement la demande (non préemption).

L’avantage est que la commutation des threads d’un même processus s’effectue auniveau utilisateur. L’inconvénient majeur est qu’au sein d’un même processus, unthread peut monopoliser le processeur. Comme le noyau ne connait pas les threads,un thread bloqué (suite à une demande de ressource ne pouvant pas êtreimmédiatement satisfaite) bloque l’ensemble des threads du processus.

Page 36: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

112

INTRODUCTION AU MULTITHREADING

Implémentation au niveau noyau

Lorsque l’implémentation des threads est effectuée au niveau du noyau, alors cedernier connait l’existence de tous les threads au sein d’un processus et il attribue leprocesseur à chacun des threads de manière indépendante. Chaque descripteur deprocessus contient alors une table des threads qui le composent avec pour chacund’eux la sauvegarde de son contexte.

Avec cette approche, on évite le blocage de tout un processus à partir du momentoù l’un de ses threads est bloqué. Par contre, les commutations de contexte étantgérées par le noyau, celles-ci sont un peu plus longues.

Multiprogrammation avec les fils d’exécution (sous Linux)

Les threads d’un même processus partageant le même espace d’adressage peuventcommuniquer entre eux sans faire appel au noyau (d’où un gain de temps parrapport à la communication entre processus par IPC ou Sockets, par exemple). Cetaspect fait apparaître la nécessité de synchroniser l’accès aux ressources pour éviterla corruption des données. Pour parvenir à cela, il est nécessaire d’utiliser desverrous (ou FUTEX – Fast Userlevel muTEX), qui rendent possibles tous lesmécanismes de synchronisation dans l’espace utilisateur.

Page 37: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

113

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Note : La bibliothèque LinuxThread utilise les signaux temps réel SIGRTMIN,SIGRTMIN+1 et SIGRTMIN+2 pour des besoins internes. Si on désire unenotification par signal temps réel, il faut nécessairement employer un numérosupérieur ou égal à SIGRTMIN+3.

L’ensemble des primitives gérant les threads est compilé et rassemblé dans unebibliothèque NPTL (Native POSIX Thread Library) dont l’en-tête se nommepthread.h sous Linux (LinuxThreads). A la compilation, la constante _REENTRANT

doit être incluse (-D_REENTRANT) et l’édition des liens nécessite alors d’intégrercette bibliothèque par l’ajout de l’option –lpthread.

Chaque thread est identifié de manière unique au sein d’une application par untype pthread_t. La primitive pthread_self() permet à chaque thread deconnaître son propre identifiant.

La fonction pthread_equal() permet de comparer deux identifiants de threads :

int pthread_equal( pthread_t thread1 , pthread_t thread2 );

Cette fonction renvoie une valeur non nulle s’ils sont égaux.

Page 38: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

114

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Création d’un thread : La fonction pthread_create() permet la création d’unnouveau thread. Celle-ci donne naissance à un nouveau fil d’exécution, qui vadémarrer en invoquant la routine dont le nom est passé en argument. Lorsque cetteroutine se termine, le thread est éliminé. Cette routine fonctionne donc un peucomme la fonction main() des programmes en C. Pour cette raison, le fild’exécution original du processus est nommé thread principal (main thread).

int pthread_create( pthread_t *thread , pthread_attr_t att , void

*(*fonction)( void *argument) , void *argument );

Le premier argument est un pointeur qui sera initialisé par la routine avecl’identifiant du nouveau thread. Le second argument correspond aux attributs donton désire doter le nouveau thread. Si la valeur NULL est transmise, le thread reçoitalors des attributs standards. Les autres attributs seront détaillés par la suite.

Le troisième argument est un pointeur représentant la fonction principale dunouveau thread. Celle-ci est invoquée dès la création du thread et reçoit enargument le pointeur passé en dernière position. Le type de l’argument étantvoid*, on pourra le transformer en n’importe quel autre type pour passer unargument au thread.

pthread_create() renvoie 0 si le thread a été créé.

Page 39: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

115

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Note : Le nombre de threads simultanés est limité par la constantePTHREAD_THREADS_MAX (1024 avec LinuxThreads).

Terminaison d’un thread : Lorsque la fonction principale d’un thread se termine,celui-ci est éliminé et la fonction doit renvoyer une valeur de type void* qui pourraêtre récupérée dans un autre fil d’exécution. Il est possible aussi de mettre fin à unthread en invoquant directement la fonction pthread_exit() avec un pointeurde type void* passé en argument :

void pthread_exit( void *argument );

Pour récupérer la valeur de l’argument d’un thread terminé, il faut utiliser lafonction pthread_join(). Celle-ci suspend l’exécution du thread appelant jusqu’àla terminaison du thread indiqué en argument.

int pthread_join( pthread_t thread , void **retour );

Cette fonction remplit alors le contenu de l’adresse passée en second argumentavec la valeur retournée par le thread terminé. pthread_join() peut échouer sile thread attendu n’existe pas, s’il est détaché ou si un risque de blocage seprésente.

Page 40: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

116

EXEMPLE

#include <pthread.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd>

#define NB_THREADS 5

void *fn_thread( void * );

static int compteur;

int main(void ) {

pthread_t thread[NB_THREADS];

int i, ret;

for ( i=0 ; i < NB_THREADS ; i++ )

if ((ret = pthread_create( &thread[i] , NULL , fn_thread , (void *)i)) != 0 ) {

fprintf(stderr , ‘’%s’’ , strerror(ret));

exit(1); }

while ( compteur < 40 ) {

fprintf( stdout , ‘’main : compteur = %d\n’’, compteur);

sleep(1); }

for ( i=0 ; i < NB_THREADS ; i++ ) pthread_join( thread[i] , NULL );

return (0);}

void *fn_thread( void *num ) {

int numero = (int)(num);

while ( compteur < 40 ) {

usleep( numero * 100000 );

compteur++;

fprintf( stdout , ‘’Thread %d : compteur = %d\n’’, numero, compteur ); }

pthread_exit( NULL );

}

Page 41: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

117

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Cette application montre l’enchevêtrement des différents threads et est donc malconçue, car les threads modifient la même variable globale sans se préoccuper lesuns des autres. C’est justement l’essence même de la programmation multithreadd’éviter ce genre de situation.

Détachement d’un thread : Lorsqu’un thread ne renvoie pas de valeur et qu’il n’apas besoin d’être attendu par un autre thread, on peut employer la fonctionpthread_detach() qui lui permet de disparaître du système quand il se termine.Cela autorise la libération immédiate des ressources privées du thread (pile etvariables automatiques).

int pthread_detach( pthread_t thread );

Un thread peut très bien invoquer pthread_detach() à propos d’un autre threadde l’application. Contrairement au processus, il n’y a pas de notion de hiérarchiechez les threads ni d’autorisations particulières pour modifier les paramètres d’unautre fil d’exécution. Cette fonction échoue alors si le thread que l’on veut détachern’existe pas (et donc en particulier s’il est déjà détaché).

Page 42: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

118

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Attributs d’un thread : Chaque thread est doté d’un certain nombre d’attributsregroupés dans une structure de données nommée pthread_attr_t. Lorsque lesattributs par défaut ne sont pas suffisants, il faut passer l’adresse d’un objet de typepthread_attr_t qui aura été configuré préalablement. Pour cela il faut invoquerla fonction pthread_attr_init() :

int pthread_attr_init( pthread_att_t * attributs );

Une fois les attributs initialisés, les fonctions pthread_attr_getXXX() etpthread_attr_setXXX() sont utilisées pour consulter ou modifier les champsde la structure de données des attributs. Le XXX indique l’attribut concerné.

Quand une variable contenant les attributs n’est plus nécessaire, elle peut êtredétruite en employant la fonction pthread_attr_destroy(), qui peut libérer desdonnées dynamiques internes :

int pthread_attr_destroy( pthread_att_t * attributs );

Page 43: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

119

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Attributs d’un thread (suite) : Les attributs stackaddr et stacksize permettent deconfigurer la pile utilisée par un thread. Il peut parfois être nécessaire de réclamerune pile de dimension plus grande que celle qui est fournie par défaut si le thread àcréer fait un large usage de fonctions récursives, par exemple. Les fonctions

int pthread_attr_getstackaddr( const pthread_attr_t *attributs , void

**valeur );

int pthread_attr_setstackaddr( pthread_attr_t *attributs , void *valeur );

int pthread_attr_getstacksize( const pthread_attr_t *attributs , size_t

*valeur );

int pthread_attr_setstacksize( pthread_attr_t *attributs , size_t valeur );

sont disponibles dans la bibliothèque si les constantes symboliques_POSIX_THREAD_ATTR_STACKADDR et _POSIX_THREAD_ATTR_STACKSIZE sontdéfinies dans le fichier à en-tête unistd.h. La valeur de la dimension minimale de lapile est accessible par consultation de la valeur de la constante symboliquePTHREAD_STACK_MIN (16 Ko le plus souvent sur PC).

Page 44: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

120

Multiprogrammation avec les fils d’exécution (sous Linux)

Attributs d’un thread (suite) : Les attributs schedpolicy, schedparam, scope etinheritsched concernent l’ordonnancement des threads. Ils sont disponibles si laconstante symbolique _POSIX_THREAD_PRIORITY_SCHEDULING a été définie

int pthread_attr_getschedpolicy( const pthread_attr_t *attributs , int

*valeur );

int pthread_attr_setschedpolicy( pthread_attr_t *attributs , int valeur );

L’attribut schedpolicy correspond à la méthode d’ordonnancement employée pour lethread. Les valeurs possibles sont :

• SCHED_OTHER : ordonnancement classique• SCHED_RR : séquencement temps réel avec la stratégie du tourniquet,• SCHED_FIFO : ordonnancement temps réel FIFO.

L’attribut schedparam contient la priorité du processus et peut être consulté oumodifié respectivement à l’aide des fonctions suivantes :

int pthread_attr_getschedparam( const pthread_attr_t *attributs , struct

sched_param *param );

int pthread_attr_setschedparam( pthread_attr_t *attributs , const struct

sched_param *param );

Page 45: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

121

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Attributs d’un thread (suite) : L’attribut schedparam qui ne concerne que lesordonnancements temps réel (RR et FIFO) ainsi que l’attribut schedpolicy peuventêtre consultés ou modifiés durant l’exécution du thread à l’aide des fonctions

int pthread_setschedparam( pthread_t thread , int ordonnancement , const

struct sched_param *param );

int pthread_getschedparam( pthread_t thread , int *ordonnancement , struct

sched_param *param );

L’attribut scope n’est pas vraiment configurable. Il sert dans les implémentationshybrides reposant en partie sur un ordonnancement par le noyau (c’est le cas sousLinux) et en partie sur une bibliothèque dans l’espace utilisateur. Dans ce cas, cetattribut peut prendre l’une des valeurs suivantes :

• PTHREAD_SCOPE_SYSTEM : cette valeur réclame un ordonnancement du thread enconcurrence avec tous les processus du système. Le séquenceur utilisé est alors celuidu noyau.• PTHREAD_SCOPE_PROCESS : cet ordonnancement oppose les threads les uns auautres, au sein d’un même processus (non supporté pour les threads Linux).

Page 46: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

122

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Attributs d’un thread (suite) : L’attribut scope est donc relatif aux priorités des threads: dans un cas vis-à-vis du système, et dans l’autre cas, interne au processus dont ilsdépendent.

int pthread_attr_getscope( const pthread_attr_t *attributs , int *valeur );

int pthread_attr_setscope( pthread_attr_t *attributs , int valeur );

L’attribut inheritsched signale si le thread dispose de sa propre configurationd’ordonnancement, comme c’est le cas par défaut, ou si les attributs schedparam etschedpolicy sont ignorés, au profit de l’ordonnancement du thread qui l’a créé. Lesvaleurs de cet attribut peuvent être :

• PTHREAD_EXPLICIT_SCHED : l’ordonnancement est spécifique au thread créé(par défaut),

• PTHREAD_INHERIT_SCHED : l’ordonnancement est hérité du thread créateur.

Page 47: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

123

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Attributs d’un thread (fin) : Les fonctions associées à cet attribut sont :

int pthread_attr_getinheritsched( const pthread_attr_t *attributs , int

*valeur );

int pthread_attr_setinheritsched( pthread_attr_t *attributs , int valeur );

Annulation d’un thread : un thread peut vouloir annuler un autre thread. Unedemande d’annulation est envoyée, et sera prise en compte ou non, en fonction de laconfiguration du thread récepteur. Le thread récepteur peut accepter la requête, larefuser ou la repousser plus loin dans son exécution (point d’annulation). Pourenvoyer une demande d’annulation, on emploie la fonction :

int pthread_cancel( pthread_t thread );

La fonction pthread_setcancelstate() permet de configurer le comportement duthread récepteur vis-à-vis d’une requête d’annulation à venir :

int pthread_setcancelstate( int etat_annulation , int *ancien_etat );

Page 48: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

124

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Déroulement et annulation d’un thread (suite) : les valeurs possibles des états sont :

• PTHREAD_CANCEL_ENABLE : le thread acceptera les requêtes d’annulation (par défaut),• PTHREAD_CANCEL_DISABLE : le thread ne tiendra pas compte des demandesd’annulation.

Les requêtes d’annulation ne sont pas mémorisées, ce qui fait qu’un threaddésactivant temporairement les requêtes d’annulation sur une zone de code critiquene se terminera pas lorsqu’il autorisera de nouveau les annulations, même siplusieurs demandes sont parvenues pendant ce laps de temps.Il existe un moyen d’empêcher l’annulation de se produire intempestivement, tout enacceptant les requêtes; il s’agit d’un mécanisme de synchronisation qui est fondé surun retardement des annulations jusqu’à atteindre des emplacements bien définis ducode. Le thread récepteur ainsi configuré ne se terminera pas dès réception d’uneannulation (pas forcément), mais continuera de s’exécuter jusqu’à atteindre ce pointd’annulation. Pour configurer ce comportement, on emploie la fonction

int pthread_setcanceltype( int type_annulation , int *ancien_type );

Page 49: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

125

INTRODUCTION AU MULTITHREADING

Multiprogrammation avec les fils d’exécution (sous Linux)

Déroulement et annulation d’un thread (fin) : avec pthread_setcanceltype(), letype d’annulation peut correspondre à l’une des valeurs suivantes :

• PTHREAD_CANCEL_DEFERRED : le thread ne se terminera qu’en atteignant un pointd’annulation (par défaut),• PTHREAD_CANCEL_ASYNCHRONOUS : l’annulation prendra effet dès réception de larequête.

La norme POSIX définit quatre fonctions qui constituent des points d’annulation,c’est-à-dire des fonctions avec lesquelles un thread est susceptible de se terminer :

• pthread_cond_wait() et pthread_cond_timedwait() : en attente qu’unecondition soit remplie, par exemple une condition de déblocage,• pthread_join(),

• void pthread_testcancel( void )

Pour cette dernière fonction, le thread peut se terminer si une demande d’annulationest en cours. On voit donc que dans le cas d’un état PTHREAD_CANCEL_DEFERRED,un thread ne sera jamais interrompu au milieu d’un calcul ou dans une boucle demanipulation de données. On pourra donc répartir des appelspthread_testcancel() dans ce genre de code, aux endroits où on est sûr qu’uneannulation ne présente aucun danger.

Page 50: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

126

SYNCHRONISATION DES TACHES

Dans un système multitâches, les tâches peuvent avoir besoin de communiquer entreelles pour échanger des données. Cet échange de données peut se faire par le biaisd’une zone de mémoire partagée, par le biais d’un fichier ou encore en utilisant lesoutils de communication du système d’exploitation. Dans tous les cas, les tâches nesont plus indépendantes et elles effectuent des accès concurrents aux ressources.

Plus généralement, une ressource désigne toute entité dont a besoin une tâche pours’exécuter. La ressource peut être matérielle comme le processeur ou unpériphérique ou elle peut être logicielle comme une variable. Une ressource est aussicaractérisée par un état qui définit si la ressource est libre ou occupée et par sonnombre de points d’accès, c’est-à-dire le nombre de tâches pouvant y accéder enmême temps. On distingue alors la notion de ressource critique qui correspond à uneressource ne pouvant être accédée que par une seule tâche à la fois.

L’utilisation d’une ressource par une tâche s’effectue en trois étapes. La premièreétape correspond à l’étape d’attribution de la ressource à la tâche qui consiste àdonner la ressource à la tâche qui la demande. Une fois que la tâche a pu obtenir laressource, elle occupe la ressource durant un certain temps (deuxième étape) puisrend la ressource; c’est la dernière étape de restitution de la ressource.Les phases d’attribution et de restitution de la ressource doivent assurer que laressource est utilisée conformément à son nombre de points d’accès.

Page 51: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

127

SYNCHRONISATION DES TACHES

Par ailleurs, l’étape d’attribution de la ressource peut se révéler bloquante pour latâche qui l’effectue si tous les points d’accès de la ressource sont occupés. Aucontraire, l’étape de restitution d’une ressource par une tâche peut entrainer ledéblocage d’une autre tâche en attente d’accès à cette ressource.Plusieurs schémas de synchronisation entre tâches ont été définis afin de garantirune bonne utilisation des ressources par les tâches et d’une manière plus généraleune communication entre tâches cohérente et sans perte de données. Un desschémas les plus répandus pour les ressources critiques est celui de l’exclusionmutuelle. Le code d’utilisation (durant la phase d’occupation) d’une ressourcecritique est appelée section critique.

Accès à une ressource critique par exclusion mutuelle : ce mécanisme desynchronisation a pour rôle de garantir qu’une ressource critique ne peut êtremanipulée que par une seule tâche à la fois. Pour ce faire, la section critique estprécédée d’un prélude et suivie d’un postlude qui assurent l’exclusion mutuelle. Leprélude prend la forme d’une protection et le postlude celui d’une fin de protection.Quant à la section critique, elle doit offrir les trois propriétés suivantes :

• exclusion mutuelle,

• attente bornée : la tâche demandant l’accès à la ressource critique doit obtenirsatisfaction au bout d’un temps borné,

Page 52: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

128

SYNCHRONISATION DES TACHES

Accès à une ressource critique par exclusion mutuelle (suite)

• bon déroulement : lorsque la ressource critique est inoccupée et qu’une ouplusieurs tâches sont attentes pour entrer dans celle-ci, le choix de la tâche entrantfinalement en section critique ne relève que des tâches en attente, ce choix devant sefaire dans un laps de temps borné. Par ailleurs, une tâche s’exécutant en dehors de lasection critique ne peut pas bloquer l’accès à la ressource critique associée.

Algorithme de Peterson : c’est une solution entièrement logicielle au problème del’accès à une section critique. En particulier, elle ne requiert pas l’aide du systèmed’exploitation. Pour l’appréhender, considérons deux tâches i et j. La structure de latâche i étant la suivante :

/* prelude */

while { Drapeau[i] = VRAI ; tour = j;

while (Drapeau[j] & tour == i );

Section critique

/* postlude */

Drapeau[i] = FAUX;

}

Page 53: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

129

SYNCHRONISATION DES TACHES

Accès à une ressource critique par exclusion mutuelle (suite)

• explications : La variable Drapeau permet à chaque tâche d’indiquer sa volontéd’entrer en section critique (donc d’accéder à la ressource critique) en positionnantcelle-ci à VRAI. La variable tour indique quelle tâche a le droit d’entrer en sectioncritique. Ce sont deux variables globales. On peut vérifier que les trois propriétésprécédentes sont vérifiées :

• exclusion mutuelle : si deux tâches veulent entrer en section critique, alorsDrapeau[i]=Drapeau[j]=VRAI. Seule la tâche pour laquelle tour contient savaleur peut entrer en section critique. Si une des tâches est déjà en sectioncritique (par exemple i) et que l’autre tâche , j, veut y entrer à son tour, alors lavariable tour vaut forcément i et la tâche j est bloquée.

• attente bornée : si une des deux tâches (i par exemple) veut entrer en sectioncritique et se trouve mise en attente, alors cela veut dire que la section critiqueest occupée par l’autre tâche (j). Lorsque la tâche j sort de la section critique,elle positionne son Drapeau à FAUX. Si cette même tâche revientimmédiatement demander l’entrée de la section critique, elle met son Drapeau

à VRAI mais également la variable tour à i. Comme la tâche i en attente dans laboucle ne modifie pas la variable tour, la tâche i entre en section critique,après une attente qui au maximum est égale à une entrée de la tâche j ensection critique.

Page 54: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

130

SYNCHRONISATION DES TACHES

Accès à une ressource critique par exclusion mutuelle (suite)

• bon déroulement : une tâche en dehors de la section critique et ne désirantpas y pénétrer positionne son Drapeau à FAUX. Elle ne peut alors pas bloquerl’entrée de la section critique. Si deux tâches veulent entrer en même temps ensection critique, toutes deux positionnent leur Drapeau à VRAI et chacunecommute le tour pour le donner à l’autre. Ainsi, pour au moins une des deuxtâches, la condition du while est fausse et cette tâche entre en section critique.

Réalisation avec des MUTEX (sous Linux)

Les mutex et les variables de conditions sont deux outils de synchronisationpermettant de réaliser l’exclusion mutuelle entre threads.

Un mutex est une variable de type pthread_mutex_t servant de verrou pourprotéger l’accès à des zones de code ou de données. Ce verrou peut prendre deuxétats, disponible ou verrouillé, et il ne peut être acquis que par un seul thread à lafois. Un thread demandant à verrouiller un mutex déjà acquis par un autre thread estmis en attente.

• Initialisation d’un mutex : l’initialisation d’un mutex s’effectue à l’aide de laconstante PTHREAD_MUTEX_INITIALIZER :

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

Page 55: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

131

SYNCHRONISATION DES TACHES

Réalisation avec des MUTEX (sous Linux)

• Verrouillage d’un mutex :le verrouillage d’un mutex s’effectue en appelant laprimitive pthread_mutex_lock() dont le prototype est :

int pthread_mutex_lock( pthread_mutex_t * mut );

Si le mutex est libre alors il est attribué au mutex appelant, sinon le thread appelantest mis en attente jusqu’à la libération du mutex. La primitivepthread_mutex_trylock() effectue également le verrouillage d’un mutex.Cependant, elle échoue (erreur EBUSY retournée) lorsque le mutex est déjà verrouilléplutôt que de bloquer le thread appelant :

int pthread_mutex_trylock( pthread_mutex_t * mut );

• Libération d’un mutex : la libération d’un mutex s’effectue en appelant la primitive

int pthread_mutex_unlock( pthread_mutex_t * mut );

• Destruction d’un mutex : la destruction d’un mutex s’effectue à l’aide de la primitive

int pthread_mutex_destroy( pthread_mutex_t * mut );

Page 56: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

132

EXEMPLE

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

int i;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *addition( void *inutile ) {

/* verrouillage du mutex */

pthread_mutex_lock( &mutex );

i = i+10;

printf(‘’hello, thread fils %d\n’’,i);

i = i+20;

printf(‘’hello, thread fils %d\n’’,i);

/* liberation du mutex */

pthread_mutex_lock( &mutex );

}

int main( void ) {

pthread_t num_thread;

i = 0;

pthread_create( &num_thread, NULL, addition , NULL );

pthread_mutex_lock( &mutex ); /* verrouillage du mutex */

i = i+1000;

printf(‘’hello, thread principal %d\n’’,i);

i = i+2000;

printf(‘’hello, thread principal %d\n’’,i);

pthread_mutex_unlock( &mutex ); /* liberation du mutex */

pthread_join( num_thread , NULL ); /* sync le th principal sur fin du th fils */

pthread_mutex_destroy( &mutex ); /* destruction du mutex */

}

Page 57: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

133

SYNCHRONISATION DES TACHES

Réalisation avec des variables conditions (et mutex - sous Linux)

Les variables conditions sont des objets de type pthread_cond_t permettant à unetâche de se mettre en attente d’un événement provenant d’un autre thread, commepar exemple la libération d’un mutex.La variable condition est associée à deux opérations, l’une permettant l’attente d’unecondition, l’autre permettant de signaler que la condition est remplie. Elle esttoujours associée à un mutex qui la protège. Son utilisation suit le schéma suivant :

• Thread en attente de la condition :1. Initialisation de la condition et du mutex associé,2. Blocage du mutex et mise en attente sur la condition,3. Libération du mutex

• Thread signalant la condition :1. Exécution jusqu’à réaliser la condition attendue,2. Blocage du mutex associé et signalisation de la condition remplie,3. Libération du mutex.

La primitive effectuant la mise en attente libère atomiquement le mutex associé à lacondition, permettant au thread signalant la condition de l’acquérir à son tour. Leverrou est de nouveau acquis par le thread en attente, une fois la condition attenduesignalée.

Page 58: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

134

SYNCHRONISATION DES TACHES

Réalisation avec des variables conditions (et mutex - sous Linux)

Précisément, l’étape 2 pour le thread attendant la condition se déroule comme suit :

• blocage du mutex,• mise en attente sur la condition et déblocage du mutex,• condition signalée, réveille du thread et blocage du mutex, une fois celui-ci libérépar le thread signalant la condition.

• Initialisation d’une variable condition : elle s’effectue à l’aide de la constantePTHREAD_COND_INITIALIZER ou en employant pthread_cond_init():

pthread_mutex_t condition = PTHREAD_COND_INITIALIZER;

• Mise en attente sur une condition : la primitive pthread_cond_wait() permet àun thread de se mettre en attente sur une condition :

pthread_cond_wait( pthread_cond_t *condition, pthread_mutex_t

*mutex );

• Signalisation d’une condition : la primitive pthread_cond_signal() permet à unthread de signaler une condition remplie :

pthread_cond_signal( pthread_cond_t *condition );

Page 59: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

135

SYNCHRONISATION DES TACHES

Réalisation avec des variables conditions (et mutex - sous Linux)

• Destruction d’une variable condition : la destruction s’effectue à l’aide de laprimitive pthread_cond_destroy() :

pthread_cond_destroy( pthread_cond_t *condition );

Thread attendant la condition

• Appel de pthread_mutex_lock(): blocagedu mutex associé à la condition.

• Appel de pthread_cond_wait(): déblocagedu mutex.

• …attente …•.•.• Dans pthread_cond_wait(), tentative derécupérer le mutex. Blocage du thread.•.•.• Fin de pthread_cond_wait().• Appel de pthread_mutex_unlock() pourrevenir à l’état initial.

Thread signalant la condition

• Appel de pthread_mutex_lock(): blocage dumutex associé à la condition

•.•.• Appel de pthread_mutex_lock() sur le mutex.• Appel de pthread_cond_signal(), qui réveillel’autre thread.•.•.• Appel de pthread_mutex_unlock(). Le mutexétant libéré, l’autre thread se débloque.

Page 60: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

136

EXEMPLE

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#include <unistd.h>

pthread_cond_t condition_alarme

= PTHREAD_COND_INITIALIZER;

pthread_mutex_t mutex_alarme

= PTHREAD_MUTEX_INITIALIZER;

static int aleatoire( int );

static void *thread_alarme(void * );

static void *thread_temperature( void * );

int main( void ) {

pthread_t thr[2];

pthread_create( &thr[0], NULL, thread_temperature , NULL );

pthread_create( &thr[1], NULL, thread_alarme , NULL );

pthread_exit( NULL );}

static void *thread_temperature(void *inutile ) {

int temperature = 20;

while (1){temperature += aleatoire(5)-2;

fprintf(stdout,‘’Temperature : %d \n’’,temperature);

if ((temperature < 16) || (temperature > 24)) {

pthread_mutex_lock(&mutex_alarme);

pthread_cond_signal(&condition_alarme);

pthread_mutex_unlock(&mutex_alarme);}

sleep(1);

}

return(NULL);}

static void *

thread_alarme(void *inutile ) {

while (1){

pthread_mutex_lock(&mutex_alarme);

pthread_cond_wait(&condition_alarme,

&mutex_alarme);

pthread_mutex_unlock(&mutex_alarme);

fprint(stdout,’’ALARME\n’’);

}

return(NULL);

}

Page 61: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

137

SEMAPHORES POSIX (sous Linux – POSIX)

La bibliothèque LinuxThreads implémente un mécanisme de synchronisation quiappartient en fait à la norme POSIX.1b (temps réel) : les sémaphores.Un sémaphore est une variable de type sem_t servant à limiter l’accès à uneressource, le plus souvent une portion de code. L’initialisation d’un sémaphore, dansl’utilisation avec les threads pour des applications temps réel, se fait grâce à lafonction sem_init(), et sa libération en employant sem_destroy() :

int sem_init( sem_t *semaphore, int partage, unsigned int val );

int sem_destroy( sem_t *semaphore );

Le deuxième argument de sem_init() indique si le sémaphore est réservé auprocessus appelant ou s’il doit être partagé entre plusieurs processus. La versionactuelle de la bibliothèque LinuxThreads ne permet pas le partage en dehors desthreads du même processus, donc cette valeur est toujours nulle. Le troisièmeargument représente la valeur initiale du sémaphore. Cette valeur est inscrite dansun compteur qui est décrémenté chaque fois qu’un thread pénètre dans la ressourcelogicielle, et incrémenté à chaque sortie de la ressource. L’entrée dans une portion decode protégée par un sémaphore ne peut se faire que si le compteur est strictementpositif. Ainsi, la valeur initiale du compteur représente le nombre maximum dethreads simultanément tolérés.

Ces fonctionnalités sont déclarées dans le fichier à en-tête sémaphore.h si laconstante symbolique _POSIX_SEMAPHORES est définie dans unistd.h.

Page 62: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

138

SEMAPHORES POSIX (sous Linux – POSIX)

Lorsqu’un thread désire entrer dans la portion de code critique, il appelle la fonctionsem_wait() qui attend que le compteur du sémaphore soit supérieur à zéro, et ledécrémente avant de revenir. La vérification de la valeur du compteur et sadécrémentation sont liées de manière atomique, évitant ainsi tout problème deconcurrence d’accès. Cette fonction est un point d’annulation pour les threads :

int sem_wait( sem_t * semaphore );

En sortie de la portion (de code) critique, le thread invoque la fonction sem_post()

qui incrémente le compteur (postlude) :

int sem_post( sem_t *semaphore );

Il existe une fonction, sem_trywait() , fonctionnant comme sem_wait() maisqui ne bloque pas si le compteur n’est pas supérieur à 0.

int sem_trywait( sem_t *semaphore );

On peut aussi consulter directement la valeur du compteur d’un sémaphore enappelant sem_getvalue( ) qui stocke l’état actuel dans la variable du secondargument de sem_getvalue():

int sem_trywait( sem_t *semaphore , int *value );

Page 63: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

139

EXEMPLE

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#include <semaphore.h>

#include <unistd.h>

sem_t semaphore;

static int aleatoire( int maximum );

static void *routine_thread( void *num_thread );

void *routine_thread( void *num_thread )

{

int i;

for (i=0; i<2 ; i++ ) {

sem_wait(&semaphore);

printf(’’Thread %d dans portion critique \n’’,(int)(num_thread));

sleep(aleatoire(4));

printf(’’Thread %d sort de la portion critique \n’’,(int)(num_thread));

sem_post(&semaphore);

sleep(aleatoire(4));

}

return(NULL);

}

int main( void ) {

int i;

pthread_t thread;

sem_init(&semaphore,0,3);

for (i=0; i < 10; i++ ) {

pthread_create( &thread, NULL,

routine_thread, (void *)(i));

pthread_exit( NULL );

}

Page 64: Mise en oeuvre: signaux, gestion du temps et multi-activités

ENSP Strasbourg (Edition 2009-2010)Les Systèmes Temps Réels - Ch. DOIGNON

140