S.T.R.S.L. – Exercices corrigés

Voici les réponses aux exercices de mon livre « Solutions temps réel sous Linux ». Certaines questions n’admettent pas une réponse unique et vous pouvez avoir une approche tout à fait différente. N’hésitez pas à me faire part de vos résultats, vos idées, vos remarques concernant ces exercices ; ceci me permettra d’enrichir cette page de solutions.

L’archive complète des solutions des exercices se trouve ici : corrections-strsl.tar.bz2

 

Chapitre 1

Exercice 1-1

Le contenu du fichier /proc/cpuinfo et la manière de l’analyser sont décrits page 8.

Exercice 1-2

Le fichier « solution-01-02.c » contient une implémentation possible.

$ gcc -o solution-01-02 solution-01-02.c -pthread -lrt
$ ./solution-01-02 
  (Contrôle-C)
$

Exercice 1-3

Le fichier « solution-01-03.c » permet de réaliser l’opération demandée. On suit la progression sur un autre terminal, et l’on voit bien les passages en états R (Running), S (Sleeping), T (Traced).

$ gcc -o solution-01-03 solution-01-03.c
$ ./solution-01-03
[25579] Boucle active...
                                  $ ps aux | grep 25579 | grep -v grep
                                  cpb      25579 99.8  0.0   1820   248 pts/4    R+   10:33   0:05 ./solution-01-03
[25579] Sommeil...
                                  $ ps aux | grep 25579 | grep -v grep
                                  cpb      25579 97.5  0.0   1820   248 pts/4    S+   10:33   0:14 ./solution-01-03
[25579] Boucle active...
                                  $ kill -STOP 25579
[1]+  Stoppé                 ./solution-01-03
$
                                  $ ps aux | grep 25579 | grep -v grep
                                    cpb      25579 65.1  0.0   1820   248 pts/4    T    10:33   0:18 ./solution-01-03
                                  $ kill -CONT 25579
                                  $ ps aux | grep 25579 | grep -v grep
                                  cpb      25579 57.1  0.0   1820   248 pts/4    R    10:33   0:21 ./solution-01-03
 [25579] Sommeil...
[25579] Boucle active...
                                  $ kill -TERM 25579
                                  $
$

Exercice 1-4

Une solution est implémentée dans le fichier « solution-01-04.c« . Lorsqu’on l’exécute on voit que le processus fils devient Zombie au bout de dix secondes. Ceci signifie qu’il est terminé mais que son père n’a pas lu son statut de terminaison.

Les tentatives d’envoi de signal sur le fils n’ont aucun effet, il est déjà mort !

$ gcc -o solution-01-04 solution-01-04.c 
$ ./solution-01-04 
[27004] Je suis le processus pere...
[27005] Je suis le processus fils...
                                  $ ps aux | grep 27005 | grep -v grep
                                  cpb      27005  0.0  0.0   1820    56 pts/4    S+   10:47   0:00 ./solution-01-04
[27005] Je vais me terminer...
                                  $ ps aux | grep 27005 | grep -v grep
                                  cpb      27005  0.0  0.0      0     0 pts/4    Z+   10:47   0:00 [solution-01-04]
                                  $ kill -TERM 27005
                                  $ ps aux | grep 27005 | grep -v grep
                                  cpb      27005  0.0  0.0      0     0 pts/4    Z+   10:47   0:00 [solution-01-04]
                                  $ kill -INT 27005
                                  $ ps aux | grep 27005 | grep -v grep
                                  cpb      27005  0.0  0.0      0     0 pts/4    Z+   10:47   0:00 [solution-01-04]
                                  $ kill -9 27005
                                  $ ps aux | grep 27005 | grep -v grep
                                  cpb      27005  0.0  0.0      0     0 pts/4    Z+   10:47   0:00 [solution-01-04]
                                  $

Pour s’en débarasser, il faut terminer le processus père. Lorsqu’un processus meurt, ses fils (zombies ou non) sont adoptés par le processus numéro 1 (init) qui vient lire le statut de terminaison et les zombies disparaissent alors.

                                  $ kill 27004
Complete
$
                                  $ ps aux | grep 27005 | grep -v grep
                                  $

