Programmation temps réel - MIME

Transcription

Programmation temps réel - MIME
———————–
Programmation temps réel
———————–
Systèmes, Processus et Threads
———————–
Université Paris 8 - Saint Denis
———————–
[email protected]
1
Table des matières
1 Introduction
1.0.1 Linux .
1.0.2 FreeBSD
1.1 RTLinux . . .
1.2 MQX . . . . .
1.3 QNX . . . . .
1.4 liens utiles . . .
. .
.
. .
. .
. .
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
4
5
5
5
5
2 Système et temps réel
6
3 Processus et thread
3.1 Rappel concernant les sémaphores . . . . . . . . . . . . . . . . .
3.2 Rappel concernant les mutex . . . . . . . . . . . . . . . . . . . .
6
6
7
4 Synchronisation en action
4.1 règles d’ordonnancement et états possibles . . . . . . . . . . . . .
4.2 schéma d’ordonnancement . . . . . . . . . . . . . . . . . . . . . .
4.3 Concurence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
7
8
8
5 Gestion des processus
5.1 Processus versus thread . . . . .
5.2 Appels systèmes . . . . . . . . .
5.2.1 system . . . . . . . . . . .
5.2.2 fork . . . . . . . . . . . .
5.2.3 vfork . . . . . . . . . . . .
5.2.4 les familles exec et spawn
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
9
9
9
9
10
10
6 Gestion des threads
13
6.1 Création d’un thread . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.2 Gestion des threads . . . . . . . . . . . . . . . . . . . . . . . . . . 15
7 Création de plusieurs threads
8 Synchronisation de threads
8.1 Avec une valeur . . . . . .
8.2 Avec mutex . . . . . . . .
8.3 Avec sémaphore et mutex
8.4 Avec barrière . . . . . . .
.
.
.
.
16
.
.
.
.
.
.
.
.
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
23
26
28
1
Introduction
POSIX : est une spécification 1 de l’interface des systèmes d’exploitation 2 portables 3
— POSIX.1 : liste les appels systèmes UNIX 4 attendus 5
— options POSIX.1 : définissent notamment les listes de controle d’accès
regroupant le “job-control”, “chown restricted” et “shadow maintain”. Le “job-control” définit la capacité d’un système 1) à suspendre
l’exécution d’un processus par sélection d’une instruction et 2) à poursuivre ou reprendre cette exécution par la suite. Pour un utilisateur courant, le “job-control” est accessible au travers des terminaux en ligne de
commande et au travers de fonction dans les scripts bash ou autre. Le
“chown restricted” définit les droits de propriété des utilisateurs et des
groupes sur les fichiers du système. Le “shadow maintain” assure que
chaque processus possède un identifiant enregistré pour les utilisateurs
et pour les groupes d’utilisateurs, ce qui permet de garantir le maintien
de droits conformes entre processus, utilisateurs et groupes d’utilisateurs.
Les fonctions se résument par :
— job-control : suspendre et résumer un processus
— chown : définir qui peut montrer et comment il peut le montrer
— shadow maintain : maintenir uid et guid au travers de fork et kill
— POSIX.4 : liste les fonctionnalités temps-réel supplémentaires
— options POSIX.4 : définissent les modalités d’utilisation 6 des entréessorties synchrones (i.e. “sync i/o”) et asynchrones (i.e. “asynch i/o”),
de la mémoire partagée (i.e. “shared memory”), des signaux temps-réel
(i.e. “realtime signals”), des files de messages (i.e. “message queue”), des
priorités d’ordonnancement (i.e. “prority scheduling”), des verrous (i.e.
“memory locking”), des sémaphores (i.e. “semaphores”) et des horloges
(i.e. “timers”) .
Les systèmes UNIX pris en compte dans ce cours :
— Linux : UNIX pour développements libre sur un ordinateur personnel
— FreeBSD : UNIX dérivé de BSD pour applications serveur sur un ordi1. Les trois grandes spécifications sont POSIX , BSD et SystemV , pouvant être respectivement vues comme la norme IEEE essayant d’établir un standard pour la construction
des systèmes d’exploitation, comme le cadre global de la spécification des UNIX libres et
comme le cadre global de la spécification des UNIX privés. Dans les détails, POSIX spécifie
les modalités d’interfaçage des applications sur les systèmes UNIX .
2. Système d’exploitation ou OS pour Operating System .
3. L’acronyme POSIX vient littéralement de la phrase Portable Operating System Interface (and what’s an os without an X on the end of it ?) .
4. L’histoire des systèmes UNIX débute en 1969 dans les laboratoires Bell, laboratoires
dans lesquels est crée un système d’exploitation pour PDP-7 de DEC, ensuite adapté pour
PDP-11/20 en 1971. D’abord développé en assembleur, UNIX permettra d’exprimer la puissance du langage C.
5. Les appels systèmes sont définis dans leur sémantique ou leur utilisation. Les questions
de l’implémentation choisie pour réaliser les fonctions de ces appels systèmes est laissée libre.
6. Ici équivalent au comportement attendu.
3
nateur personnel
Les systèmes temps-réel pris en compte dans ce cours :
— QNX : UNIX micronoyau pour architectures i386
— RTLinux : Linux avec patch temps-réel pour architectures i386
— MQX : micronoyau temps-réel pour architectures ARM et PowerPC
1.0.1
Linux
historique des systèmes d’exploitation Linux 7 :
— 1991 : version 0 pour 80386 proposée par Linus Torvalds 8
— version 1.0
— version 2.0
— version 2.2
— version 2.4
— version 2.6
1.0.2
FreeBSD
historique des systèmes d’exploitation FreeBSD 9 :
— 1993 : FreeBSD 1.0 résultat d’un mix de 386BSD avec BSD-lite 4.3
UNIX développé à l’Université de Berkeley en Californie
— 1995 : En janvier 1995, FreeBSD 2.0 est une réécriture complète sans les
anciennes parties Novell
— version 3
— version 4
— version 5 10
7. Linux est le fruit d’un développement mondiale basé sur le bénévolat de nombreux
développeurs. Il s’inscrit dans la mouvance de la Free Software Foundation (abrégée FSF ),
de l’Open Source et de la licence publique générale GNU (abrégéé GNU GPL). Linux est
disponible sous la forme de nombreuses distributions payantes ou gratuites. Il implémente
POSIX.1 et SystemV . Il tends vers le support de POSIX.4 et l’application d’un patch sur
son noyau peut en faire un système temps réel tel que RTLinuxFree. Linux est aujourd’hui de
plus en plus présent dans les entreprises.
8. la version 0.01 initialement développée à l’université d’Helsinki est disponible à l’adresse
suivante http://www.kernel.org/pub/linux/kernel/Historic/. Cette première version non
exécutable requiert Minix. La version 0.02 ajoute un shell bourne et gcc. Une version 0.03
précédera de nombreuses versions 0.99 résultants de la résolution de nombreux bugs.
9. implémente POSIX.1 et BSD .
10. version donnant lieu à une cission du groupe de développement et à la création d’une
nouvelle distribution restant sur la version 4 et dédiée à la réalisation de cluster, appelée
DragonFly BSD. Cette dernière ditribution se focalise sur des points cruciaux concernant la
construction des cluster tels que :
— un modèle de vue globale pour la gestion de la mémoire et des entrées-sorties au travers
d’une image globale du système (regroupant tous les ordinateurs du cluster )
— une nouvelle API semi-synchronisée pour la gestion des entrées-sorties en développant les
concepts d’objet, de communication entre objets par message et de sauvegarde d’objets
dans des flux de données
— la communication par message
— un système de gestion des applications permettant de choisir plus librement les versions
des applications tout en assurant la gestion automatique des dépendances
4
— version 6
1.1
RTLinux
C’est un système Linux avec patch temps-réel pour architectures i386.
1.2
MQX
C’est un micronoyau temps-réel pour architectures ARM et PowerPC distribué par la société Freescale.
1.3
QNX
Historique des systèmes d’exploitation QNX 11 :
— 1980 : La compagnie Quantum Software Systems Limited propose le
système Quantum UNIX (i.e. UNIX sur 8088) fondée par Dane Dodge
& Gordon Bell. Très vite, ce système prend le nom de QNX , qui aura
pour des raisons de marketing de modifier le nom de la compagnie pour
QNX Software Systems Limited puis QNX Software Systems.
— 1990 : QNX2 suit l’évolution des processus Intel pour proposer un système
temps-réel.
— 1991 : QNX4 est une version 32bits avec un meilleur support des normes
POSIX . Les différentes évolutions de ce système se terminent avec la
version 4.25, qui fut connue pour exister en version disquette 12 .
— 1995 : QNX6 Neutrino est maintenant abrégé QNX6 ou Neutrino .
— 2000 : QNX RTP se présente comme une plateforme de développement
pour le temps-réel en regroupant le système Neutrino , l’environnement
graphique Photon, les utilitaires de développement graphique PhAB. Le
noyau de QNX6 et le gestionnaire de processus sont regroupés dans
procnto.
1.4
—
—
—
—
liens utiles
posix.ieee.org
www.linux.com
www.freebsd.org
www.bsd.org
— le retrait des processus en mode noyau (en les remplaçant par des threads particuliers
appelés light weight kernel threading abrégés LWKT )
— un ensemble d’appels système basé sur les données utilisées dans ces derniers
— un nouveau système de fichiers virtuel
DragonFly BSD est le dernier sorti des systèmes BSD . Les deux autres branches de BSD sont
NetBSD et OpenBSD . NetBSD s’attarde fortement à assurer sa portabilité sur un maximum
d’architectures. OpenBSD s’attarde fortement à assurer la sécurité de son exécution.
11. implémente en tendance POSIX.1 , POSIX.4 et ajoute de nouvelles fonctionnalités
spécifiques à la programmation temps réel.
12. une version contenant l’OS et un navigateur web à considérablement contribuée à
l’expansion de QNX . Du coté des distributions BSD , un projet similaire s’appelle PicoBSD.
5
—
—
—
—
—
2
rt.wiki.kernel.org
www.freescale.com
www.qnx.com
qdn.qnx.com
www.openqnx.com
Système et temps réel
La notion de temps-réel implique le respect de contraites de temps. Répondre
en temps-réel correspond à répondre conformément à des contraintes de temps.
En temps-réel mou, répondre après un temps T implique une utilisation
dégradée.
En temps-réel dur, répondre après un temps T n’est pas acceptable ou ne
permet pas d’utilisation.
Couramment le temps dans un système d’exploitation est fonction du temps
de commutation et de la gestion des priorités au travers des procédures d’ordonnancement.
3
Processus et thread
— processus V.S. thread (1 thread est une sous-partie d’un processus).
— mono-thread V.S. multi-thread (Avec une capacité de calcul fixée, une
ressource partagée unique et des traitements séquentiels, la répartiton
des traitements dans des threads distincts peut ne présenter aucun gain
en temps de réponse).
— section critique et exclusion mutuelle (La notion de section critique apparait lors du partage de ressources communes entre des traitements. L’exclusion mutuelle des traitements sur une section critique permet ainsi
de garantir une utilisation unique de cette ressource commune. POSIX
propose la structure pthread mutex t pour la l’utilisation d’une exclusion mutuelle entre pthreads. Il est important de noter que l’exclusion
mutuelle est garantie par la bonne utilisation de cette structure et que
dans l’absolu, rien n’interdit une mauvaise utilisation d’une ressource
commune sans utiliser cette structure.).
— synchronisation et ressource commune (La notion de synchronisation est
différente de la notion de ressource commune. Pour réaliser ces opérations
de principe, les mécanismes de communication inter-processus (i.e. IPC
pour Inter-Processus Communications) regroupent sémaphores, mutex,
mémoires partagées, files de messages et variables conditionnelles.).
3.1
Rappel concernant les sémaphores
les sémaphores sont définis dans semaphore.h :
— structure de type sem t
— non-nommées (sem init, sem destroy)
6
— nommées (sem open, sem close, sem unlink)
— opérations sur sémaphores (sem wait, sem trywait, sem post, sem getvalues)
Assurer le support des sémaphores :
#ifdef _POSIX_SEMAPHORES
#include <semaphore.h>
int main(void) {
sem_t s0;
...
return 0;
}
#endif /* _POSIX_SEMAPHORES */
3.2
Rappel concernant les mutex
Les mutex sont définis dans pthread.h :
— structure de type pthread mutex t
— initialiser (pthread mutex init)
— verrouiller (pthread mutex trylock, pthread mutex timedlock, pthread mutex lock)
— déverrouiller (pthread mutex unlock)
— libérer (pthread mutex destroy)
4
Synchronisation en action
Gestion de plusieurs threads => ordonnancement.
Discussion :
— temps de latence = temps écoulé entre choix et effectivité d’un thread
— préemption = un thread prioritaire peut interrompre un thread moins
prioritaire
— temps-réel =
— répondre après un temps t équivaut à répondre faux
— répondre dans un temps t dans un environnement physique
— simuler un système en reproduisant sa vitesse initiale
4.1
règles d’ordonnancement et états possibles
Algorithmes d’ordonnancement classiques :
— F IF O (peps)
— RR pour round-robin (tourniquet)
Règles d’exécution des threads sur un mono-processeur :
— un seul thread simultanéement
— le thread READY de plus haute priorité
— un thread ne retourne rien sans blocage ou sortie
7
— un thread RR possède une durée max d’exécution
Etats possibles d’un thread :
— READY : prêt pour exécution
— RUNNING : en cours d’exécution
— BLOCKED : (CONDVAR, DEAD, INTR, JOIN, MUTEX, NANOSLEEP, NET REPLY, NET SEND, RECEIVE, REPLY, SEM, SEND,
SIGSUSPEND, SIGWAITINFO, STACK, STOPPED, WAITCTX, WAITPAGE, WAITTHREAD)
4.2
schéma d’ordonnancement
1. événement de ré-ordonnancement (1.)
2. choisir un thread (3.)
3. il existe un thread READY plus prioritaire? (o/n = 9./4.)
4. le thread courant est RR? (o/n = 5./8.)
5. le temps est écoulé? (o/n = 6./10.)
6. il existe un thread READY plus prioritaire? (o/n = 7./8.)
7. le thread courant est placé en fin de file d’exécution (8.)
8. attendre un événement de ré-ordonnancement (1.)
9. changer de thread (1.)
10. le compteur de temps unitaire est réinitialisé (8.)
4.3
Concurence
Gestion de la concurence entre threads :
— le noyau à une priorité permanente absolue (0 en valeur)
— priorité (1 à 63)
— temps d’attente
gestion explicite avec les mutex :
— pthread mutex lock
— pthread mutex unlock
8
Gestion d’une ressource commune à 2 threads avec les sémaphores :
— Sémaphore initialisé à 1
— Réserver la ressource = décrémenter (sem wait)
— Libérer la ressource = incrémenter (sem post)
5
Gestion des processus
5.1
Processus versus thread
Pourquoi pas 1 processus avec n threads ?
— Modularité
— Maintenabilité
— Sureté
Pourquoi pas n processus avec 1 threads ?
— Gain en temps
— Gain en espace
— Fonctionnalités supplémentaires
5.2
Appels systèmes
Pour démarrer un processus dans un programme :
— system()
— fork()
— vfork()
— Famille des exec
— Famille des spawn
leur utilisation dépends de la portabilité et des fonctionnalités recherchées.
Dans un shell : a.out; a.out&; nice a.out;
5.2.1
system
int system(const char* cmd)
— /bin/sh -c cmd
— pendant cmd :
— SIGCHLD est bloqué
— SIGINT et SIGQUIT sont ignorés
— retourne 127 si échec de /bin/sh
— retourne −1 si erreur de cmd
— retourne la valeur de retour de cmd
5.2.2
fork
pid t fork(void)
— création d’une copie du processus courant avec
— avec nouveau pid
9
— avec nouveaux descripteurs de fichier
— avec nouveaux flux de repertoire
— avec nouveaux timers d’exécution
— sans conservation des verroux
— sans conservation des signaux
— sans conservation des alarmes
— retourne 0 dans processus-fils
— retourne pid-fils dans processus-père
— retourne −1 en cas d’échec
5.2.3
vfork
pid t vfork(void)
— Suspend le père jusqu’à :
— suspension du fils par exécution d’un autre processus
— terminaison du fils
— Père et fils partagent le même espace d’adressage
5.2.4
les familles exec et spawn
différences entre ces deux familles :
— exec
— insertion dans la séquence d’instructions courante
— sans changement de pid
— spawn
— création d’une nouvelle séquence d’instructions
— nouveau processus, nouveau pid
— POSIX :
— spawn()
— execl(), execle(), execlp(), execlpe(), execp(), execv(), execve(),
execvp()
— non POSIX :
— spawnl(), spawnle(), spawnlp(), spawnlpe(), spwanp(), spwanv(),
spwanve(), spwanvp(), spwanvpe()
— execvpe()
— signification des suffixes :
— l, spécifie une liste de paramètres
— e, spécifie un environement
— p, spécifie un chemin
— v, spécifie un vecteur de paramètres
— interêt du chemin :
$ls -al /bin/gzip
-rwxr-xr-x 1 root root 47624 Jul 30 18:13 /bin/gzip
$ls -al /bin/gunzip
lrwxrwxrwx 1 root root 4 Aug 18 17:11 /bin/gunzip -> gzip
$ls -al /bin/zcat
10
Figure 1 – relation entre les fonctions de la famille spawn POSIX et non-POSIX (sur un
schéma cependant proche de POSIX)
lrwxrwxrwx
1 root root 4 Aug 18 17:11 /bin/zcat -> gzip
appel système spawn :
#include <spawn.h>
pid_t spawn(const char* path,
int fd_count, const int fd_map[],
const struct inheritance* inherit,
char* const argv[], char* const envp[]);
— path : chemin de l’exécutable
— fd count : taille de fd map
— fd map : descripteurs de fichiers hérités (fd count= 0 implique tous seront hérités)
— inherit : spécification d’héritage du parent
— argv : vecteur d’arguments
— envp : vecteur de chaı̂nes de caractères définissant des variables d’environnement
ajout d’un mode pour toutes les fonctions de la famille de spawn (excepté spawn
elle-même) :
— P WAIT : bloque le père pendant l’exécution du fils
— P NOWAIT : exécution concurrente du père et du fils
— P NOWAITO : exécution concurrente et attente du fils avant exit
11
— P OVERLAY : exécution d’un appel de la famille exec
suffixes ’l’ ou ’v’ :
#include <process.h>
int execl(const char* path, const char* arg0, const char* arg1,
... ,
const char* argn, NULL);
int execv(const char* path, char* const argv[]);
int spawnl(int mode, const char* path, const char* arg0, const char* arg1,
..., const char* argn, NULL);
int spawnv(int mode, const char* path, char* const argv[]);
exemple :
#include <stddef.h>
#include <process.h>
char* listMp0[] = {"/home/n/mp0", "AV1", "AV2", NULL};
/* substitue le processus courant en /home/n/mp0 */
ret = execl("/home/n/mp0", "/home/n/mp0", "AV1", "AV2", NULL);
ret = execv(listMp0[0], listMp0);
/* lance /home/n/mp0 tout en continuant son execution */
ret = spawnl(P_NOWAIT, "/home/n/mp0", "/home/n/mp0", "AV1", "AV2", NULL);
ret = spawnv(P_NOWAIT, listMp0[0], listMp0);
suffixes ’p’ avec ’l’ ou ’v’ :
#include <process.h>
int execlp(const char* file, const char* arg0, const char* arg1,
... ,
const char* argn, NULL);
int execvp(const char* file, char* const argv[]);
int spawnlp(int mode, const char* file,
const char* arg0, const char* arg1, ...,
const char * argn, NULL);
int spawnvp(int mode, const char* file, char* const argv[]);
12
exemple :
#include <stddef.h>
#include <process.h>
char* listMp0[] = {"mp0", "AV1", "AV2", NULL};
/* substitue le processus courant en mp0 en recherchant mp0 dans le PATH */
ret = execlp("mp0", "mp0", "AV1", "AV2", NULL);
ret = execvp(listMp0[0], listMp0);
/* lance mp0 tout en continuant son execution en recherchant mp0 dans le PATH */
ret = spawnlp(P_NOWAIT, "mp0", "mp0", "AV1", "AV2", NULL);
ret = spawnvp(P_NOWAIT, listMp0[0], listMp0);
suffixes ’e’ avec ’l’ ou ’lp’ ou ’v’ ou ’vp’ :
(prototypes et exemples se limitent au suffixe ’le’ ; les autres peuvent s’obtenir
par déclinaison de ’le’)
#include <process.h>
int execle(const char* path, const char* arg0, const char* arg1,
... ,
const char* argn, NULL, const char* envp[]);
int spawnle(int mode, const char* path,
const char* arg0, const char* arg1,
...,
const char* argn, NULL, const char* envp[]);
exemple :
#include <stddef.h>
#include <process.h>
char *envMp0[] = {"PATH=.:/bin:/usr/bin:/opt/bin",
"LD_LIBRARY_PATH=.:/lib:/usr/lib:/opt/lib",
NULL};
ret = execle("mp0", "mp0", "AV1", "AV2", NULL, envMp0);
ret = spawnle(P_WAIT, "mp0", "mp0", "AV1", "AV2", NULL, envMp0);
6
Gestion des threads
— POSIX : ne pas combiner thread et fork
— Neutrino : pthread atfork() (mais incompatible avec les mutex)
13
— pthread t et pthread attr t
/* supportThreadPosix
* programme testant les possibilites d’ordonnancement des threads posix
* n 2006 */
#define _POSIX_SOURCE
#include <unistd.h>
#include <stdio.h>
int main(void) {
#if _POSIX_VERSION < 199506L
printf("sans thread posix\n");
#else
printf("avec thread posix\n");
#ifdef _POSIX_PTHREAD_PRIORITY_SCHEDULING
printf("avec ordonnancement des threads\n");
#else
printf("sans ordonnancement des threads\n");
#endif /* _POSIX_PTHREAD_PRIORITY_SCHEDULING */
#endif /* _POSIX_VERSION < 199506L */
return 0;
}
6.1
Création d’un thread
#include <pthread.h>
int pthread_create(pthread_t* thread, const pthread_attr_t* attr,
void* (*start_routine)(void* ), void* arg );
avec :
— thread : identifiant préalablement alloué
— attr : attributs du thread créé
typedef struct {
int
flags;
size_t
stacksize;
void
*stackaddr;
void
(*exitfunc)(void* status);
int
policy;
struct sched_param param;
unsigned
guardsize;
} pthread_attr_t;
— flags : booléen indiquant exécution detached ou joinable (i.e. exécution
respectivement sans ou avec rendez-vous)
— exitfunc : fonction exécutée à la fin de la séquence d’instructions
— policy : SCHED FIFO, SCHED RR, SCHED OTHER
14
— start routine : fonction définissant la séquence d’instructions associée
— arg : arguments de start routine
6.2
Gestion des threads
pthread_t pthread_self(void);
void pthread_exit(void* value_ptr);
int pthread_join(pthread_t thread, void** value_ptr);
int pthread_detach(pthread_t thread);
int pthread_abort(pthread_t thread);
int pthread_kill(pthread_t thread, int sig);
int pthread_equal(pthread_t t1, pthread_t t2);
Création d’un thread détaché
/* creation d’un pthread detache
* la fin du main met fin a tous les threads
* ici funcPth0 devrait finir avant le main (donc pas de probleme)
* n 2006 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void* funcPth0(void* _av) {
pthread_t id = pthread_self();
printf("thread %d\n", *(int*)&id);
return 0;
}
int main(void) {
pthread_t pth;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&pth, &attr, &funcPth0, NULL);
sleep(3);
return 0;
}
15
Création d’un thread attendu
/* creation d’un pthread attendu
* le main attend la fin du pthread
* n 2006 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
/* thread recevant une valeur v
* et retournant v+1 apres 3 secondes */
void* funcPth0(void* _av) {
int v = *(int*)_av;
pthread_t id = pthread_self();
printf("thread %d\n", *(int*)&id);
v++;
return (*(void**)&v);
}
int main(void) {
int v = 3; /* valeur envoyee */
void* ret; /* valeur recue */
pthread_t pth;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&pth, &attr, &funcPth0, &v);
pthread_join(pth, &ret);
printf("envoyee:%d recue:%d\n", v, *(int*)&ret);
return 0;
}
7
Création de plusieurs threads
Discussion : thread principal et thread secondaire
Création de 10 threads secondaires
Discussion : comprendre les ID
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
16
void* f(void* _p) {
printf("thread %d\n", (int)pthread_self());
}
int main() {
printf("thread PRINCIPAL %d\n", (int)pthread_self());
f(0);
pthread_t ID[10];
pthread_attr_t ATTR;
pthread_attr_init(&ATTR);
int i;
for(i = 0; i < 10; i++) {
pthread_create(&ID[i], &ATTR, f, 0);
}
return 0;
}
Rendez-vous et valeur partagée
Discussion : comprendre les rendez-vous et le partage de l’adressage globale
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int v;
void* f(void* _p) {
v++;
//printf("thread %d\n", (int)pthread_self());
}
int main() {
pthread_t ID;
pthread_attr_t ATTR;
pthread_attr_init(&ATTR);
v = 0;
pthread_create(&ID, &ATTR, f, 0);
if(1) {
pthread_join(ID, 0);
17
printf("v %d\n", v);
} else {
printf("v %d\n", v);
pthread_join(ID, 0);
}
return 0;
}
Paramètres et valeurs de retour
Discussion : comprendre paramètres et retour
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void* f(void* _p) {
int a = *(int*)_p;
int b = *(int*)(_p+sizeof(int));
printf("thread %d %d %d\n", (int)pthread_self(), a, b);
a = a+1;
int t[2];
t[0] = 32;
t[1] = 33;
return t;
}
int main() {
pthread_t ID;
pthread_attr_t ATTR;
pthread_attr_init(&ATTR);
int v[2];
v[0] = 1;
v[1] = 2;
pthread_create(&ID, &ATTR, f, v);
int ret_a[2];
pthread_join(ID, (void**)&ret_a);
18
printf("ret_a %d %d\n", ret_a[0], ret_a[1]);
return 0;
}
Synchronisation par valeur
Discussion : perte de cycles
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include "semaphore.h"
// le nombre de caracteres A et B imprimes est INFERIEUR a 100
char turn;
void* f0(void* _p) {
int i;
for(i = 0; i < 1000; i++) {
if(turn == ’A’) {
printf("A");
turn = ’B’;
}
}
}
void* f1(void* _p) {
int i;
for(i = 0; i < 1000; i++) {
if(turn == ’B’) {
printf("B");
turn = ’A’;
}
}
}
int main() {
pthread_t ID[2];
pthread_attr_t ATTR;
pthread_attr_init(&ATTR);
turn = ’A’;
pthread_create(&ID[0], &ATTR, f0, 0);
19
pthread_create(&ID[1], &ATTR, f1, 0);
pthread_join(ID[0], 0);
pthread_join(ID[1], 0);
return 0;
}
Synchronisation par semaphore
Discussion : aucune perte de cycle
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include "semaphore.h"
sem_t turnA;
sem_t turnB;
void* f0(void* _p) {
int i;
for(i = 0; i < 1000; i++) {
sem_wait(&turnA);
printf("A");
sem_post(&turnB);
}
}
void* f1(void* _p) {
int i;
for(i = 0; i < 1000; i++) {
sem_wait(&turnB);
printf("B");
sem_post(&turnA);
}
}
int main() {
pthread_t ID[2];
pthread_attr_t ATTR;
pthread_attr_init(&ATTR);
sem_init(&turnA, 0, 0);
sem_init(&turnB, 0, 0);
sem_post(&turnA);
20
pthread_create(&ID[0], &ATTR, f0, 0);
pthread_create(&ID[1], &ATTR, f1, 0);
pthread_join(ID[0], 0);
pthread_join(ID[1], 0);
return 0;
}
8
8.1
Synchronisation de threads
Avec une valeur
/* threadEtAltern.c
* gcc -pthread threadEtAltern.c -o threadEtAltern
* (-lpthread ou -D_REENTRANT selon arch)
*
* exemple A NE PAS SUIVRE !!!
* synchronisation tres maladroite entre deux threads
*
* deux thread avec des while(1)
* executer une boucle dans chaque while par alternance
* pour verifier cette alternance
* l’un printf, et l’autre lit la valeur precendente +1
* si NON, stop et affiche altern echec
* auncun semaphore ni mutex
*
* n 2004
*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
/* code pour linux par defaut */
#ifndef IRIX
#define LINUX
#endif /* IRIX */
struct Donnees {
int nombre;
int niter;
};
/* un nombre */
/* nombre d’iterations */
int valeurDeSynchro;
21
#define TOUR_ECRI 0
#define TOUR_VERI 1
/* ecrire dans _data avec un mutex */
void *Ecri(void* _data) {
int envie = 1;
struct Donnees *d = _data;
while(envie) {
if(valeurDeSynchro == TOUR_ECRI) {
d->nombre +=1;
if(d->niter == -1) envie = 0;
valeurDeSynchro = TOUR_VERI;
} else usleep(1);
}
return NULL;
}
/* verification des donnees ecrites */
void *Veri(void* _data) {
int envie = 1;
int verif;
struct Donnees *d = _data;
verif = d->nombre;
while(envie) {
if(valeurDeSynchro == TOUR_VERI) {
if(verif != d->nombre) {
printf("echec de synchro sur lock unlock entre thread boucle %d\n", verif);
d->niter = -1;
envie = 0;
} else verif++;
if(d->nombre == d->niter) {
d->niter = -1;
envie = 0;
}
if(d->niter == -1) {
d->niter = -1;
envie = 0;
}
if(d->nombre %1000 == 0) printf("boucle %d\n", d->nombre);
valeurDeSynchro = TOUR_ECRI;
} else usleep(1);
}
return NULL;
}
22
int main(void) {
pthread_t t1, t2;
struct Donnees d2;
d2.nombre = 0;
d2.niter = 10000;
valeurDeSynchro = TOUR_ECRI;
pthread_create(&t1, NULL, Ecri, (void *) &d2);
usleep(500); /* attendre l’init de Ecri */
pthread_create(&t2, NULL, Veri, (void *) &d2);
#ifdef LINUX
pthread_join(t1, NULL);
pthread_join(t2, NULL);
#else
#ifdef IRIX
while(1) {
sleep(1);
printf("main\n");
if(d2.niter == -1) break;
}
#endif /* IRIX */
#endif /* LINUX */
return 0;
}
8.2
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Avec mutex
threadEtAltern_1.c
gcc -pthread threadEtAltern_1.c -o threadEtAltern_1
(-lpthread ou -D_REENTRANT selon arch)
deux thread avec des while(1)
executer une boucle dans chaque while par alternance
pour verifier cette alternance
l’un printf, et l’autre lit la valeur precendente +1
si NON, stop et affiche altern echec
synchro avec mutex
l’alternance des thread Ecri et Veri n’est pas garantie
sur linux, le test d’alternance passe souvent
sur irix, beaucoup moins
un exemple qui ne fonctionne pas sur FreeBSD
23
* n 2004
*/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
/* code pour linux par defaut */
#ifndef IRIX
#define LINUX
#endif /* IRIX */
typedef struct
struct donnees
volatile int
volatile int
};
donnees donnees;
{
nombre;
/* un nombre */
niter;
/* nombre d’iterations */
pthread_mutex_t excluA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t excluB = PTHREAD_MUTEX_INITIALIZER;
/* ecrire dans _data avec un mutex */
void *Ecri(void* _data) {
int envie = 1;
donnees *d = _data;
while(envie) {
if(pthread_mutex_lock(&excluA) == 0) {
d->nombre +=1;
if(d->niter == -1) envie = 0;
printf("a");
pthread_mutex_unlock(&excluB);
}
usleep(1); /* augmente la commutation */
}
return NULL;
}
/* verification des donnees ecrites */
void *Veri(void* _data) {
int envie = 1;
int verif;
donnees *d = _data;
verif = d->nombre;
while(envie) {
if(pthread_mutex_lock(&excluB) == 0) {
if(verif != d->nombre) {
24
printf("echec de synchro sur lock unlock entre thread boucle %d\n", verif);
d->niter = -1;
envie = 0;
} else verif++;
if(d->nombre == d->niter) {
d->niter = -1;
envie = 0;
}
if(d->niter == -1) {
d->niter = -1;
envie = 0;
}
printf("b");
if(d->nombre %1000 == 0) printf("boucle %d\n", d->nombre);
pthread_mutex_unlock(&excluA);
}
usleep(1); /* augmente la commutation */
}
return NULL;
}
int main(void) {
pthread_t t1, t2;
volatile donnees d2; /* donnees partagees */
setbuf(stdout, 0);
d2.nombre = 0;
d2.niter = 10000;
pthread_create(&t1, NULL, Ecri, (void *) &d2);
usleep(1); /* attendre l’init de Ecri */
pthread_create(&t2, NULL, Veri, (void *) &d2);
#ifdef LINUX
pthread_join(t1, NULL);
pthread_join(t2, NULL);
#else
#ifdef IRIX
while(1) {
if(pthread_mutex_trylock(&excluB ) &&
pthread_mutex_trylock(&excluA)
) {
sleep(1);
printf("main\n");
if(d2.niter == -1) break;
}
25
}
#endif /* IRIX */
#endif /* LINUX */
pthread_mutex_destroy(&excluA);
pthread_mutex_destroy(&excluB);
return 0;
}
8.3
Avec sémaphore et mutex
/* threadEtAltern_2.c
* gcc -pthread threadEtAltern_2.c -o threadEtAltern_2
* (-lpthread ou -D_REENTRANT selon arch)
*
* a contrario de threadEtAltern.c
* exemple A SUIVRE
*
* deux thread avec des while(1)
* executer une boucle dans chaque while par alternance
* pour verifier cette alternance
* l’un printf, et l’autre lit la valeur precendente +1
* si NON, stop et affiche altern echec
*
* alternance par post et wait de semaphore
* serait-ce du barrierage?
*
* n 2004
*/
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
/* code pour linux par defaut */
#ifndef IRIX
#define LINUX
#endif /* IRIX */
struct Donnees {
int nombre;
int niter;
};
/* un nombre */
/* nombre d’iterations */
pthread_mutex_t mute; /* protection de Donnees */
sem_t semEcri;
/* semaphore sur Ecri */
26
sem_t semVeri;
/* semaphore sur Veri */
/* ecrire dans _data avec mutex et semaphore
* alternance par post et wait
* exclusion par mutex */
void *Ecri(void* _data) {
int envie = 1;
struct Donnees *d = _data;
while(envie) {
sem_wait(&semEcri);
pthread_mutex_lock(&mute);
d->nombre +=1;
if(d->niter == -1) envie = 0;
pthread_mutex_unlock(&mute);
sem_post(&semVeri); /* valide Veri pour un tour */
}
return NULL;
}
/* verification des donnees ecrites */
void *Veri(void* _data) {
int envie = 1;
int verif;
struct Donnees *d = _data;
verif = d->nombre;
while(envie) {
sem_wait(&semVeri);
pthread_mutex_lock(&mute);
if(verif != d->nombre) {
printf("echec de synchro sur lock unlock entre thread boucle %d\n", verif);
d->niter = -1;
envie = 0;
} else verif++;
if(d->nombre == d->niter) {
d->niter = -1;
envie = 0;
}
if(d->niter == -1) {
d->niter = -1;
envie = 0;
}
if(d->nombre %1000 == 0) printf("boucle %d\n", d->nombre);
pthread_mutex_unlock(&mute);
sem_post(&semEcri); /* valide Ecri pour un tour */
27
}
return NULL;
}
int main(void) {
pthread_t t1, t2;
struct Donnees d2;
d2.nombre = 0;
d2.niter = 10000;
pthread_mutex_init(&mute, NULL);
sem_init(&semEcri, 0, 0);
sem_init(&semVeri, 0, 1); /* commence par Veri */
pthread_create(&t1, NULL, Ecri, (void *) &d2);
usleep(500); /* attendre l’init de Ecri */
pthread_create(&t2, NULL, Veri, (void *) &d2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mute);
sem_destroy(&semEcri);
sem_destroy(&semVeri);
return 0;
}
8.4
Avec barrière
/* exemple de barrier entre 2 threads+main
* soit un total de 3 threads
*
* n 2004
*/
#define _POSIX_C_SOURCE 200112L /* posix 1003.1-2004 */
#define _QNX_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
pthread_barrier_t barriere;
#define TPS_SLEEP_1 1
28
#define TPS_SLEEP_2 2
/* deux fonction de thread th_1 et th_2 avec des tempos differentes */
void *th_1(void* _av) {
printf("th_1 ok\n");
sleep(TPS_SLEEP_1);
pthread_barrier_wait(&barriere);
printf("th_1 barrier ok\n");
return 0;
}
void *th_2(void* _av) {
printf("th_2 ok\n");
sleep(TPS_SLEEP_2);
pthread_barrier_wait(&barriere);
printf("th_2 barrier ok\n");
return 0;
}
int main(void) {
pthread_t th1, th2;
time_t tps;
/* creer une barriere pour 3 threads */
pthread_barrier_init(&barriere, NULL, 3);
/* lancer 2 threads */
if(pthread_create(&th1, 0, th_1, 0) < 0) {
fprintf(stderr, "pthread_create echec\n");
exit(1);
}
if(pthread_create(&th2, 0, th_2, 0) < 0) {
fprintf(stderr, "pthread_create echec\n");
exit (1);
}
/* se placer devant la barriere */
time(&tps);
printf("main attends: %s", ctime(&tps));
pthread_barrier_wait(&barriere);
time(&tps);
printf("main barriere ok: %s", ctime(&tps));
pthread_barrier_destroy(&barriere);
return 0;
}
29