Introduction
Sous Linux, plusieurs processus et threads peuvent être prêts à s’exécuter en même temps. Le noyau doit donc décider :
- quel processus obtient le processeur ;
- pendant combien de temps il peut s’exécuter ;
- à quel niveau de priorité ;
- sur quel processeur ou cœur il est autorisé à fonctionner.
Ce chapitre présente trois notions principales :
- les priorités classiques avec les valeurs nice ;
- les politiques d’ordonnancement temps réel ;
- l’affinité processeur.
1.1 Principe général
Linux exécute habituellement les processus selon un modèle de partage du temps.
Chaque processus prêt à s’exécuter reçoit périodiquement une portion de temps processeur appelée quantum ou tranche de temps.
L’objectif est de garantir :
- une certaine équité entre les processus ;
- une bonne réactivité du système ;
- l’absence de blocage permanent des processus de faible priorité.
1.2 Valeurs nice
La plage des valeurs nice est généralement :
Code: Select all
-20 à +19Code: Select all
-20 : priorité classique la plus élevée
0 : priorité par défaut
+19 : priorité classique la plus faibleUne forte valeur nice signifie que le processus laisse davantage de temps processeur aux autres processus.
Attention : la valeur nice ne crée pas une hiérarchie absolue. Un processus ayant une faible priorité peut toujours recevoir du temps CPU.
1.3 Fonction nice()
Prototype :
Code: Select all
#include <unistd.h>
int nice(int increment);
Exemple :
Code: Select all
if (nice(5) == -1) {
perror("nice");
}
Pour augmenter réellement la priorité, il faut diminuer la valeur nice. Cette opération nécessite généralement des privilèges particuliers.
La valeur de retour peut être -1 sans qu’une erreur ait eu lieu. Pour détecter correctement une erreur, il faut consulter errno.
Exemple :
Code: Select all
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
errno = 0;
int value = nice(1);
if (value == -1 && errno != 0) {
perror("nice");
}
2.1 getpriority()
Prototype :
Code: Select all
#include <sys/resource.h>
int getpriority(int which, id_t who);
Le paramètre which peut prendre les valeurs suivantes :
Code: Select all
PRIO_PROCESS
PRIO_PGRP
PRIO_USER
PRIO_PGRP cible un groupe de processus.
PRIO_USER cible les processus appartenant à un utilisateur.
Le paramètre who contient selon le cas :
- un PID ;
- un identifiant de groupe de processus ;
- un UID.
Exemple :
Code: Select all
#include <errno.h>
#include <stdio.h>
#include <sys/resource.h>
errno = 0;
int prio = getpriority(PRIO_PROCESS, 0);
if (prio == -1 && errno != 0) {
perror("getpriority");
} else {
printf("Valeur nice : %d\n", prio);
}
Prototype :
Code: Select all
#include <sys/resource.h>
int setpriority(int which, id_t who, int prio);
Exemple :
Code: Select all
#include <stdio.h>
#include <sys/resource.h>
if (setpriority(PRIO_PROCESS, 0, 10) == -1) {
perror("setpriority");
}
2.3 Permissions
Un processus ordinaire peut généralement :
- augmenter sa valeur nice ;
- réduire sa priorité ;
- modifier certains processus appartenant au même utilisateur.
- la capacité CAP_SYS_NICE ;
- ou une limite RLIMIT_NICE suffisante.
3.1 Besoins d’une application temps réel
Une application temps réel peut devoir garantir :
- un temps de réponse maximal ;
- une exécution prioritaire ;
- une réduction des retards imprévisibles ;
- une maîtrise de la mémoire et du processeur.
Code: Select all
mlock()
mlockall()
Cela signifie que les politiques temps réel améliorent fortement la priorité et la prévisibilité, mais ne garantissent pas nécessairement toutes les contraintes d’un système temps réel dur.
3.2 Priorités temps réel
Sous Linux, les priorités temps réel sont généralement comprises entre :
Code: Select all
1 et 99- plus la priorité temps réel est grande, plus le processus est prioritaire ;
- une tâche temps réel exécutable passe avant une tâche classique ;
- parmi les tâches temps réel, la priorité la plus élevée est choisie en premier.
Code: Select all
Nice :
-20 = priorité élevée
+19 = priorité faible
Temps réel :
99 = priorité élevée
1 = priorité faible
4.1 SCHED_OTHER
SCHED_OTHER correspond à la politique d’ordonnancement normale de Linux.
Elle est utilisée par la majorité des processus.
La priorité temps réel associée dans struct sched_param doit être égale à 0.
4.2 SCHED_BATCH
SCHED_BATCH est adaptée aux traitements longs et non interactifs.
Exemples :
- calculs lourds ;
- compression ;
- encodage ;
- traitement de données ;
- tâches d’arrière-plan.
4.3 SCHED_IDLE
SCHED_IDLE donne une priorité extrêmement faible.
Le processus ne s’exécute que lorsque les autres tâches ont peu ou pas besoin du processeur.
Cette politique est encore moins prioritaire qu’un processus ayant une valeur nice élevée.
4.4 SCHED_RR
SCHED_RR signifie Round-Robin temps réel.
Les tâches ayant la même priorité se partagent le processeur à tour de rôle.
Chaque tâche reçoit une tranche de temps.
Une tâche SCHED_RR cesse d’utiliser le processeur lorsqu’elle :
- bloque ;
- termine ;
- appelle sched_yield() ;
- épuise son quantum ;
- est préemptée par une tâche de priorité supérieure.
4.5 SCHED_FIFO
SCHED_FIFO signifie First-In, First-Out temps réel.
Cette politique ne possède pas de quantum.
Une tâche SCHED_FIFO continue de s’exécuter jusqu’à ce qu’elle :
- bloque ;
- termine ;
- appelle sched_yield() ;
- soit préemptée par une tâche de priorité supérieure.
Deux tâches SCHED_FIFO de même priorité ne tournent pas automatiquement à tour de rôle.
5. PRIORITÉS MINIMALES ET MAXIMALES
5.1 sched_get_priority_min()
Prototype :
Code: Select all
#include <sched.h>
int sched_get_priority_min(int policy);
5.2 sched_get_priority_max()
Prototype :
Code: Select all
#include <sched.h>
int sched_get_priority_max(int policy);
Exemple :
Code: Select all
#include <sched.h>
#include <stdio.h>
int min = sched_get_priority_min(SCHED_FIFO);
int max = sched_get_priority_max(SCHED_FIFO);
printf("Priorité minimale : %d\n", min);
printf("Priorité maximale : %d\n", max);
6. STRUCTURE sched_param
La structure sched_param contient la priorité d’ordonnancement.
Code: Select all
struct sched_param {
int sched_priority;
};
Pour les politiques classiques comme SCHED_OTHER, cette valeur doit normalement être égale à 0.
7. MODIFIER LA POLITIQUE ET LA PRIORITÉ
7.1 sched_setscheduler()
Prototype :
Code: Select all
#include <sched.h>
int sched_setscheduler(
pid_t pid,
int policy,
const struct sched_param *param
);
- la politique d’ordonnancement ;
- la priorité associée.
Exemple avec SCHED_RR :
Code: Select all
#include <sched.h>
#include <stdio.h>
int main(void)
{
struct sched_param param;
param.sched_priority = 20;
if (sched_setscheduler(0, SCHED_RR, ¶m) == -1) {
perror("sched_setscheduler");
return 1;
}
return 0;
}
7.2 sched_getscheduler()
Prototype :
Code: Select all
#include <sched.h>
int sched_getscheduler(pid_t pid);
Exemple :
Code: Select all
#include <sched.h>
#include <stdio.h>
int policy = sched_getscheduler(0);
if (policy == -1) {
perror("sched_getscheduler");
} else if (policy == SCHED_OTHER) {
printf("SCHED_OTHER\n");
} else if (policy == SCHED_FIFO) {
printf("SCHED_FIFO\n");
} else if (policy == SCHED_RR) {
printf("SCHED_RR\n");
} else if (policy == SCHED_BATCH) {
printf("SCHED_BATCH\n");
} else if (policy == SCHED_IDLE) {
printf("SCHED_IDLE\n");
}
8.1 sched_setparam()
Prototype :
Code: Select all
#include <sched.h>
int sched_setparam(
pid_t pid,
const struct sched_param *param
);
Exemple :
Code: Select all
struct sched_param param;
param.sched_priority = 30;
if (sched_setparam(0, ¶m) == -1) {
perror("sched_setparam");
}
Prototype :
Code: Select all
#include <sched.h>
int sched_getparam(
pid_t pid,
struct sched_param *param
);
Exemple :
Code: Select all
#include <sched.h>
#include <stdio.h>
struct sched_param param;
if (sched_getparam(0, ¶m) == -1) {
perror("sched_getparam");
} else {
printf("Priorité : %d\n", param.sched_priority);
}
9.1 CAP_SYS_NICE
La capacité CAP_SYS_NICE permet notamment :
- d’augmenter les priorités ;
- d’utiliser certaines politiques temps réel ;
- de modifier l’ordonnancement d’autres processus ;
- de modifier certaines affinités processeur.
RLIMIT_RTPRIO limite la priorité temps réel maximale qu’un processus non privilégié peut demander.
Cela empêche un programme ordinaire d’obtenir librement une priorité très élevée.
9.3 RLIMIT_NICE
RLIMIT_NICE contrôle jusqu’à quel point un processus non privilégié peut diminuer sa valeur nice et donc augmenter sa priorité classique.
10. EMPÊCHER UNE TÂCHE TEMPS RÉEL DE BLOQUER LE SYSTÈME
Une tâche temps réel mal conçue peut monopoliser le processeur.
Plusieurs mécanismes peuvent limiter ce risque.
10.1 RLIMIT_CPU
RLIMIT_CPU limite la quantité totale de temps CPU consommée par un processus.
Une fois la limite atteinte, le processus reçoit un signal.
10.2 RLIMIT_RTTIME
RLIMIT_RTTIME limite le temps CPU qu’une tâche temps réel peut consommer sans effectuer un appel système bloquant.
Cette limite est particulièrement utile avec SCHED_FIFO et SCHED_RR.
10.3 Alarmes et temporisateurs
Un programme peut utiliser :
Code: Select all
alarm()
setitimer()
timer_create()
10.4 Thread watchdog
Un thread de surveillance peut contrôler une tâche temps réel et :
- réduire sa priorité ;
- changer sa politique ;
- lui envoyer un signal ;
- terminer le programme en cas de blocage.
Le drapeau SCHED_RESET_ON_FORK évite qu’un enfant hérite dangereusement d’une politique temps réel privilégiée.
Exemple :
Code: Select all
sched_setscheduler(
0,
SCHED_FIFO | SCHED_RESET_ON_FORK,
¶m
);
11. LIBÉRER VOLONTAIREMENT LE PROCESSEUR
11.1 sched_yield()
Prototype :
Code: Select all
#include <sched.h>
int sched_yield(void);
Exemple :
Code: Select all
if (sched_yield() == -1) {
perror("sched_yield");
}
sched_yield() ne doit pas être utilisée comme remplacement d’un mutex, d’une condition ou d’un sémaphore.
12. OBTENIR LE QUANTUM DE SCHED_RR
12.1 sched_rr_get_interval()
Prototype :
Code: Select all
#include <sched.h>
int sched_rr_get_interval(
pid_t pid,
struct timespec *tp
);
La durée est stockée dans une structure timespec.
Code: Select all
struct timespec {
time_t tv_sec;
long tv_nsec;
};
Code: Select all
#include <sched.h>
#include <stdio.h>
struct timespec ts;
if (sched_rr_get_interval(0, &ts) == -1) {
perror("sched_rr_get_interval");
} else {
printf(
"Quantum : %ld s et %ld ns\n",
ts.tv_sec,
ts.tv_nsec
);
}
13.1 Définition
L’affinité processeur définit les processeurs sur lesquels une tâche est autorisée à s’exécuter.
Sans restriction particulière, l’ordonnanceur peut déplacer une tâche d’un CPU à un autre.
Cette migration peut provoquer :
- une perte de données utiles dans les caches ;
- des rechargements du TLB ;
- une moins bonne localité mémoire ;
- des performances moins prévisibles.
- de fixer une tâche sur un cœur ;
- de limiter une tâche à plusieurs cœurs ;
- de réduire les migrations ;
- d’isoler des charges de travail ;
- d’améliorer la stabilité d’une application temps réel.
13.2 sched_setaffinity()
Prototype :
Code: Select all
#define _GNU_SOURCE
#include <sched.h>
int sched_setaffinity(
pid_t pid,
size_t cpusetsize,
const cpu_set_t *mask
);
Lorsque pid vaut 0, elle cible le thread appelant.
13.3 sched_getaffinity()
Prototype :
Code: Select all
#define _GNU_SOURCE
#include <sched.h>
int sched_getaffinity(
pid_t pid,
size_t cpusetsize,
cpu_set_t *mask
);
14. MANIPULATION D’UN cpu_set_t
Un ensemble de processeurs est représenté par le type cpu_set_t.
Code: Select all
cpu_set_t set;
Vide complètement l’ensemble.
Code: Select all
CPU_ZERO(&set);
Ajoute un processeur.
Code: Select all
CPU_SET(2, &set);
Retire un processeur.
Code: Select all
CPU_CLR(2, &set);
Teste si un processeur appartient à l’ensemble.
Code: Select all
if (CPU_ISSET(2, &set)) {
printf("Le CPU 2 est présent\n");
}
CPU_SETSIZE indique le nombre maximal de processeurs représentables par le type cpu_set_t statique.
15. EXEMPLE COMPLET D’AFFINITÉ CPU
Le programme suivant limite le thread appelant aux CPU 1, 2 et 3.
Code: Select all
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(1, &set);
CPU_SET(2, &set);
CPU_SET(3, &set);
if (sched_setaffinity(
0,
sizeof(set),
&set
) == -1) {
perror("sched_setaffinity");
return EXIT_FAILURE;
}
printf("Affinité modifiée avec succès\n");
return EXIT_SUCCESS;
}
Code: Select all
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
cpu_set_t set;
CPU_ZERO(&set);
if (sched_getaffinity(
0,
sizeof(set),
&set
) == -1) {
perror("sched_getaffinity");
return EXIT_FAILURE;
}
long cpu_count = sysconf(_SC_NPROCESSORS_CONF);
for (int cpu = 0; cpu < cpu_count; cpu++) {
if (CPU_ISSET(cpu, &set)) {
printf("CPU autorisé : %d\n", cpu);
}
}
return EXIT_SUCCESS;
}
Code: Select all
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
static const char *policy_to_string(int policy)
{
switch (policy) {
case SCHED_OTHER:
return "SCHED_OTHER";
case SCHED_FIFO:
return "SCHED_FIFO";
case SCHED_RR:
return "SCHED_RR";
#ifdef SCHED_BATCH
case SCHED_BATCH:
return "SCHED_BATCH";
#endif
#ifdef SCHED_IDLE
case SCHED_IDLE:
return "SCHED_IDLE";
#endif
default:
return "Politique inconnue";
}
}
int main(void)
{
int policy = sched_getscheduler(0);
if (policy == -1) {
perror("sched_getscheduler");
return EXIT_FAILURE;
}
struct sched_param param;
if (sched_getparam(0, ¶m) == -1) {
perror("sched_getparam");
return EXIT_FAILURE;
}
printf("Politique : %s\n", policy_to_string(policy));
printf("Priorité : %d\n", param.sched_priority);
return EXIT_SUCCESS;
}
Valeur nice
La valeur nice agit sur les processus utilisant l’ordonnancement classique.
Elle modifie leur part relative de temps processeur.
Un processus nice n’est normalement pas totalement privé de CPU.
Priorité temps réel
Une priorité temps réel s’applique principalement à SCHED_FIFO et SCHED_RR.
Une tâche temps réel exécutable passe avant les tâches classiques.
Une tâche mal conçue peut donc fortement perturber le système.
Résumé :
Code: Select all
Nice faible = priorité classique élevée
Nice élevée = priorité classique faible
Priorité RT forte = priorité temps réel élevée
Priorité RT faible = priorité temps réel faible
SCHED_FIFO
- aucune tranche de temps ;
- exécution jusqu’au blocage, à la fin, au yield ou à une préemption supérieure ;
- une tâche peut monopoliser un CPU ;
- les tâches de même priorité ne tournent pas automatiquement.
- possède une tranche de temps ;
- rotation entre tâches de même priorité ;
- préemption immédiate par une priorité supérieure ;
- plus adaptée lorsque plusieurs tâches temps réel ont la même priorité.
Confondre nice et priorité temps réel
Avec nice, une valeur plus faible signifie une priorité plus élevée.
Avec le temps réel, une valeur plus élevée signifie une priorité plus élevée.
Utiliser SCHED_FIFO sans blocage
Une boucle infinie sous SCHED_FIFO peut monopoliser totalement un cœur.
Utiliser sched_yield() comme synchronisation
sched_yield() ne protège aucune donnée partagée et ne garantit pas l’ordre d’exécution.
Oublier les privilèges
Les modifications de priorité et de politique peuvent échouer avec EPERM.
Supposer que l’affinité est globale au processus
Sous Linux, l’affinité est principalement gérée par thread.
Dans un programme multithread, il faut donc configurer chaque thread concerné.
Oublier CPU_ZERO()
Un cpu_set_t doit être initialisé avant l’utilisation de CPU_SET().
21. API ESSENTIELLES DU CHAPITRE
Priorités classiques
Code: Select all
nice()
getpriority()
setpriority()
Code: Select all
sched_get_priority_min()
sched_get_priority_max()
Code: Select all
sched_setscheduler()
sched_getscheduler()
sched_setparam()
sched_getparam()
Code: Select all
sched_yield()
sched_rr_get_interval()
Code: Select all
sched_setaffinity()
sched_getaffinity()
CPU_ZERO()
CPU_SET()
CPU_CLR()
CPU_ISSET()
CPU_SETSIZE
- L’ordonnanceur décide quelle tâche utilise le processeur.
- La valeur nice influence la priorité des tâches classiques.
- La plage nice va généralement de -20 à +19.
- Une valeur nice faible correspond à une priorité élevée.
- SCHED_OTHER est la politique normale.
- SCHED_BATCH vise les traitements non interactifs.
- SCHED_IDLE donne une priorité extrêmement faible.
- SCHED_FIFO est une politique temps réel sans quantum.
- SCHED_RR est une politique temps réel avec quantum.
- Les tâches temps réel ont priorité sur les tâches classiques.
- sched_setscheduler() modifie la politique et la priorité.
- sched_setparam() modifie uniquement la priorité.
- sched_yield() libère volontairement le processeur.
- sched_rr_get_interval() retourne le quantum de SCHED_RR.
- sched_setaffinity() définit les CPU autorisés.
- sched_getaffinity() récupère le masque d’affinité.
- L’affinité CPU est principalement gérée par thread sous Linux.
- CAP_SYS_NICE est nécessaire pour de nombreuses opérations privilégiées.
- Une tâche temps réel mal conçue peut bloquer ou ralentir fortement le système.
