PRIORITÉS ET ORDONNANCEMENT DES PROCESSUS SOUS LINUX

Ce forum est dédié à apprendre le développement de programmes user mode sur Linux

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 54
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

PRIORITÉS ET ORDONNANCEMENT DES PROCESSUS SOUS LINUX

Post by Hydraxx »

PRIORITÉS ET ORDONNANCEMENT DES PROCESSUS SOUS LINUX

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.
Cette décision est prise par l’ordonnanceur du noyau.

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. ORDONNANCEMENT CLASSIQUE ET VALEURS NICE

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é.
La priorité classique d’un processus est influencée par sa valeur nice.

1.2 Valeurs nice

La plage des valeurs nice est généralement :

Code: Select all

-20 à +19
Interprétation :

Code: Select all

-20 : priorité classique la plus élevée
  0 : priorité par défaut
+19 : priorité classique la plus faible
Une faible valeur nice correspond donc à une priorité plus élevée.

Une 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);
La fonction nice() ajoute la valeur increment à la valeur nice du processus appelant.

Exemple :

Code: Select all

if (nice(5) == -1) {
    perror("nice");
}
Dans cet exemple, la valeur nice augmente de 5. Le processus devient donc moins prioritaire.

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. LIRE ET MODIFIER UNE VALEUR NICE

2.1 getpriority()

Prototype :

Code: Select all

#include <sys/resource.h>

int getpriority(int which, id_t who);
Cette fonction retourne la valeur nice associée à un processus, un groupe de processus ou un utilisateur.

Le paramètre which peut prendre les valeurs suivantes :

Code: Select all

PRIO_PROCESS
PRIO_PGRP
PRIO_USER
PRIO_PROCESS cible un processus.

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.
Lorsque who vaut 0, la cible correspond généralement à l’objet associé au processus appelant.

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);
}
2.2 setpriority()

Prototype :

Code: Select all

#include <sys/resource.h>

int setpriority(int which, id_t who, int prio);
Cette fonction définit directement une valeur nice.

Exemple :

Code: Select all

#include <stdio.h>
#include <sys/resource.h>

if (setpriority(PRIO_PROCESS, 0, 10) == -1) {
    perror("setpriority");
}
Le processus appelant reçoit ici une valeur nice égale à 10.

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.
En revanche, diminuer la valeur nice et donc augmenter la priorité nécessite généralement :
  • la capacité CAP_SYS_NICE ;
  • ou une limite RLIMIT_NICE suffisante.
3. INTRODUCTION À L’ORDONNANCEMENT TEMPS RÉEL

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.
Pour réduire les défauts de page, elle peut verrouiller sa mémoire avec :

Code: Select all

mlock()
mlockall()
Linux fournit principalement un modèle temps réel souple.

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
Contrairement aux valeurs nice :
  • 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.
Comparaison :

Code: Select all

Nice :
-20 = priorité élevée
+19 = priorité faible

Temps réel :
99 = priorité élevée
1  = priorité faible
4. LES PRINCIPALES POLITIQUES D’ORDONNANCEMENT

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.
Elle indique au noyau que le programme n’a pas besoin d’une forte réactivité interactive.

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.
Lorsqu’elle épuise son quantum, elle est replacée à la fin de la file correspondant à sa priorité.

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.
Une tâche SCHED_FIFO peut donc monopoliser un cœur si elle ne bloque jamais.

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);
Cette fonction retourne la priorité minimale autorisée pour une politique.

5.2 sched_get_priority_max()

Prototype :

Code: Select all

#include <sched.h>

int sched_get_priority_max(int policy);
Cette fonction retourne la priorité maximale autorisée.

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);
Il est préférable de toujours utiliser ces fonctions au lieu de supposer que la plage est forcément de 1 à 99.

6. STRUCTURE sched_param

La structure sched_param contient la priorité d’ordonnancement.

Code: Select all

struct sched_param {
    int sched_priority;
};
Pour SCHED_FIFO et SCHED_RR, sched_priority contient la priorité temps réel.

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
);
Cette fonction modifie :
  • la politique d’ordonnancement ;
  • la priorité associée.
Lorsque pid vaut 0, la fonction cible le processus appelant.

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, &param) == -1) {
        perror("sched_setscheduler");
        return 1;
    }

    return 0;
}
Cette opération nécessite généralement des privilèges.