Chapitre 2

Exercice 2-1

En exécutant « watch -n 0.1 cat /proc/interrupts » (ou 0,1 éventuellement) vous devriez voir une interruption se déclencher périodiquement. Peut-être une interruption locale, interne au processeur. Sur la plupart des distributions actuelles, la fréquence du timer est à 250Hz ou 300Hz. Aussi, en observant le chiffre des milliers d’interruptions, vous devriez le voir évoluer toutes les 3 à 4 secondes.

Ceci dépend largement du système et du noyau installé.

Exercice 2-2

En pressant une touche du clavier ou en déplaçant la souris vous verrez une interruption se produire. Parfois il s’agit d’une interruption provoquée par un contrôleur USB auquel le clavier ou la souris sont branchés.

Exercice 2-3

Supposons que votre souris soit sur l’interruption 18, et qu’elle soit traitée majoritairement sur le CPU 0, alors que vous disposez de quatre cœurs. Vous pouvez forcer le traitement sur le cœur 2 en utilisant :

# echo 4 > /proc/irq/18/smp_affinity

La valeur a écrire est 2 puissance le numéro du cœur voulu (numéroté à partir de zéro). Il faut avoir les droits root pour effectuer cette modification.

Exercice 2-4

Pour effectuer une division par zéro sans que le compilateur ne s’en rende compte et nous l’interdise à la compilation, il suffit d’utiliser le nombre d’arguments sur la ligne de commande. C’est ce qui est implémenté dans le fichier « solution-02-04.c » qui déclenche bien l’exception attendue.

$ ./solution-02-04 abcdef 
Je vais calculer i=1/1
Le resultat est 1
$ ./solution-02-04 abcdef ghijkl
Je vais calculer i=1/2
Le resultat est 0
$ ./solution-02-04 
Je vais calculer i=1/0
Exception en point flottant
$

Exercice 2-5

Pour déclencher l’exception « Illegal instruction« , il faut exécuter du code invalide pour le processeur employé. C’est difficile avec les architectures actuelles car les pages de code sont verrouillées en lecture seule (donc impossible de modifier des fonctions existantes) et les pages de données ne sont pas exécutables (donc impossible d’y dérouter l’exécution).

La solution consiste à générer un code assembleur invalide et à l’insérer manuellement dans un programme C, comme c’est fait dans le fichier « solution-02-05.c » (les instructions sont invalides pour un processeur x86, il faudra peut-être changer les valeurs si on le teste sur une autre architecture).

$ gcc -o solution-02-05 solution-02-05.c 
$ ./solution-02-05 
J'execute la fonction avec le code invalide...
Instruction non permise
$

Chapitre 3

Exercice 3-1

Comme on peut s’y attendre, le nombre de boucles par dizaine de secondes reste a peu près constant, et se répartit équitablement entre les processus.

$ ./exemple-taux-cpu 
[3036] nb_boucles : 79183231
$ ./exemple-taux-cpu & ./exemple-taux-cpu 
[3039] nb_boucles : 40693996
[3040] nb_boucles : 40694974
$ ./exemple-taux-cpu & ./exemple-taux-cpu & ./exemple-taux-cpu 
[3133] nb_boucles : 26061303
[3131] nb_boucles : 25634096
[3132] nb_boucles : 25702652
$

Exercice 3-2

En utilisant la commande nice on favorise un processus par rapport à l’autre. Plus la valeur transmise à nice est élevée, plus le processus est pénalisé.

$ ./exemple-taux-cpu & nice -n +3 ./exemple-taux-cpu 
[3611] nb_boucles : 25088529
[3610] nb_boucles : 49568574
$ ./exemple-taux-cpu & nice -n +5 ./exemple-taux-cpu 
[3701] nb_boucles : 56678953
[3702] nb_boucles : 18291412
$ ./exemple-taux-cpu & nice -n +10 ./exemple-taux-cpu 
[3705] nb_boucles : 74797470
[3706] nb_boucles : 7845659
$ ./exemple-taux-cpu & nice -n +20 ./exemple-taux-cpu 
[3709] nb_boucles : 79928673
[3710] nb_boucles : 1187910
$

Exercice 3-3

