• Aucun résultat trouvé

COURS Le temps réel sous Linux (processus)

N/A
N/A
Protected

Academic year: 2022

Partager "COURS Le temps réel sous Linux (processus)"

Copied!
8
0
0

Texte intégral

(1)

Extrait du référentiel : BTS Systèmes Numériques option A (Informatique et Réseaux) Niveau(x) S4. Développement logiciel

S4.9. Programmation événementielle S6. Système d’exploitation

S6.3. Spécificités temps-réel

Environnement temps réel : espace utilisateur, espace noyau, etc.

Contraintes de temps d’un système de contrôle/commande

Interruptions Noyau temps réel

Commutation de contexte en modes coopératif et préemptif

2

3 3 2 2

Objectifs du cours :

Ce cours traitera essentiellement les points suivants : - Échelle des priorités

- Configuration de l’ordonnancement - Processus temps réel

- Garde-fou temps réel

ÉCHELLE DES PRIORITÉS

Les tâches temps réel disposent d'une échelle des priorités spécifique, totalement indépendante des tâches temps partagé. Sous Linux, les priorités temps réel se trouvent dans l'intervalle [1,99].

Une tâche évoluant avec la priorité 99 est donc la plus prioritaire.

On peut considerer que la priorité temps réel 0 correspond à l'ordonnancement temps partagé, gérant lui-même ses propres priorités à travers la valeur de courtoisie (nice). On peut observer la frontière entre ces deux classes d'ordonnancement sur la figure page suivante.

(2)

Échelle des priorités

Le principe général est qu'une tâche d'une priorité donnée ne pourra jamais être préemptée ou laissée en attente (Runnable) tandis qu'une tâche de priorité moindre dispose du processeur.

Une tâche de priorité 50 ne laissera jamais s'exécuter une tâche de priorité 49 et encore moins une tâche temps partagé.

Attention, cette dernière affirmation n'est pas tout à fait exacte, nous en reparlerons plus loin.

Pour que la tâche de priorité 49 puisse s'exécuter, il faudra que celle de priorité 50 s'endorme volontairement (ou se termine) comme représenté ci-dessous.

Cession de CPU en temps réel

En revanche, notre tâche de priorité 50 sera immédiatement préemptée si une tâche de priorité 51 se réveille (suite à une interruption ou au déclenchement d'un timer, par exemple), comme nous pouvons le voir sur la figure page suivante. Il lui faudra attendre l'endormissement ou la

terminaison de la tâche de priorité 51 pour reprendre son exécution.

(3)

Préemption en temps réel

Il subsiste malgré tout une interrogation lorsque deux tâches se trouvent sur le même niveau de priorité. Pour préciser le comportement, il existe deux types d'ordonnancements temps réel normalisés par la norme Posix.

• Fifo (First ln, First Out, premier arrivé, premier servi) : une tâche sous ordonnancement « Fifo » ne peut être préemptée que par une tâche de priorité strictement supérieure qui devient prête.

• Round Robin (tourniquet) : lorsqu'une tâche sous ordonnancement « Round Robin » est activée, on lui accorde un délai au bout duquel elle sera préemptée pour laisser passer les autres tâches de même priorité en attente. Si aucune tâche n'est prête, la tâche initiale reprend son exécution normalement.

Même en « Round Robin », une tâche ne laissera jamais s'exécuter une tâche de priorité inférieure.

CONFIGURATION DE L’ORDONNANCEMENT

Pour fixer ou consulter le type d'ordonnancement d'un processus, on utilise les appels système.

#include <sched.h>

int sched_setscheduler (pid_t cible, int ordonnancement,

const struct sched_param * parametre);

int sched_getscheduler (pid_t cible);

int sched_setparam (pid_t cible,

const struct sched_param *parametre);

int sched_getparam (pid_t cible,

struct sched_param * parametre);

Le premier argument est l'identifiant du processus concerné. En effet, un processus peut tout à fait modifier l'ordonnancement d'un autre processus. Si l'on indique « 0 » en premier argument, c'est au contraire son propre ordonnancement que l'on modifie.

(4)

Le second argument de sched_setscheduler( )ou le retour de sched_getscheduler( )représente le type d'ordonnancement parmi les trois valeurs normalisées par Posix.

Il existe d'autres types d'ordonnancements temps partagé (SCHED_BATCH, SCHED_ IDLE) spécifiques à Linux et dont le comportement n'est pas constant selon les versions du kernel.

Le troisième argument de sched_ setscheduler( ) ou le deuxième de sched_getparam( ) et sched_

setparam( ) est un pointeur sur une structure de paramètres. Cette structure ne contient qu'un seul champ normalisé.

Lorsque l’on fixe un nouvel ordonnancement, avec sched_setscheduler(), on se contentera de remplir ce champ sched_priority. Sous Linux, il s'agit bien de l'unique champ de la structure, toutefois il est possible que d'autres systèmes ajoutent leurs propres paramètres (on peut imagi- ner, par exemple, une notion de « deadline » pour d'autres ordonnancements).