7.2 sched_getscheduler()

Prototype :

Code: Select all

#include <sched.h>

int sched_getscheduler(pid_t pid);
Cette fonction retourne la politique d’ordonnancement utilisée par la tâche.

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. MODIFIER UNIQUEMENT LA PRIORITÉ

8.1 sched_setparam()

Prototype :

Code: Select all

#include <sched.h>

int sched_setparam(
    pid_t pid,
    const struct sched_param *param
);
Cette fonction modifie la priorité sans changer la politique.

Exemple :

Code: Select all

struct sched_param param;

param.sched_priority = 30;

if (sched_setparam(0, &param) == -1) {
    perror("sched_setparam");
}
8.2 sched_getparam()

Prototype :

Code: Select all

#include <sched.h>

int sched_getparam(
    pid_t pid,
    struct sched_param *param
);
Cette fonction récupère la priorité actuelle.

Exemple :

Code: Select all

#include <sched.h>
#include <stdio.h>

struct sched_param param;

if (sched_getparam(0, &param) == -1) {
    perror("sched_getparam");
} else {
    printf("Priorité : %d\n", param.sched_priority);
}
9. PERMISSIONS ET LIMITES TEMPS RÉEL

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.
9.2 RLIMIT_RTPRIO

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()
Ces mécanismes permettent de détecter un dépassement de temps.

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.
10.5 SCHED_RESET_ON_FORK

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,
    &param
);
Après fork(), l’enfant récupère une politique ou une priorité plus sûre.

11. LIBÉRER VOLONTAIREMENT LE PROCESSEUR

11.1 sched_yield()

Prototype :

Code: Select all

#include <sched.h>

int sched_yield(void);
Cette fonction demande à l’ordonnanceur de laisser un autre processus ou thread exécutable utiliser le processeur.

Exemple :

Code: Select all

if (sched_yield() == -1) {
    perror("sched_yield");
}
Dans une politique temps réel, le thread appelant est replacé dans la file correspondant à sa priorité.

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
);
Cette fonction retourne la durée du quantum attribué à une tâche SCHED_RR.

La durée est stockée dans une structure timespec.

Code: Select all

struct timespec {
    time_t tv_sec;
    long tv_nsec;
};
Exemple :

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. AFFINITÉ PROCESSEUR

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.
L’affinité peut permettre :
  • 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.
Sous Linux, l’affinité est en pratique un attribut par thread.

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
);
Cette fonction modifie le masque d’affinité.

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
);
Cette fonction récupère le masque d’affinité.

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;
14.1 CPU_ZERO()

Vide complètement l’ensemble.

Code: Select all

CPU_ZERO(&set);
14.2 CPU_SET()

Ajoute un processeur.

Code: Select all

CPU_SET(2, &set);
14.3 CPU_CLR()

Retire un processeur.

Code: Select all

CPU_CLR(2, &set);
14.4 CPU_ISSET()

Teste si un processeur appartient à l’ensemble.

Code: Select all

if (CPU_ISSET(2, &set)) {
    printf("Le CPU 2 est présent\n");
}
14.5 CPU_SETSIZE

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;
}
16. LIRE L’AFFINITÉ DU THREAD APPELANT

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;
}
17. EXEMPLE COMPLET DE LECTURE DE LA POLITIQUE

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, &param) == -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;
}
18. DIFFÉRENCE ENTRE NICE ET TEMPS RÉEL

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
19. DIFFÉRENCE ENTRE SCHED_FIFO ET SCHED_RR

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.
SCHED_RR
  • 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é.
20. ERREURS COURANTES

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()
Priorités temps réel

Code: Select all

sched_get_priority_min()
sched_get_priority_max()
Politique et priorité

Code: Select all

sched_setscheduler()
sched_getscheduler()
sched_setparam()
sched_getparam()
Contrôle de l’exécution

Code: Select all

sched_yield()
sched_rr_get_interval()
Affinité processeur

Code: Select all

sched_setaffinity()
sched_getaffinity()
CPU_ZERO()
CPU_SET()
CPU_CLR()
CPU_ISSET()
CPU_SETSIZE
22. RÉCAPITULATIF FINAL
  • 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.

Who is online

Users browsing this forum: No registered users and 1 guest