La rédaction du script shell n’est pas évidente car il faut lancer les vingt processus en parallèle et trier leurs résultats. On en trouvera un exemple dans le fichier solution-03-03.sh. Notez que les valeurs croissantes de nice correspondent aux valeurs croissantes de PID.

$ ./solution-03-03.sh 
[3841] nb_boucles : 16599509
[3842] nb_boucles : 13338231
[3843] nb_boucles : 10650787
[3844] nb_boucles : 8574728
[3845] nb_boucles : 6903253
[3846] nb_boucles : 5472881
[3847] nb_boucles : 4428908
[3848] nb_boucles : 3508128
[3849] nb_boucles : 2801397
[3850] nb_boucles : 1995259
[3851] nb_boucles : 1601489
[3852] nb_boucles : 1265770
[3853] nb_boucles : 1044762
[3854] nb_boucles : 910331
[3855] nb_boucles : 749787
[3856] nb_boucles : 584887
[3857] nb_boucles : 486954
[3858] nb_boucles : 389343
[3859] nb_boucles : 285891
[3860] nb_boucles : 251992
$

Exercice 3-4

Pour tester les résultats de l’exercice précédent sur plusieurs versions du noyau Linux, il faut réaliser une longue série de compilations (comme indiqué dans l’annexe I). J’ai compilé un noyau Vanilla 2.6.39.9 sur une distribution Ubuntu 11.10. La première version sans l’option autogroup, et l’autre avec cette option. Les fichiers de configuration des noyaux se trouvent dans l’archive des solutions.

Les résultats sont les suivants, en commençant sur un système faiblement chargé.

Option autogroup NON activée:
[2065] nb_boucles : 15809768
[2066] nb_boucles : 12317797
[2067] nb_boucles : 10108224
[2068] nb_boucles : 7879477
[2069] nb_boucles : 6340445
[2070] nb_boucles : 5023591
[2071] nb_boucles : 4191715
[2072] nb_boucles : 3326890
[2073] nb_boucles : 2662925
[2074] nb_boucles : 2135578
[2075] nb_boucles : 1701423
[2076] nb_boucles : 1327210
[2077] nb_boucles : 1075745
[2078] nb_boucles : 865417
[2079] nb_boucles : 698098
[2080] nb_boucles : 547356
[2081] nb_boucles : 449937
[2082] nb_boucles : 353484
[2083] nb_boucles : 287868
[2084] nb_boucles : 224124

et

Option autogroup activée
[4124] nb_boucles : 16547401
[4125] nb_boucles : 13360087
[4126] nb_boucles : 10621702
[4127] nb_boucles : 8524357
[4128] nb_boucles : 6842971
[4129] nb_boucles : 5452880
[4130] nb_boucles : 4413384
[4131] nb_boucles : 3516360
[4132] nb_boucles : 2820007
[4133] nb_boucles : 2236505
[4134] nb_boucles : 1784830
[4135] nb_boucles : 1423908
[4136] nb_boucles : 1163712
[4137] nb_boucles : 928116
[4138] nb_boucles : 761456
[4139] nb_boucles : 589009
[4140] nb_boucles : 495110
[4141] nb_boucles : 397024
[4142] nb_boucles : 296815
[4143] nb_boucles : 263285

Peu de différences notables dans le comportement. Voyons à présent sur un système avec une charge en processus assez élevée.

Option autogroup NON activée:
[2294] nb_boucles : 11454211
[2295] nb_boucles : 9183630
[2296] nb_boucles : 7472511
[2297] nb_boucles : 6001470
[2298] nb_boucles : 4813134
[2299] nb_boucles : 3762535
[2300] nb_boucles : 3110984
[2301] nb_boucles : 2394422
[2302] nb_boucles : 1934842
[2303] nb_boucles : 1581542
[2304] nb_boucles : 1235447
[2305] nb_boucles : 988488
[2306] nb_boucles : 819291
[2307] nb_boucles : 644526
[2308] nb_boucles : 514887
[2309] nb_boucles : 419180
[2310] nb_boucles : 326733
[2311] nb_boucles : 255844
[2312] nb_boucles : 222833
[2313] nb_boucles : 188144