Aussi, pour assurer la portabilité d'un programme, prendra-t-on soin, lorsque l'on voudra modifier la priorité, de commencer par lire la structure avec sched_getparam(),de modifier le champ

sched_priority, puis de réécrire la structure avec sched_setparam().

Les valeurs limites pour les priorités temps réel sont dans l'intervalle [1,99]. Ceci est spécifique à Linux, et un programme se voulant portable devrait déterminer correctement l'échelle des priorités en invoquant :

int sched_get_priority_max (int ordonnancement);

int sched_get_priority_min (int ordonnancement);

Exemple de code pour une vérification des valeurs indiquées précédemment :

#include <sched.h>

#include <stdio.h>

#include <stdlib.h>

int main(void) {

printf("SCHED_FIFO : [%d - %d]\n",

sched_get_priority_min(SCHED_FIFO), sched_get_priority_max(SCHED_FIFO));

printf("SCHED_RR : [%d - %d]\n", sched_get_priority_min(SCHED_RR), sched_get_priority_max(SCHED_RR));

(5)

printf("SCHED_OTHER : [%d - %d]\n",

sched_get_priority_min(SCHED_OTHER), sched_get_priority_max(SCHED_OTHER));

return EXIT_SUCCESS;

}

Exemple d’affichage console sous Ubuntu 15

Il est important de savoir que pour soumettre un processus à un ordonnancement temps réel, avec sched_setscheduler(), l'appelant doit disposer des droits « root ». En effet, cette opération est potentiellement dangereuse, puisque la tâche temps réel peut se mettre à « dévorer » tout le temps CPU disponible et créer ainsi un déni de service sur le système.

PROCESSUS TEMPS RÉEL

Nous allons créer un processus père qui va lancer autant de processus fils qu'il y a de « CPU » disponibles sur le système. Chacun d'entre eux fixera son affinité afin d'occuper tous les proces- seurs. Ensuite, ils passeront en temps réel Fifo de priorité 99, avant d'attaquer une boucle active infinie.

Bien sûr, si nous nous lançons directement dans cette boucle, tous les « CPU » seront saturés et nous ne pourrons pas reprendre la main sur notre machine. Notre seul recours sera l'interrupteur Marche/Arrêt. Pour éviter ceci, nous allons programmer une alarme de 15 secondes avant de rentrer dans la boucle.

Au bout des 15 secondes, le noyau enverra automatiquement un signal « SIGALRM » à chaque processus qui a programmé l'alarme. Comme nous ne faisons rien pour gérer ce signal, les processus en boucle seront tués, et nous reprendrons le contrôle de notre système. Voici une implémentation : « exemple-boucle-temps-reel.c »

#define _GNU_SOURCE // Pour avoir sched_setaffinity

#include <sched.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/wait.h>

void fonction_fils (int cpu);

int main(void) {

int nb_cpus = sysconf(_SC_NPROCESSORS_ONLN);

int cpu;

for (cpu = 0; cpu < nb_cpus; cpu ++) {

(6)

if (fork() == 0) {

fonction_fils(cpu);

}

}

/* Attendre la fin des fils */

for (cpu = 0; cpu < nb_cpus; cpu ++) waitpid(-1, NULL, 0);

return EXIT_SUCCESS;

}

void fonction_fils (int cpu) {

struct sched_param param;

cpu_set_t cpuset;

/* Se placer sur le CPU indique */

CPU_ZERO(& cpuset);

CPU_SET(cpu, &cpuset);

if (sched_setaffinity(0, sizeof(cpuset), & cpuset) !=0) { perror("sched_setaffinity");

exit(EXIT_FAILURE);

}

/* Passer en temps-reel */

param.sched_priority = 99;

if (sched_setscheduler(0, SCHED_FIFO, & param) != 0) { perror("sched_setscheduler");

exit(EXIT_FAILURE);

}

/* Dormir une seconde pour etre sur que tous les autres processus sont sur leurs CPUs respectifs */

sleep(1);

/* Programmer une alarme dans 15 secondes */

alarm(15);

/* Boucler activement */

while (1);

}

Avant de lancer ce programme, il faut savoir que certains environnements graphiques n'aiment pas être privés de temps CPU pendant plusieurs secondes et peuvent se

comporter bizarrement (envoyer des rafales de retours chariot dans les terminaux texte, par exemple). De même, on peut constater des déconnexions d'adaptateur Wi-Fi si le temps dépasse 20 à 30 secondes. Il est conseillé de tester le programme sur un poste de travail indépendant, que vous pourrez réinitialiser sans gêner d'autres utilisateurs.

