VIII. Exemples d'utilisation▲
Ce chapitre traite d'exemples simples. Le but ici n'est pas de faire un tour exhaustif de l'API mais d'en décrire la philosophie.
Des cas d'utilisation de chaque fonctionnalité de l'API sont disponibles dans le répertoire après décompression xenomai-<version>/ksrc/skins/native/snippets.
VIII-A. Avant d'utiliser les fonctions Xenomai : les environnements d'appels▲
Dans la documentation de l'API Xenomai, chaque fonction est associée à un ou plusieurs environnements d'appel :
- Kernel module initialization/cleanup code : code d'initialisation/suppression des modules Linux (.ko) contenu à l'intérieur des macros module_init() et module_exit() ;
- Interrupt service routine : handler d'interruption ;
- Kernel-based task : tâche Xenomai lancée par rtdm_task_init() ;
- User-space task : tâche Xenomai ou Linux lancée explicitement par l'utilisateur.
En cas de mauvais contexte, les appels système vous répondront un sympathique -EPERM.
Contrôlez toujours le code de retour des appels système, qu'ils soient Xenomai ou Linux.
VIII-B. Les tâches▲
VIII-B-1. Les paramètres de lancement d'une tâche▲
Les tâches peuvent être créées (rt_task_create) puis lancées (rt_task_start), ou bien les deux à la fois (rt_task_spawn).
On retrouve les paramètres classiques pour les tâches d'un OS temps réel (point d'entrée, priorité, round-robin...).
La taille de la pile par défaut qui est dite « raisonnable » (4 Ko sur x86). Si celle-ci n'est pas suffisante, on obtient immédiatement un segmentation fault :
[root@localhost tuto]# ./stack_too_small
Segmentation fault
[root@localhost tuto]#
Pour mode, affinity sélectionne le processeur recevant la tâche.
VIII-B-2. Tâches périodiques▲
Les tâches périodiques sont créées comme les autres tâches, il n'y a pas de flags spécifiques mais seulement deux appels de fonction :
- rt_task_set_periodic afin d'indiquer à la tâche sa période ;
- rt_task_wait_period pour descheduler la tâche en attendant le prochain cycle.
L'exemple ci-dessous inclut la détection de l'overrun de la tâche périodique, c'est-à-dire le nombre de fois où le réveil de la tâche a échoué car elle était encore en cours d'exécution.
#include <stdio.h>
#include <assert.h>
#include <sys/mman.h>
#include <native/task.h>
#include <native/timer.h>
#define ONE_SECOND 1000000000
static
void
task
(
void
*
arg)
{
/* Print a message each second */
unsigned
int
cnt=
0
;
unsigned
long
int
ov=
0
;
volatile
unsigned
int
i=
0
;
unsigned
long
long
int
start =
0
;
RT_TIMER_INFO timer_info;
if
(
rt_task_set_periodic
(
NULL
, /* the current task */
TM_NOW, /* delay before release, TM_NOW = none */
ONE_SECOND /* we don't use a periodic timer, so this value is in nanosec */
)!=
0
)
{
printf
(
"
rt_task_set_periodic error
\n
"
);
return
;
}
(
void
)rt_timer_inquire
(&
timer_info);
start =
timer_info.date/
ONE_SECOND;
while
(
1
)
{
/* Go in secondary mode */
(
void
)rt_timer_inquire
(&
timer_info);
printf
(
"
Hello world #%u, date: %llu s
\n
"
,cnt,timer_info.date/
ONE_SECOND -
start);
++
cnt;
if
(
cnt==
3
)
/* Big computation for step 3 */
for
(
i=
0
;i<
1000000000
;++
i);
ov =
0
;
/* Go back in primary mode because we do a Xenomai system call */
switch
(
rt_task_wait_period
(
&
ov ) )
{
case
0
:
case
-
ETIMEDOUT:
if
(
ov)
printf
(
"
Overrun: %lu cycles lost
\n
"
,ov); // secondary mode
break
;
default
:
printf
(
"
rt_task_wait_period error
\n
"
); // secondary mode
return
;
}
}
}
int
main (
void
)
{
RT_TASK task_desc;
int
n;
mlockall
(
MCL_CURRENT |
MCL_FUTURE );
if
((
n=
rt_task_spawn
(
&
task_desc, "
my task
"
, 0
, 99
, T_JOINABLE, &
task, NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
rt_task_join
(&
task_desc);
return
0
;
}
VIII-B-3. Ce qui donne les traces suivantes :▲
[root@localhost tuto]# ./periodic
Hello world #0, date: 0 s
Hello world #1, date: 1 s
Hello world #2, date: 2 s
Overrun: 2
cycles lost
Hello world #3, date: 5 s
Hello world #4, date: 6 s
Hello world #5, date: 7 s
Hello world #6, date: 8 s
Hello world #7, date: 9 s
Hello world #8, date: 10 s
Hello world #9, date: 11 s
Hello world #10, date: 12 s
La fonction rt_task_set_period mérite un peu d'attention. Son troisième argument est la période, son unité dépend du type de timer choisi lors de la compilation du noyau (cf. Enable periodic timing).
Dans cet exemple, le basculement en secondary mode n'est pas gênant pour afficher un message d'erreur.
En effet, on considère l'application comme « perdue » et on ne tient plus compte des contraintes temporelles.
VIII-B-4. Transformation d'un processus Linux en tâche Xenomai▲
Prenons l'exemple suivant :
#include <stdio.h>
#include <sys/mman.h>
#include <native/task.h>
#include <native/sem.h>
static
RT_SEM sem;
static
void
task
(
void
*
arg)
{
int
n=
0
;
volatile
int
i =
0
;
/* computation */
for
(
i=
0
;i<
10000
;++
i);
if
((
n=
rt_sem_v
(&
sem))!=
0
)
printf
(
"
rt_sem_v error %d
\n
"
,n);
}
int
main (
void
)
{
RT_TASK task_desc;
int
n=
0
;
mlockall
(
MCL_CURRENT |
MCL_FUTURE );
if
(
rt_sem_create
(
&
sem, "
my sem
"
, 0
, S_FIFO ) !=
0
)
{
printf
(
"
rt_sem_create error
\n
"
);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc,"
my task
"
,0
,99
,0
,&
task,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
/* wait for "my task" termination (like rt_task_join) */
if
((
n=
rt_sem_p
(&
sem,TM_INFINITE))!=
0
)
{
printf
(
"
rt_sem_p error %d
\n
"
,n);
return
1
;
}
return
0
;
}
Notre application se termine lorsque le sémaphore sem est relâché. Or, lors de l'exécution, nous obtenons le message :
[root@localhost tuto]# ./error_context
rt_sem_p error -1
[root@localhost tuto]#
-1 correspond au code -EPERM(36). L'opération est interdite car on ne se trouve pas dans le bon contexte, il faudrait être ici dans une tâche Xenomai. Pour cela, on va utiliser la fonction rt_task_shadow.
...
if
((
n=
rt_task_spawn
(
&
task_desc,"
my task
"
,0
,99
,0
,&
task,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
if
(
rt_task_shadow
(
&
task_main, "
main
"
, 99
, 0
) !=
0
)
{
printf
(
"
rt_task_shadow error
\n
"
);
return
1
;
}
/* wait for "my task" termination (like rt_task_join) */
if
((
n=
rt_sem_p
(&
sem,TM_INFINITE))!=
0
)
{
printf
(
"
rt_sem_p error %d
\n
"
,n);
return
1
;
}
...
- terminer des tâches Xenomai, en appelant depuis la nouvelle tâche des fonctions de destruction de sémaphores, files de messages...
- simplifier l'architecture dynamique en réutilisant la tâche « principale » main.
VIII-B-5. Affectation tâche/processeur (affinity )▲
Cet exemple monte l'utilisation de l'option T_CPU(n) dans le lancement des tâches.
#include <stdio.h>
#include <sys/mman.h>
#include <native/task.h>
#define TEN_SECONDS (10*1e9)
static
void
task
(
void
*
arg)
{
rt_task_sleep
(
TEN_SECONDS );
}
int
main (
void
)
{
RT_TASK task_desc0,task_desc1;
/* disable memory swap */
mlockall
(
MCL_CURRENT |
MCL_FUTURE );
if
(
rt_task_spawn
(
&
task_desc0, /* task descriptor */
"
my task 0
"
, /* name */
0
/* 0 = default stack size */
,
99
/* priority */
,
T_JOINABLE |
T_CPU
(
0
), /* needed to call rt_task_join after */
&
task, /* entry point (function pointer) */
NULL
/* function argument */
)!=
0
)
{
printf
(
"
rt_task_spawn error
\n
"
);
return
1
;
}
if
(
rt_task_spawn
(
&
task_desc1, /* task descriptor */
"
my task 1
"
, /* name */
0
/* 0 = default stack size */
,
99
/* priority */
,
T_JOINABLE |
T_CPU
(
1
), /* needed to call rt_task_join after */
&
task, /* entry point (function pointer) */
NULL
/* function argument */
)!=
0
)
{
printf
(
"
rt_task_spawn error
\n
"
);
return
1
;
}
/* wait for task function termination */
rt_task_join
(&
task_desc0);
rt_task_join
(&
task_desc1);
return
0
;
}
On vérifie maintenant que nos tâches sont bien affectées à deux processeurs différents dans le scheduler :
[dchabal@localhost tuto]$
more /proc/xenomai/sched
CPU PID PRI PERIOD TIMEOUT TIMEBASE STAT NAME
0
0
-1
0
0
master R ROOT/0
1
0
-1
0
0
master R ROOT/1
0
4820
99
0
7629058446
master D my task 0
1
4821
99
0
7629328952
master D my task 1
VIII-B-6. Contrôle du scheduling d'une tâche : la fonction rt_task_set_mode▲
Encore une fonction intéressante, qui nous permet cette fois-ci de jouer un peu avec le scheduler.
rt_task_set_mode s'applique à la tâche courante. Parmi les paramètres, on trouve notamment :
- l'activation de l'Interrupt Shield ;
- la détection des passages « sauvages » en secondary mode (cf. § X.C.2) ;
- le passage explicite d'un mode à l'autre.
VIII-C. Les sémaphores (à compteur et d'exclusion mutuelle)▲
Cet exemple (semaphore.c) montre l'utilisation des deux types de sémaphores.
#include <stdio.h>
#include <sys/mman.h>
#include <native/task.h>
#include <native/sem.h>
#include <native/mutex.h>
static
RT_SEM sem;
static
RT_MUTEX mutex;
static
int
shared_variable;
static
void
task1
(
void
*
arg)
{
int
n=
0
;
printf
(
"
Task 1 computation
\n
"
);
/* Release sem */
if
((
n=
rt_sem_v
(&
sem))!=
0
)
printf
(
"
rt_sem_v error %d
\n
"
,n);
}
static
void
task2
(
void
*
arg)
{
int
n=
0
;
/* Wait for sem */
if
((
n=
rt_sem_p
(&
sem,TM_INFINITE))!=
0
)
printf
(
"
rt_sem_p error %d
\n
"
,n);
printf
(
"
Task 2 computation
\n
"
);
}
static
void
task
(
void
*
arg)
{
unsigned
int
runs=
10
;
char
*
taskname =
(
char
*
)arg;
while
(
runs--
)
{
if
(
rt_mutex_acquire
(
&
mutex, TM_INFINITE ) !=
0
)
printf
(
"
rt_mutex_acquire error
\n
"
);
++
shared_variable;
printf
(
"
%s, shared_variable=%d
\n
"
,taskname,shared_variable);
/* wait forcing round-robin */
rt_task_sleep
(
1
);
if
(
rt_mutex_release
(
&
mutex ) !=
0
)
printf
(
"
rt_mutex_release error
\n
"
);
}
}
int
main (
void
)
{
RT_TASK task_desc1,task_desc2,task_desc3,task_desc4;
int
n=
0
;
mlockall
(
MCL_CURRENT |
MCL_FUTURE ):
if
(
rt_sem_create
(
&
sem, "
my sem
"
, 1
, S_FIFO ) !=
0
)
{
printf
(
"
rt_sem_create error
\n
"
);
return
1
;
}
if
(
rt_mutex_create
(
&
mutex, "
my mutex
"
) !=
0
)
{
printf
(
"
rt_mutex_create error
\n
"
);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc1,"
my task 1
"
,0
,99
,T_JOINABLE,&
task1,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc2,"
my task 2
"
,0
,99
,T_JOINABLE,&
task2,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc3,"
my task 3
"
,0
,50
,T_JOINABLE|
T_RRB,&
task,"
my task 3
"
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc4,"
my task 4
"
,0
,50
,T_JOINABLE|
T_RRB,&
task,"
my task 4
"
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
(
void
)rt_task_join
(&
task_desc1);
(
void
)rt_task_join
(&
task_desc2);
(
void
)rt_task_join
(&
task_desc3);
(
void
)rt_task_join
(&
task_desc4);
return
0
;
}
Pour la mise au point :
des fichiers se trouvant dans /proc/xenomai/registry indiquent l'état des sémaphores et les tâches en attente.
VIII-D. Les files de messages▲
Dans cet exemple, deux tâches communiquent via des messages de taille variable.
#include <stdio.h>
#include <sys/mman.h>
#include <native/task.h>
#include <native/queue.h>
#define MSG_QUEUE_NAME "my msgqueue"
#define N_MSGS 15
static
RT_QUEUE msgq;
typedef
struct
{
char
buf[32
];
}
my_message;
static
void
task1
(
void
*
arg)
{
unsigned
int
i=
0
;
int
n=
0
;
my_message m;
for
(
i=
0
;i<
N_MSGS+
1
;++
i)
{
memset
(
m.buf,i,i+
1
);
n =
rt_queue_write
(&
msgq,&
m,i+
1
,Q_NORMAL);
if
(
n<
0
)
printf
(
"
rt_queue_write error %d
\n
"
,n);
}
}
static
void
task2
(
void
*
arg)
{
int
n=
0
,i=
1
;
my_message m;
while
(
1
)
{
memset
(
m.buf,0x00
,sizeof
(
m));
n =
rt_queue_read
(&
msgq,&
m,sizeof
(
m),TM_NONBLOCK);
if
(
n!=
i)
{
printf
(
"
rt_queue_read error %d
\n
"
,n);
break
;
}
printf
(
"
%d bytes read, first byte: %x
\n
"
,n,m.buf[0
]);
++
i;
}
}
int
main (
void
)
{
RT_TASK task_desc1,task_desc2;
int
n=
0
;
mlockall
(
MCL_CURRENT |
MCL_FUTURE );
if
(
(
n=
rt_queue_create
(
&
msgq, MSG_QUEUE_NAME, sizeof
(
my_message)*
N_MSGS, N_MSGS, Q_FIFO )) !=
0
)
{
printf
(
"
rt_queue_create error %d
\n
"
,n);
return
1
;
}
/* High priority task (writer) */
if
((
n=
rt_task_spawn
(
&
task_desc1,"
my task 1
"
,0
,99
,T_JOINABLE,&
task1,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
/* Low priority task (reader) */
if
((
n=
rt_task_spawn
(
&
task_desc2,"
my task 2
"
,0
,50
,T_JOINABLE,&
task2,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
(
void
)rt_task_join
(&
task_desc1);
(
void
)rt_task_join
(&
task_desc2);
if
(
rt_queue_delete
(
&
msgq )!=
0
)
printf
(
"
rt_queue_delete error
\n
"
);
return
0
;
}
Le répertoire /proc/xenomai/registry contient des informations sur les objets utilisés, par exemple pour notre file (le programme a été mis en pause) :
[dchabal@localhost queues]$
more /proc/xenomai/registry/native/queues/my\ msgqueue
type
=
shared:poolsz
=
8192
:usedmem
=
512
:limit
=
15
:mcount
=
15
[dchabal@localhost queues]$
mcount indique le nombre de messages stockés.
VIII-E. Les alarmes (watchdogs )▲
Contrairement à ce que l'on pourrait penser, ces alarmes n'appellent pas un handler lorsqu'elles expirent, mais débloquent une tâche en attente.
Cet exemple présente un calcul de longue durée qui arme/désarme une alarme. Dès que le désarmement n'est plus opéré, la tâche « my alarm task » affiche le nombre de déclenchements.
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <native/task.h>
#include <native/alarm.h>
#define ONE_SECOND 1e9
static
RT_ALARM my_alarm;
static
RT_TASK task_desc1;
static
void
task1
(
void
*
arg)
{
register
unsigned
int
i=
0
;
int
n=
0
;
/* start my_alarm */
if
((
n=
rt_alarm_start
(&
my_alarm, 0
, ONE_SECOND ))!=
0
)
printf
(
"
rt_alarm_start error %d
\n
"
,n);
/* big computation */
for
(
i=
0
;i<
100000
;++
i);
/* re-arm alarm */
if
(
rt_alarm_stop
(&
my_alarm )!=
0
)
printf
(
"
rt_alarm_stop error
\n
"
);
if
(
rt_alarm_start
(&
my_alarm, 0
, /* expiration time */
ONE_SECOND )!=
0
)
printf
(
"
rt_alarm_start error
\n
"
);
for
(
i=
0
;i<
100000
;++
i);
/* re-arm alarm */
if
(
rt_alarm_stop
(&
my_alarm )!=
0
)
printf
(
"
rt_alarm_stop error
\n
"
);
if
(
rt_alarm_start
(&
my_alarm, 0
, ONE_SECOND )!=
0
)
printf
(
"
rt_alarm_start error
\n
"
);
/* really big computation (alarm not rearmed) */
for
(
i=
0
;i<
1000000000
;++
i);
for
(
i=
0
;i<
1000000000
;++
i);
for
(
i=
0
;i<
1000000000
;++
i);
}
static
void
task_alarm
(
void
*
arg)
{
int
n=
0
;
RT_ALARM_INFO info;
while
(
1
)
{
if
((
n=
rt_alarm_wait
(&
my_alarm))==
0
)
{
if
(
rt_alarm_inquire
(&
my_alarm,&
info)==
0
)
{
printf
(
"
my_alarm %s triggerred %lu times
\n
"
,info.name,info.expiries);
rt_task_delete
(&
task_desc1);
}
else
printf
(
"
rt_alarm_inquire
\n
"
);
return
;
}
else
printf
(
"
rt_alarm_wait error %d
\n
"
,n);
}
}
int
main (
void
)
{
RT_TASK task_desc_alarm,task_main;
int
n=
0
;
if
(
mlockall
(
MCL_CURRENT |
MCL_FUTURE )!=
0
)
return
1
;
if
(
(
n=
rt_alarm_create
(
&
my_alarm,"
computation
"
)) !=
0
)
{
printf
(
"
rt_alarm_create error %d
\n
"
,n);
return
1
;
}
/* rt_alarm_start needs to be called from a Xenomai task */
if
(
rt_task_shadow
(&
task_main,"
main
"
,1
,0
)!=
0
)
{
printf
(
"
rt_task_shadow error
\n
"
);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc_alarm,"
my_alarm task
"
,0
,99
,T_JOINABLE,&
task_alarm,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
if
((
n=
rt_task_spawn
(
&
task_desc1,"
my task 1
"
,0
,99
,T_JOINABLE,&
task1,NULL
))!=
0
)
{
printf
(
"
rt_task_spawn error %d
\n
"
,n);
return
1
;
}
rt_task_join
(&
task_desc_alarm);
rt_task_join
(&
task_desc1);
return
0
;
}
VIII-F. Les pipes et la communication avec les applications Linux « normales »▲
Vous pilotez des cartes et vous souhaitez afficher des résultats ou des états dans une IHM. Comment faire ? Développer une application monobloc regroupant dialogue temps réel avec l'équipement en primary mode et affichage en secondary mode ? Pas très élégant de mélanger une IHM avec une réactivité pour être humain et du temps réel...
Heureusement, dans Xenomai il y a tout et en particulier des objets appelés RT_PIPE qui permettent à deux applications Xenomai et Linux de dialoguer à travers un « pipe » (les guillemets sont là pour vous rappeler que ce n'est pas un pipe créé avec pipe() ou mkfifo()).
L'approche est semblable aux tubes nommés qui, je vous le rappelle, ont une existence dans l'arborescence « / ».
Passons maintenant à une petite séance de brainstorming :
- une tâche Xenomai en primary mode doit accéder à ce pipe sans basculer en secondary mode, cette ressource doit donc être managée par Xenomai ;
- un programme Linux doit également y accéder sans même savoir qu'il y a une tâche Xenomai à l'autre bout du pipe ; qui doit donc être managé par Linux.
Intéressant paradoxe, la solution l'est tout autant : le pipe a deux noms, un pour les applications « pur » Linux, un autre pour les Xenomai.
- /dev/rtp npour les applications Linux ;
- rtp npour les applications Xenomai.
Voici un exemple de code qui envoie une chaîne de caractères d'une application Xenomai vers une application Linux.
L'application Xenomai :
#include <stdio.h>
#include <sys/mman.h>
#include <native/task.h>
#include <native/pipe.h>
static
RT_PIPE my_pipe;
static
void
task
(
void
*
arg)
{
char
*
s=
"
Hello world!
"
;
unsigned
int
sz=
strlen
(
s)+
1
;
if
(
rt_pipe_write
(
&
my_pipe, s, sz, P_NORMAL) !=
sz )
{
printf
(
"
rt_pipe_write error
\n
"
);
}
}
int
main (
void
)
{
RT_TASK task_desc;
/* disable memory swap */
if
(
mlockall
(
MCL_CURRENT |
MCL_FUTURE ) !=
0
)
{
printf
(
"
mlockall error
\n
"
);
return
1
;
}
if
(
rt_pipe_create
(
&
my_pipe, "
rtp0
"
, P_MINOR_AUTO, 0
) !=
0
)
{
printf
(
"
rt_pipe_create error
\n
"
);
return
1
;
}
if
(
rt_task_spawn
(
&
task_desc, /* task descriptor */
"
my task
"
, /* name */
0
/* 0 = default stack size */
,
99
/* priority */
,
T_JOINABLE, /* needed to call rt_task_join after */
&
task, /* entry point (function pointer) */
NULL
/* function argument */
)!=
0
)
{
printf
(
"
rt_task_spawn error
\n
"
);
return
1
;
}
/* wait for task function termination */
rt_task_join
(&
task_desc);
return
0
;
}
et l'application Linux :
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int
main
(
void
)
{
char
buf[128
];
int
fd =
open
(
"
/dev/rtp0
"
, O_RDWR );
if
(
fd <
0
)
{
printf
(
"
open error (%d)
\n
"
,fd);
return
1
;
}
if
(
0
<
read
(
fd, buf, sizeof
(
buf)) )
{
printf
(
"
==> %s
\n
"
,buf);
}
}
Le résultat de l'exécution :
VIII-G. Nommage et rémanence des objets▲
Les tâches, les files de messages, les sémaphores, les alarmes(37)... partagent un espace de nommage commun (registry) dans le noyau Xenomai. Autrement dit, une file de messages et une tâche ne peuvent avoir le même nom.
Depuis Xenomai 2.4, la suppression des objets orphelins est automatique.
VIII-H. Aperçu de l'API native▲
Fonctionnalité | Nom dans l'API |
Alarmes | rt_alarm_* |
Variables de condition | rt_cond_* |
Évènements | rt_event_ |
Allocation de mémoire RT | rt_heap_* |
Gestion des interruptions | rt_intr_* |
Mutexes | rt_mutex_* |
Pipes | rt_pipe_* |
Files de messages | rt_queue_* |
Sémaphores | rt_sem_* |
Tâches | rt_task_* |
Timers | rt_timer_* |