et

Option autogroup activée
[4362] nb_boucles : 4972521
[4363] nb_boucles : 4015143
[4364] nb_boucles : 3176701
[4365] nb_boucles : 2553396
[4366] nb_boucles : 2069002
[4367] nb_boucles : 1648680
[4368] nb_boucles : 1324571
[4369] nb_boucles : 1069048
[4370] nb_boucles : 838920
[4371] nb_boucles : 679788
[4372] nb_boucles : 551903
[4373] nb_boucles : 451465
[4374] nb_boucles : 354519
[4375] nb_boucles : 290077
[4376] nb_boucles : 224147
[4377] nb_boucles : 190850
[4378] nb_boucles : 158436
[4379] nb_boucles : 126133
[4380] nb_boucles : 93978
[4381] nb_boucles : 88006

Dans le second cas, l’ensemble des processus se sont partagé le temps CPU attribué à leur terminal, sans perturber excessivement les autres tâches du système. On pourra trouver plus d’informations dans cet article.

Exercice 3-5

Les informations sur le mécanisme des cgroups se trouvent dans le répertoire Documentation/cgroups à l’intérieur des sources du noyau Linux. Montons le système de fichiers correspondant si ce n’est pas fait par la distribution

# mount | grep cgroup
# mkdir -p /dev/cgroup
# mount none /dev/cgroup/ -t cgroup -o cpuset
# ls /dev/cgroup/
cgroup.clone_children  cpuset.cpu_exclusive  cpuset.mem_hardwall     cpuset.memory_pressure_enabled  cpuset.mems                      notify_on_release
cgroup.event_control   cpuset.cpus           cpuset.memory_migrate   cpuset.memory_spread_page       cpuset.sched_load_balance        release_agent
cgroup.procs           cpuset.mem_exclusive  cpuset.memory_pressure  cpuset.memory_spread_slab       cpuset.sched_relax_domain_level  tasks
#

Créons un groupe (arbitrairement nommé) que nous limiterons au CPU 3 de cette machine, en lui attribuan le noeud mémoire zéro.

# mkdir /dev/cgroup/CPU-3
# ls /dev/cgroup/CPU-3/
cgroup.clone_children  cgroup.procs          cpuset.cpus           cpuset.mem_hardwall    cpuset.memory_pressure     cpuset.memory_spread_slab  cpuset.sched_load_balance        notify_on_release
cgroup.event_control   cpuset.cpu_exclusive  cpuset.mem_exclusive  cpuset.memory_migrate  cpuset.memory_spread_page  cpuset.mems                cpuset.sched_relax_domain_level  tasks
# echo 3 > /dev/cgroup/CPU-3/cpuset.cpus
# echo 0 > /dev/cgroup/CPU-3/cpuset.mems 
#

Plaçons notre shell (dont le PID est donné par la variable $$) dans ce groupe.

# echo $$ > /dev/cgroup/CPU-3/tasks
#

Vérifions l’affinité de notre shell.

# taskset -p $$
pid 29562's current affinity mask: 8
#

L’affinité est 8 (2^3), le shell ne peut donc fonctionner que sur le CPU 3. Essayons de le replacer sur le CPU 0.

# taskset -pc 0 $$
pid 29562's current affinity list: 3
taskset: failed to set pid 29562's affinity: Argument invalide
#

Le déplacement n’est pas autorisé. Les affectations par le mécanisme cgroups est plus contraignant que la simple modification de l’affinité. Les processus ne peuvent plus sortir des CPU qui leur ont été attribués.

 

Chapitre 4

Exercice 4-1

La modification de la priorité temps partagé n’a pas d’influence sur le nombre d’appels système gettimeofday() que notre processus réussit à effectuer par micro-seconde.

Exercice 4-2

Les opérations ont été décrites pages 72 et 73, les statistiques peuvent présenter de grosses variations d’une exécution à l’autre en fonction de la charge du système.

Exercice 4-3

Vous devriez obtenir des histogrammes proches de ceux présentés pages 77 et 79. Il est nécessaire d’installer le programme Gnuplot pour disposer d’une représentation graphique. En principe on arrive a obtenir un timer de qualité correcte avec une période de quelques millisecondes, et un système peu chargé.