Un bon réflexe à acquérir lorsqu'on se prépare à démarrer un programme d'expérimentation en temps réel est de lancer auparavant depuis le shell la commande « sync ». Celle-ci

s'assurera que tous les contenus des buffers de cache disque en attente ont été transmis aux pilotes de périphériques. On limite ainsi la perte de données en cas de nécessité de

réinitialisation abrupte.

(7)

Affichage en console après exécution

Le passage en temps réel échoue si vous n'avez pas les droits « root ».

Avec les droits « root » : après une période de 15 secondes pendant laquelle tout le système était entièrement gelé, nous reprenons normalement la main sur le système.

Mais, est-ce bien vrai? Le système est-il effectivement gelé pendant 15 secondes ? Recommen- cez plusieurs fois l'expérience et vous pourrez remarquer que le pointeur de la souris arrive à se déplacer (de façon très saccadée), que le curseur du shell continue à clignoter et que l'horloge graphique avance (en sautant parfois quelques secondes).

L’expérience montre que les applications tournent en ordonnancement temps partagé, malgré les boucles temps réel Fifo de priorités très élevées.

GARDE-FOU TEMPS RÉEL

Depuis la version 2.6.25 de Linux, sortie en avril 2008, une petite partie (5 %) du temps CPU dis- ponible est réservée aux tâches temps partagé, même lorsque des tâches temps réel s'exécutent ! Ce comportement qui avait été introduit sans grande publicité dans le nouveau noyau viole bien entendu les principes mêmes des systèmes temps réel. Il est conçu comme une sorte de garde- fou qui protège l'usager inconscient contre les boucles infinies qu'il aurait malencontreusement lancées sous un ordonnancement temps réel. Bien sûr, cela peut s'avérer très utile lors de la mise au point d'un système temps réel ou pour son débogage. C'est une option active par défaut. Les noyaux Linux depuis le 2.6.25 adoptent une attitude ultra prudente, préférant, par défaut, pénaliser le comportement temps réel plutôt que de risquer un déni de service par une boucle infinie.

Heureusement, il est possible de « débrayer » ce garde-fou, en écrivant dans un pseudo-fichier de

« /proc », même si l'on peut regretter qu'il n'y ait pas une option pour désactiver ce comportement par défaut lors de la compilation du noyau. Le fichier « /proc/sys/kernel/sched_ rt_ period_us » représente une période, exprimée en microsecondes, que partagent les tâches temps réel et les tâches temps partagé. Sa durée par défaut est 1 000 000 s, ce qui correspond à 1 seconde.

Dans cette période, on réserve aux tâches temps réel la durée indiquée dans « /proc/sys/kernel/

sched_rt_runtime_us ». Par défaut, cette durée vaut 950 000 s. Ainsi, pour chaque seconde, on réserve 50 millisecondes pour les tâches temps partagé, et les tâches temps réel peuvent consommer jusqu'à 950 millisecondes.

(8)

Garde-fou du noyau Linux

Afin de désactiver ce comportement, il suffit d'écrire la valeur « -1 » dans le pseudo-fichier

« sched_rt_ runtime_us ». L’opération peut se faire dès le boot du système, dans un script de démarrage par exemple. Cette opération peut être réalisée aisément à l'aide de la commande

« sysctl » qui fonctionne également lorsqu'on doit la préfixer de sudo.

Testons donc nos boucles temps réel parallèles sans laisser de temps CPU pour les autres tâches :

# sysctl kernel.sched_rt_runtime_us=-1 kernel.sched_rt_runtime_us=-1

# ./exemple-boucle-temps-reel

Cette fois, aucune activité ne peut s'exécuter pendant 15 secondes, le système est complètement figé.

Références

Documents relatifs

For quantitative atan probe analysis of Silicon-containing alloys, it appears that a pulse fraction of at least 15% is required and the analysis inside the central ring of low

In the current study, we tested a Zebrafish Embryonic Toxicity (ZET) exposure paradigm from 6–120 hpf that is similar to those used in some of the previous studies [ 14 , 15 ]

Features of the MDE, including bipolar symptoms listed in the DSM-IV-TR (24) diagnostic criteria for BD, known risk factors for BD (e.g., family history of BD and

This report describes a series of 5 full-scale Class B fire tests designed to compare low and high water flow conditions with the normal CAF design flow condition and a standard

The column units and infill units are placed along the depth, tack welded to column units and to the capital units. All the joints are

Le groupe Al-Kashi, seul groupe spécialisé en cours de répétions à domicile et aux préparations à domicile aux concours d’entrée dans les grandes écoles scientifiques...

The second paper conducted a study using Chickering and Gamson’s research on good principles for undergraduate teaching as a framework to explore instructors’ and students’

The concern for mechanical reliability exists in present day 2D ICs containing millions of transistors on a microprocessor chip, where the interconnect layers are densely stacked