Introduction à RT-POSIX et systèmes temps-réels
Introduction à RT-POSIX et systèmes temps-réels
RT-‐POSIX
E0enne
Borde,
Bertrand
Dupouy
Pourquoi
POSIX?
• Qu’est-‐ce
que
POSIX?
– Un
standard
IEEE
– Une
API,
et
donc
une
librairie
– Fournis
par
le
Système
d’Exploita0on
(SE)
– Ges0on
des
processus
et
threads
(processus
légers)
• Alors,
pourquoi
POSIX?
– Portabilité:
Portable
Opera0ng
System
Interface
– Une
applica0on
u0lisant
l’API
POSIX
pourra
être
exécutée
sur
n’importe
quel
SE
qui
implémente
POSIX
(Linux,
…
mais
aussi
…
INTEGRITY,
LynxOS,
RTEMS,
VxWorks,
…)
Pourquoi
RT-‐POSIX?
• De
plus
en
plus
d’applica0ons
temps-‐réels
• SE
classique
non-‐adaptés
à
un
ges0on
deterministe
du
temps
• Une
des
raison
de
sa
popularité
est
d’être
u0lisable
sur
une
machine
Linux,
puis
portable
sur
des
OS
propriétaires
• Note1
:
ces
exemples
introduisent
une
no0on
de
cri2cité
et
de
précision
des
contraintes
temporelles
• Note2
:
POSIX=interface
de
programma0on
seulement,
donc
sa
capacité
à
être
u0lisé
dans
un
domaine
d’applica0on
dépend
de
l’implémenta)on
de
cege
API
et
de
la
façon
dont
elle
est
u)lisée.
Synthèse
des
différences
SE/STR
SE
– Les
performances
sont
jugées
suivant
le
rendement
:
exécuter
le
plus
de
tâches
possibles,
le
plus
rapidement
possible,
C'est
le
SE
qui
décide
de
la
dynamique
d'exécu2on
(CONTRAINTES
LOGICIELLES)
STR
– Le
critère
de
performance
est
le
suivant
:
respect
de
toutes
ou
d'une
par2e
(en
cas
de
surcharge)
des
échéances,
qu'elles
soient
périodiques
ou
non.
Si
on
exige
le
respect
de
toutes
les
échéances,
on
parle
de
TR
dur,
sinon
TR
souple
(mou,
son).
C'est
l'environnement
extérieur
qui
impose
la
dynamique
d’exécu2on
(CONTRAINTES
PHYSIQUES)
Le
temps
dans
les
STR
• Real-‐0me
compu0ng
is
not
fast
compu0ng:
FAUX
VRAI
• Ex:
Airbag…
Tâche
urgente
et
tâche
cri0que
• Chaque
tâche
a
un
degré
:
– d'urgence,
lié
à
la
date
de
son
échéance;
– de
cri0cité,
lié
à
son
importance
rela0ve.
• Mais
:
une
tâche
très
cri0que
peu
avoir
de
faibles
contraintes
de
temps
et
une
tâche
peu
cri0que
de
fortes
contraintes
de
temps
…
è
Pb.
des
ordonnancements
du
type
RMS
où
le
seul
critère
est
la
période
des
tâches
ou
EDF
où
le
seul
critère
est
la
deadline
des
tâches.
• et
encore
:
– format
de
l’exécutable
(édi0on
de
liens
sta0que/dynamique)
– protocole
réseau
pour
les
SE
répar0s
– la
mesure
doit-‐elle
se
faire
dans
la
configura0on
la
plus
défavorable
(worst
case
execu0on
0me,
WCET)
?
Temps-‐réel,
ges0on
de
la
mémoire,
et
codage
• Ges0on
sta0que
:
– le
nombre,
la
taille
et
l’emplacement
des
objets
sont
connus,
ou
bornés,
lorsque
l’applica0on
est
lancée,
– avantage
:
accès
aux
objets
en
temps
constant
et
faible
(objets
implantés
sous
forme
de
tableaux)
– inconvénient
:
pas
flexible
• Ges0on
dynamique
:
– avantage
:
souplesse
– inconvénients
:
• temps
d’accès
et
d’alloca0on
difficile
à
prédire
ou
à
borner
• déterminisme
de
la
désalloca0on
• Pra$que
des
systèmes
fortements
cri$ques:
pas
d’alloca$on
dynamique
de
mémoire.
Temps-‐réel,
ges0on
de
la
mémoire,
et
pagina0on
• Temps
d’accès
aux
informa0ons
difficile
à
prédire
• Ce
temps
d’accès
peut
être
très
long
:
accès
disques
possibles
• la
pagina0on
est
donc
peu
u0lisée
en
TR,
ou
bien
avec
des
mécanismes
de
verrouillage
des
pages
en
mémoire
pour
les
applica0ons
cri0ques
Temps
réels
et
mémoires
caches
• Avantage
de
l’u0lisa0on
de
caches
:
– diminue
le
temps
d’exécu0on
des
tâches
de
manière
probabiliste
(cf.
hit
ra0o)
• Inconvénients
:
– augmenta0on
du
temps
de
changement
de
contexte
(réini0alisa0on
des
caches)
si
les
espaces
d’adressage
sont
séparés,
d’où
u0lisa0on
de
threads
dans
les
STR
– moins
de
prédic0bilité,
il
existe
diverses
méthodes
d’es0ma0on
du
comportement
des
caches
Partage
de
l’espace
d’adressage
• Les
threads
peuvent
aussi
être
détachés
(et
ne
plus
être
“joinable”):
– int pthread_detach(pthread_t thread);!
– Les
threads
détachés
con0nuent
leur
exécu0on
après
la
fin
du
main
si
le
main
termine
en
appelant
pthread_exit()
plutôt
que
exit
ou
return.
Exemple
de
code
#include <pthread.h>!
void *thread(void *data) { !
int i; !
for (i = 0; i < 100; i++) {!
printf(« Hello world from thread »);!
}!
} !
int main(void) { !
pthread_t th;!
pthread_create(& th, NULL, thread, NULL); !
pthread_join(th, NULL);!
} !
Affichage:
Hello
world
from
thread
Hello
world
from
thread
Hello
world
from
thread
Hello
world
from
thread
Hello
world
from
thread
Hello
world
from
thread
Hello
world
from
thread
etc….
Annula0on
d’un
thread
(par
l’exemple)
#include <pthread.h> !
void *thread(void *data) { !
while(1) { !
printf(« Hello world from thread »); !
} !
} !
int main(void) { !
pthread_t th;!
pthread_create(& th, NULL, thread, NULL); !
sleep(1); !
pthread_cancel(th); !
pthread_join(th, NULL); !
return 0; !
} !
Peu
u0lisé
dans
un
système
temps-‐réel,
sauf
en
cas
de
“reconfigura0on”
(e.g.
Traitement
d’erreurs,
changement
de
mode).
L’ordonnancement
en
POSIX
• Ordonnancement
préemp0f
à
priorités
fixes
– 32
niveaux
de
priorité
doivent
être
proposés
• les
poli0ques
de
ges0on
des
files
d'agentes
associées
à
ces
priorités
sont
:
FIFO,
RR,
OTHERS…
“Within
priori0es”
• seul
l'u0lisateur
privilégié
(root)
peut
accéder
à
ce
service
d'ordonnancement
pour
choisir
FIFO
ou
OTHERS
• On
peut
aussi
megre
à
jour
la
priorité
d’un
thread
via
ses
agributs
(voir
exemple
ci-‐après)
• Exemple:
int pthread_mutexattr_settype
(pthread_mutexattr_t *attr, int type); !
où
type
peut
prendre
comme
valeur:
PTHREAD_MUTEX_NORMAL,
PTHREAD_MUTEX_ERRORCHECK,
PTHREAD_MUTEX_RECURSIVE,
PTHREAD_MUTEX_DEFAULT
Mutexes
pour
le
TR
• Un
mutex
peut-‐être
configuré
avec
une
poli0que
– Poli0que
«
PCP-‐like
»:
si
un
thread
t1
a
pris
le
verrou
et
bloque
un
thread
t2
plus
prioritaire,
alors
t1
hérite
de
la
priorité
max
associée
au
verrou.
pthread_mutex_t lock1;!
pthread_mutexattr_t mutex_attr;!
pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_PROTECT);!
pthread_mutexattr_setprioceiling(&mutex_attr, max_prio-1);!
pthread_mutex_init(&lock1, &mutex_attr);!
– Poli0que
«
PIP-‐like
»:
si
un
thread
t1
a
pris
le
verrou
et
bloque
un
thread
t2
plus
prioritaire,
alors
t1
hérite
de
la
priorité
de
t1.
pthread_mutex_t lock1;!
pthread_mutexattr_t mutex_attr;!
pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_INHERIT);!
pthread_mutex_init(&lock1, &mutex_attr);!
Variables
condi0onnelles
• Les
variables
condi0onnelles
permegent
de
suspendre
l’exécu0on
d’un
thread
jusqu’à
ce
qu’une
condi0on
devienne
vrai;
cege
condi0on
est
signalée
par
un
autre
thread.
• Ini0alisa0on
– pthread_cond_t
cond;
– pthread_cond_init(&
cond,
NULL);
• Agente:
– pthread_cond_wait(&
cond,
&
mutex)
– Toujours
bloquant
– Le
mutex
passé
en
paramètre
est
sera
libéré
avant
la
mise
en
agente
(de
façon
atomique),
puis
repris
immédiatement
au
réveil
(trylock)
• Signalisa0on:
– A
un
thread
en
agente
(pas
forcément
FIFO):
pthread_cond_signal(&
cond);
– A
tous
les
threads
en
agente:
pthread_cond_broadcast(&
cond);
– Non
mémorisé
(perdu
si
aucun
thread
en
agente)
Example
de
code
/****** variables partagees ******/ !
pthread_mutex_init(&Verrou, NULL); !
pthread_cond_init(&VarCond, NULL); !
// création de threads
...!
...!
while (...){!
pthread_mutex_lock (&Verrou); !
...!
while (N < Compteur) { !
pthread_mutex_lock(&Verrou);!
pthread_cond_wait(&VarCond,!
Compteur ++;!
&Verrou); !
if (Compteur > N)!
}!
pthread_cond_broadcast(&VarCond);!
printf ("Seuil atteint! "\n); !
pthread_mutex_unlock (&Verrou);!
...!
... !
pthread_mutex_unlock (&Verrou);!
}!
!
Vérfifie
qu’un
seuil
est
ageint
Vérfifient
que
le
seuil
soit
ageint
Signaux
• Dans
l’implémenta0on
TR
les
différentes
occurrences
d’un
même
signal
sont
conservées,
le
nombre
de
signaux
reçus
correspond
toujours
au
nombre
de
signaux
émis.
– Pas
de
perte
:
ges0on
d'une
liste
de
signaux
en
agente
– La
priorité
liée
au
signal
est
respectée
dans
la
ges0on
de
la
file
d'agente
•
Emission
par
sigqueue,
par
un
0mer,
ou
par
une
fin
d'e/s
•
Nouveaux
signaux
dans
RT-‐POSIX:
RTSIG_MAX
signaux,
numérotés
de
SIGRTMIN
à
SIGRTMAX
(les
signaux
avec
un
plus
pe0t
numéro
sont
considérés
comme
plus
prioritaires).
La
priorité
rela0ves
des
signaux
RT
et
standards
est
non-‐spécifiée
dans
le
standard.
² Sigaction: modifier
le
traitement
associé
à
un
signal
² sigwaitinfo:
Agendre
un
signal
et
une
info
² sigtimedwait:
Agendre
d’un
signal
avec
temporisa0on
Signaux
(émission)
² kill!
² int pthread_kill(pthread_t thread, int sig);!
² sigqueue:
Megre
un
signal
dans
la
file
d’agente
associée
au
processus
des0nataire
² int sigqueue(pid_t pid, int sig, const union sigval
value);!
² Paramètre
value:
spécifier
une
donnée
associée
au
signal
² Défini2on
de
sigval:
union sigval {!
int sival_int;!
" " void *sival_ptr;!
};!
² int
pthread_sigqueue(pthread_t
thread,
int
sig,
const
union
sigval
value);!
Signaux
(traitement)
² sigaction:!
" int sigaction(int sig, const struct sigaction * act, " "
" "struct sigaction * oldact);!
² Paramètre
act:
spécifie
la
nouvelle
ac0on
associé
au
signal
sig;
paramètre
oldact:
si
non
null,
sert
à
sauvegarder
l’ancienne
ac0on
associé
au
signal
sig.
!
struct sigaction {!
"
clk_id
=
CLOCK_REALTIME/CLOCK_MONOTONIC
Timers
² timer_create:
Créa0on
d’un
0mer
² int
0mer_create(clockid_t
clockid,
struct
sigevent
*sevp,
0mer_t
*
0merid);
² Structure
qui
spécifie
comment
l’appelant
sera
no0fié
de
l’échéance
du
0mer:
le
champ
sigev_no$fy
de
sevp
sert
à
préciser
cela:
² SIGEV_NONE
à
pas
de
no2f
² SIGEV_SIGNAL
à
génère
un
signal
vers
le
processus
appelant;
le
champs
sigev_signo
de
sevp
précise
le
numéro
de
signal;
² timer_delete:
Destruc0on
d’un
0mer
² timer_settime:
Armement/désarmement
d’un
0mer
² timer_gettime:
Lire
le
délai
restant
sur
un
0mer
² timer_getoverrun:
Lire
le
délai
dépassé
sur
un
0mer
Sémaphores
(vues
en
cours
de
UNIX)
Les
sémaphores
sont
l'implanta0on
classique
de
l'ou0l
défini
par
Dijkstra.
La
file
d'agente
est
gérée
par
ordre
de
priorités
décroissantes,
les
sémaphores
peuvent
être
u0lisés
entre
threads
ou
processus,
suivant
les
op0ons.
² sem_open:
open
and
/
or
² sem_getvalue:
get
current
create
a
named
semaphore.
semaphore
count
² sem_close:
close
a
named
² sem_wait:
Try
to
lock
the
semaphore
semaphore.
Wait
otherwise.
² sem_unlink:
destroy
a
named
² sem_trywait:
Just
tries
to
semaphore
lock
the
semaphore,
but
gives
up
² sem_init:
ini0alize
an
if
the
semaphore
is
already
unnamed
semaphore
locked.
² sem_destroy:
destroy
an
² sem_post:
Release
the
unnamed
semaphore
semaphore.
Files
de
messages
Elles
sont
similaires
à
ceux
proposés
par
les
IPC
System
V,
mais
à
chaque
message
est
associée
une
priorité.
Le
problème
de
l'inversion
de
priorité
n'est
pas
géré
:
Que
manque-‐t-‐il?
Ordonnancement
RMS
int main()!
{!
...!
pthread_create(&tid1, &attr1, (void* (*)(void*))body_of_T1, NULL);!
pthread_create(&tid2, &attr2, (void* (*)(void*))body_of_T2, NULL);!
pthread_create(&tid3, &attr3, (void* (*)(void*))body_of_T3, NULL);!
!
// wait for threads to finish (otherwise the process terminates!
// immediately)!
pthread_join(tid1, NULL);!
pthread_join(tid2, NULL);!
pthread_join(tid3, NULL);!
}!
!
void body_of_T1()!
{…}!
void body_of_T2()!
{…}!
void body_of_T3()!
{…}!
Ordonnancement
RMS
void body_of_T1()!
{!
unsigned int iter=0;!
while(1)!
{!
iter++;!
printf("Executing T1 iter %d\n", iter);!
// Compute next dispatch time!
clock_gettime(CLOCK_REALTIME, &T1_timer); !
T1_timer.tv_sec = T1_timer.tv_sec+PERIODT1_s; !
T1_timer.tv_nsec = T1_timer.tv_nsec;!
!
// T1‘s code executed here!
!
// Wait for next dispatch time!
pthread_mutex_lock (&T1_mutex);!
pthread_cond_timedwait (&T1_cond, &T1_mutex, &T1_timer);!
pthread_mutex_unlock (&T1_mutex);!
!
!
Problème?
…
Départ
synchronisé
des
threads