Exercice 4-4

Plus les perturbations externes seront fortes (tant en interruptions qu’en charge système), plus le timer fluctuera. Dans des situations extrèmes, les ecarts pourront même dépasser la seconde.

Exercice 4-5

Les durées maximales des préemptions dépendent très fortement du matériel, de sa charge en interruption et de sa charge en processus. Sur un système peu chargé, il ne doit pas y avoir de préemption supérieure à une milliseconde. On peut s’en assurer ainsi.

$ ./exemple-gettimeofday-02 1000
$

Pour affiner l’expérience, il peut s’avérer nécessaire d’allonger la durée initiale de « exemple-gettimeofday-02.c » (ligne 51, la durée initiale est de cinq secondes).

Chapitre 5

Exercice 5-1

Le script « cherche-taches-rt.sh » permet d’afficher les paramètres des processus ordonnancés Round Robin ou Fifo. En voici un exemple d’utilisation.

$ ./cherche-taches-rt.sh
pid 11's current scheduling policy: SCHED_FIFO
pid 11's current scheduling priority: 99
pid 14's current scheduling policy: SCHED_FIFO
pid 14's current scheduling priority: 99
pid 21514's current scheduling policy: SCHED_RR
pid 21514's current scheduling priority: 1
pid 6's current scheduling policy: SCHED_FIFO
pid 6's current scheduling priority: 99
pid 7's current scheduling policy: SCHED_FIFO
pid 7's current scheduling priority: 99
$

Exercice 5-2

Il faut disposer des droit root (ou employer sudo) pour passer l’ordonnancement d’une tâche en temps réel. Pour modifier l’ordonnancement du shell, on peut employer la variable $$ qui est toujours initialisée avec son propre PID.

$ sudo chrt -pf 10 $$
[sudo] password for cpb:

On peut vérifier l’ordonnancement configuré avec chrt également.

$ chrt -p $$
pid 11868's current scheduling policy: SCHED_FIFO
pid 11868's current scheduling priority: 10
$

Le shell ne présente pas de modification de son comportement, mais tous les processus qu’il lance démarreront en temps réel.

Exercice 5-3

Le script passer-taches-rt.sh passe en ordonnancement Fifo priorité 10 tous les processus initialement en temps partagé. Le système fonctionne tout à fait normalement. Il peut y avoir néanmoins des périodes de gel temporaire de l’activité lorsqu’on exécute des processus qui consomme beaucoup de CPU (calcul, compilation, etc.).

Exercice 5-4

Au lancement du programme le système semble figé, mais quelques tâches temps partagé arrivent à s’exécuter quand même, à cause du paramètre /proc/sys/kernel/sched_rt_runtime_us comme indiqué page 99. En écrivant -1 dans ce pseudo-fichier, on peut geler temporairement tout le système avec des boucles actives.

Exercice 5-5

Il n’est pas simple de trouver toutes les tâches à passer en temps réel pour conserver le contrôle de l’interface graphique malgré la boucle active. J’ai déjà remarqué que les fenêtres des terminaux en mode texte semblent recevoir des rafales de retours-chariot si elles n’ont pas d’accès au CPU pendant un temps prolongé. Je suppose que cela est lié à un délai de timeout qui expire et correspond à l’émission d’un caractère break vers le terminal.

Chapitre 6

Exercice 6-1

L’exécution de exemple-timer-create-02.c sous un ordonnancement temps réel permet d’obtenir un résultat plus fiable (mais pas plus rapide bien sûr) qu’en temps partagé. Les fluctuations devraient se compter en dizaines de micro-secondes

Exercice 6-2

Le programme exemple-perturbateur.c créant essentiellement de la charge CPU en temps partagé, il n’a que très peu d’influence sur le timer. Si on l’ordonnance en temps-réel, il pourra perturber très violement le timer aussitôt que leurs priorités seront identiques.

Exercice 6-3

Sur la plupart des systèmes, la fluctuation d’un timer (l’écart type des périodes mesurées) reste inférieure à 5% de la période si celle-ci est de l’ordre d’une cinquantaine de microsecondes au minimum.

Exercice 6-4

La mesure du temps de commutation entre threads synchronisés par un mutex peut se faire en employant le programme « exemple-commutations-threads.c » de la manière décrite pages 117 à 120. Pour réaliser la même mesure sur des processus sycnhronisés par un sémaphore, employez le programme « exemple-commutation-processus.c » tel qu’indiqué page 121.

Les temps de commutation entre processus peuvent être légèrement plus longs, ceci étant dû à la mémoire virtuelle, et à la nécessité de modifier la configuration de la MMU.

Exercice 6-5

Vous trouverez des exemples de mesure de l’efficacité des outils de synchronisation et de communication entre processus dans les articles suivants.

Chapitre 7

Exercice 7-1

Pour synchroniser le démarrage de plusieurs processus distincts, il est possible de verrouiller un sémaphore Posix avant leur lancement (ou de l’initialiser avec un compteur à zéro). Chaque processus demande le sémaphore (et reste donc bloqué initialment) puis le relâche immédiatement.

Le processus principal, après avoir créé tous les processus fils va les laisser démarrer en relâchant le sémpahore. Vous en trouverez une implémentation dans le fichier solution-07-01.c.

Exercice 7-2

L’implémentation proposé dans « solution-07-02.c » nous démontre qu’une situation d’inversion de priorité peut se produire sur un sémaphore (P2 s’exécute avant P3).

# ./solution-07-02 
P1 demarre
P1 demande le sempahore
P1 tient le semaphore
P1 cree P3
        P3 demarre
        P3 demande le semaphore
P1 cree P2
    P2 demarre
    P2 se termine
P1 lache le semaphore
        P3 tient le sempahore
        P3 lache le semaphore
        P3 se termine
P1 se termine
#

Il n’existe pas d’héritage de priorité sur les sémaphores, car à la différence des mutex, ceux-ci n’ont pas de propriétaire. Un sémaphore est essentiellement un compteur, qui peut prendre une valeur positive ou nulle. Lorsque le sémaphore est à zéro, n’importe quel processus peut l’incrémenter. Il n’y a pas de notion de processus qui « tient » le sémaphore, comme un thread tient un mutex.

Exercice 7-3

Sur un système où les mutex n’offrent pas d’héritage de priorité, une solution classique pour éviter les inversions de priorité, est de rendre un thread non-préemptible dès qu’il tient un objet de synchronisation. Sur certains systèmes d’exploitation, on réalise ceci en bloquant les interruptions. Pour travailler de manière portable, je proposerai plutôt de monter la priorité du thread tenant un mutex à la valeur la plus élevée possible.

Dans le fichier « solution-07-03.c » j’ai encapsulé les appels pthread_mutex_lock() et pthread_mutex_unlock() dans des fonctions qui modifient la priorité du thread appelant. Notez que le résultat d’exécution évite bien l’inversion de priorité, mais n’est pas tout à fait identique à celui obtenu page 147 avec le Priority Inheritance Protocol.

# ./solution-07-03
T1 demarre
T1 demande le mutex
T1 tient le mutex
T1 cree T3
T1 cree T2
T1 lache le mutex
        T3 demarre
        T3 demande le mutex
        T3 tient le mutex
        T3 lache le mutex
        T3 se termine
    T2 demarre
    T2 se termine
T1 se termine
#

Exercice 7-4

Le fichier « solution-07-04.c » permet de s’assurer qu’un sémaphore est bien obtenu par le processus le plus prioritaire qui le demande. Dans le cas d’un ordonnancement en Round Robin, les processus obtiennent le sémaphore dans l’ordre d’arrivée (l’ordre de demande).

Exercice 7-5

Pour qu’il y ait un partage équitable de l’accès au sémaphore, et que les processus l’obtiennent les uns après les autres, il est nécessaire d’introduire un appel à sched_yield(), comme nous en avons parlé page 159.

# chrt -r 10 ./solution-07-05 & chrt -r 10 ./solution-07-05 & chrt -r 10 ./solution-07-05 &
[1] 15403
[2] 15404
[3] 15405
#
[15405] demande le semaphore
   [15405] tient le semaphore
[15404] demande le semaphore
[15403] demande le semaphore
   [15405] lache le semaphore
   [15404] tient le semaphore
[15405] demande le semaphore
   [15404] lache le semaphore
   [15403] tient le semaphore
[15404] demande le semaphore
   [15403] lache le semaphore
   [15405] tient le semaphore
[15403] demande le semaphore
   [15405] lache le semaphore
   [15404] tient le semaphore
[15405] demande le semaphore
   [15404] lache le semaphore
   [15403] tient le semaphore
[15404] demande le semaphore
   [15403] lache le semaphore
   [15405] tient le semaphore
[15403] demande le semaphore
   [15405] lache le semaphore
   [15404] tient le semaphore
[15405] demande le semaphore
   [15404] lache le semaphore
   [15403] tient le semaphore
[15404] demande le semaphore
   [15403] lache le semaphore
   [15405] tient le semaphore
[15403] demande le semaphore
# killall solution-07-05
   [15405] lache le semaphore
   [15404] tient le semaphore
   [15404] lache le semaphore
   [15403] tient le semaphore
   [15403] lache le semaphore
[1]   Complété              chrt -r 10 ./solution-07-05
[2]-  Complété              chrt -r 10 ./solution-07-05
[3]+  Complété              chrt -r 10 ./solution-07-05
#

Chapitre 8

Exercice 8-1

Pour compiler un noyau modifié avec le patch Linux-rt, je vous conseille de vous reporter aux pages 169 et 170, ainsi qu’aux articles suivants.

Exercice 8-2

Les résultats obtenus avec « exemple-timer-create-02.c » sur un noyau modifié avec le patch Linux-rt devraient être plus fiables qu’avec le noyau vanilla si le processus est exécuté avec une priorité assez élevée. Les différences apparaîtront lorsque la charge en interruption augmente (avec un ping flood par exemple).

Exercice 8-3

Sur un noyau vanilla, une boucle active de priorité élevée est préemptée par le noyau dès qu’une opération est à réaliser en réponse à une interruption. Et les tâches réseau d’un noyau standard sont considérées comme suffisamment prioritaires pour être traitées directement à la réception de l’interruption. Ainsi, même si une tâche de priorité Fifo 99 est active, on l’interrompra pour répondre à un ping. Avec un noyau modifié Linux-rt, les traitements en réponse aux interruptions sont effectués dans des threads du kernel, ordonnancés avec une priorité temps-réel 50. Si une tâche utilisateur est active avec une priorité supérieure, les threads du noyau (et donc la réponse au ping), seront retardés jusqu’à la fin de la tâche de haute priorité.

Exercice 8-4

Si vous montez le thread kernel correspondant à l’interface réseau à une priorité supérieure à celle de la boucle, le système répond à nouveau au ping.

Exercice 8-5

On trouvera les éléments de réponse dans cet article.

Chapitre 9

Exercice 9-1

Vous trouverez la dernière version de Xenomai ici : http://www.xenomai.org/

Téléchargez l’archive, décompressez-la, et vérifiez le patch le plus récent pour votre architecture. Téléchargez le noyau Linux (sur http://www.kernel.org) correspondant. Vous trouverez les détails pages 193 et 194.

Exercice 9-2

Pour la compilation du noyau avec Xenomai, je vous propose un premier article (installation sur une distribution Ubuntu) et un second sur carte à processeur Arm.

Exercice 9-3

Après compilation des bibliothèques (voir pages 197 et 198), le programme latency vous permettre d’obtenir des résultats similaires à ceux présentés  page 201.

Exercice 9-4

Les résultats de cette expérience sont très variables d’une architecture à l’autre. Sur les PC on remarque souvent des fluctuations plus importantes sur des machines de bureau que sur des postes industriels. A titre d’exemple, sur un PC industriel avec une charge très élevé en interruptions et en processus, j’ai observé en une journée une fluctuation maximale de 36 microsecondes sur un timer programmé à 10kHz.

Chapitre 10

Exercice 10-1

Le programme « solution-10-01.c » propose une implémentation de boucle active de 10 secondes sur tous les CPU. Pendant l’exécution, tout le système est gelé. Depuis un autre poste, le ping se comporte ainsi:

$ ping 192.168.3.1
64 bytes from 192.168.3.1: icmp_req=41 ttl=64 time=0.660 ms
64 bytes from 192.168.3.1: icmp_req=42 ttl=64 time=0.838 ms
                   (exécution du programme)
64 bytes from 192.168.3.1: icmp_req=43 ttl=64 time=9936 ms
64 bytes from 192.168.3.1: icmp_req=44 ttl=64 time=8937 ms
64 bytes from 192.168.3.1: icmp_req=45 ttl=64 time=7937 ms
64 bytes from 192.168.3.1: icmp_req=46 ttl=64 time=6937 ms
64 bytes from 192.168.3.1: icmp_req=47 ttl=64 time=5937 ms
64 bytes from 192.168.3.1: icmp_req=48 ttl=64 time=4937 ms
64 bytes from 192.168.3.1: icmp_req=49 ttl=64 time=3938 ms
64 bytes from 192.168.3.1: icmp_req=50 ttl=64 time=2938 ms
64 bytes from 192.168.3.1: icmp_req=51 ttl=64 time=1938 ms
64 bytes from 192.168.3.1: icmp_req=52 ttl=64 time=938 ms
64 bytes from 192.168.3.1: icmp_req=53 ttl=64 time=0.840 ms
64 bytes from 192.168.3.1: icmp_req=54 ttl=64 time=0.843 ms

Exercice 10-2

Le programme « solution-10-02.c » permet de mesurer la durée d’une boucle programmée avec rt_timer_spin(). La durée théorique est 1 seconde, les valeurs affichées sont des nanosecondes.

# ./solution-10-02 
duree : 1000018442
duree : 1000029363
duree : 1000011173
duree : 1000016787
duree : 1000011203

Nous voyons des fluctuations de l’ordre de quelques dizaines de microsecondes

Exercice 10-3

Apparemment, les fluctuations avec rt_task_sleep() sont un peu plus importantes qu’avec rt_timer_spin() comme en témoigne le programme « solution-10-03.c » dont les résultats sont les suivants. Obtenez-vous la même conclusion ?

# ./solution-10-03 
duree : 1000027833
duree : 1000041888
duree : 1000023600
duree : 1000031104
duree : 1000040409
duree : 1000029946
duree : 1000029999

Exercice 10-4

Le programme « solution-10-04.c » affiche les variations des periodes mesurées par les timers lancés sur chaque CPU. Les pêriodes théoriques (en microsecondes) sont 100, 200, 300, etc. Les fluctuations mesurées sont affichées également en microsecondes. Suivant les machines, les fluctuations peuvent aller de quelques microsecondes à quelques dizaines de microsecondes.

Chapitre 11

Exercice 11-1

Il existe de nombreuses solutions possibles. Une implémentation est fournie dans le fichier « solution-11-01.c » mais elle pourrait être améliorée.

# insmod ./solution-11-01.ko 
# echo AZERTY > /dev/solution_11_01 
# echo UIOP > /dev/solution_11_01 
# echo QSDF > /dev/solution_11_01 
# cat /dev/solution_11_01 
AZERTY
# cat /dev/solution_11_01 
UIOP
# cat /dev/solution_11_01 
QSDF
# cat /dev/solution_11_01 
# rmmod solution_11_01 
#

Exercice 11-2

Le modules « solution-11-02.c » présente une solution possible. La gestion de l’offset dans la méthode read() est un peu rudimentaire… Sur mon poste de travail, la souris déclenche l’interruption 18.

# insmod ./solution-11-02.ko numero_irq=18
# cat /dev/solution_11_02 
0
     (quelques mouvements de souris)
# cat /dev/solution_11_02
93
     (quelques mouvements de souris)
# cat /dev/solution_11_02 
116
# cat /dev/solution_11_02 
0
# rmmod solution_11_02 
#

Exercice 11-3

La solution de cet exercice a fait l’objet d’un article. N’hésitez pas à m’envoyer les résultats que vous obtenez.

Exercice 11-4

Vous trouverez un exemple de code réalisant le travail demandé dans cet article, mais je vous conseille de commencer par la lecture de celui-ci.

URL de trackback pour cette page