Groupes de processus, sessions et contrôle des tâches 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:

Groupes de processus, sessions et contrôle des tâches sous Linux

Post by Hydraxx »

Groupes de processus, sessions et contrôle des tâches sous Linux

Introduction

Sous Linux et plus généralement sous les systèmes UNIX, les processus ne sont pas seulement organisés individuellement.

Ils peuvent être regroupés dans des groupes de processus, eux-mêmes rassemblés dans des sessions.

Cette organisation permet notamment au shell de gérer :
  • les commandes exécutées au premier plan ;
  • les commandes exécutées en arrière-plan ;
  • les pipelines ;
  • les tâches suspendues ;
  • les tâches reprises avec fg ou bg ;
  • les signaux générés par le terminal ;
  • la fermeture d’un terminal.
L’organisation générale est la suivante :

Code: Select all

Session
└── Groupes de processus
    └── Processus
Une session peut posséder un terminal de contrôle.

Parmi les groupes de processus de cette session, un seul peut être le groupe de processus au premier plan du terminal.

Les autres groupes sont considérés comme étant en arrière-plan.


1. Les groupes de processus

1.1 Définition

Un groupe de processus est un ensemble d’un ou plusieurs processus qui partagent le même identifiant de groupe de processus.

Cet identifiant est appelé :

Code: Select all

PGID
Chaque processus possède donc :
  • un PID ;
  • un PPID ;
  • un PGID ;
  • un SID.
Le premier processus d’un groupe est appelé le chef de groupe de processus.

Pour ce processus :

Code: Select all

PID == PGID
Lorsqu’un processus est créé avec fork(), il hérite initialement du groupe de processus de son parent.

Cela permet au shell de placer plusieurs processus dans un même groupe, notamment lorsqu’ils appartiennent au même pipeline.

Exemple :

Code: Select all

cat fichier.txt | grep erreur | less
Même si cette commande crée plusieurs processus, le shell peut traiter tout le pipeline comme une seule tâche.

Les trois processus peuvent donc partager le même PGID.


1.2 Obtenir le groupe du processus appelant

Code: Select all

#include <unistd.h>

pid_t getpgrp(void);
La fonction getpgrp() retourne le PGID du processus appelant.

Exemple :

Code: Select all

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());

    return 0;
}

1.3 Obtenir le groupe d’un processus précis

Code: Select all

#include <unistd.h>

pid_t getpgid(pid_t pid);
La fonction getpgid() retourne le PGID du processus indiqué.

Si pid vaut 0, la fonction retourne le PGID du processus appelant.

Exemple :

Code: Select all

pid_t pgid = getpgid(0);

if (pgid == -1) {
    perror("getpgid");
}

1.4 Créer ou modifier un groupe de processus

Code: Select all

#include <unistd.h>

int setpgid(pid_t pid, pid_t pgid);
La fonction setpgid() permet de modifier le groupe de processus d’un processus.

Signification des paramètres :
  • pid == 0 : le processus appelant est ciblé ;
  • pgid == 0 : le PID du processus ciblé est utilisé comme PGID.
L’appel suivant crée un nouveau groupe de processus dont le processus appelant devient le chef :

Code: Select all

setpgid(0, 0);
Après réussite :

Code: Select all

PID == PGID
Exemple :

Code: Select all

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    if (setpgid(0, 0) == -1) {
        perror("setpgid");
        return 1;
    }

    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());

    return 0;
}

1.5 Restrictions de setpgid()

Un processus ne peut pas déplacer librement n’importe quel autre processus.

Les principales restrictions sont les suivantes :
  • le processus ciblé doit appartenir à la même session ;
  • un processus peut généralement modifier son propre groupe ;
  • un parent peut modifier le groupe de l’un de ses enfants avant que celui-ci n’appelle exec() ;
  • un chef de session ne peut pas changer de groupe de processus ;
  • un processus ne peut pas rejoindre un groupe appartenant à une autre session.
Lorsqu’un shell crée un pipeline, le parent et l’enfant peuvent tous les deux appeler setpgid().

Cette technique évite une condition de compétition.

Peu importe lequel s’exécute en premier : les processus du pipeline finissent dans le bon groupe.


2. Les sessions

2.1 Définition

Une session est un ensemble de groupes de processus.

L’identifiant d’une session est appelé :

Code: Select all

SID
Le processus qui crée la session devient le chef de session.

Pour le chef de session :

Code: Select all

PID == PGID == SID
Une session est généralement utilisée pour représenter une connexion utilisateur ou un environnement terminal complet.

Elle peut contenir :
  • le shell ;
  • le groupe de processus du shell ;
  • plusieurs groupes de processus correspondant à des tâches ;
  • un terminal de contrôle.

2.2 Obtenir l’identifiant de session

Code: Select all

#include <unistd.h>

pid_t getsid(pid_t pid);
La fonction getsid() retourne le SID du processus indiqué.

Si pid vaut 0, elle retourne le SID du processus appelant.

Exemple :

Code: Select all

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());
    printf("SID  : %ld\n", (long)getsid(0));

    return 0;
}

2.3 Créer une nouvelle session

Code: Select all

#include <unistd.h>

pid_t setsid(void);
La fonction setsid() effectue plusieurs opérations :
  • elle crée une nouvelle session ;
  • elle crée un nouveau groupe de processus ;
  • elle fait du processus appelant le chef de session ;
  • elle fait du processus appelant le chef du nouveau groupe ;
  • elle détache initialement le processus de tout terminal de contrôle.
Après réussite :

Code: Select all

PID == PGID == SID

2.4 Pourquoi setsid() peut échouer

setsid() échoue si le processus appelant est déjà chef d’un groupe de processus.

La technique classique consiste donc à créer un enfant avec fork(), puis à terminer le parent.

L’enfant hérite de l’ancien PGID, mais possède un nouveau PID.

Il n’est donc normalement pas chef de groupe et peut appeler setsid().

Exemple :

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        return 1;
    }

    if (pid > 0) {
        _exit(EXIT_SUCCESS);
    }

    if (setsid() == -1) {
        perror("setsid");
        return 1;
    }

    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());
    printf("SID  : %ld\n", (long)getsid(0));

    return 0;
}
Cette technique est notamment utilisée pendant la création d’un daemon.


3. Le terminal de contrôle

3.1 Définition

Une session peut posséder un unique terminal de contrôle.

Le terminal de contrôle permet :
  • de fournir les entrées et sorties interactives ;
  • de produire des signaux à partir du clavier ;
  • de distinguer les processus au premier plan et en arrière-plan ;
  • de détecter la fermeture ou la déconnexion du terminal.
Les processus appartenant à une session associée à ce terminal sont appelés les processus contrôlés par ce terminal.


3.2 Acquisition d’un terminal de contrôle

Un chef de session n’ayant pas encore de terminal de contrôle peut, selon le système, en acquérir un en ouvrant un terminal.

L’opération TIOCSCTTY permet de demander explicitement qu’un terminal devienne le terminal de contrôle de la session.

Exemple conceptuel :

Code: Select all

#include <sys/ioctl.h>

ioctl(fd, TIOCSCTTY, 0);
Le descripteur fd doit représenter le terminal concerné.


3.3 Obtenir le nom du terminal de contrôle

Code: Select all

#include <stdio.h>

char *ctermid(char *s);
La fonction ctermid() retourne une chaîne représentant le chemin du terminal de contrôle.

Exemple :

Code: Select all

#include <stdio.h>

int main(void)
{
    char terminal[L_ctermid];

    if (ctermid(terminal) != NULL) {
        printf("Terminal de contrôle : %s\n", terminal);
    }

    return 0;
}

3.4 Obtenir le terminal associé à un descripteur

Code: Select all

#include <unistd.h>

char *ttyname(int fd);
La fonction ttyname() retourne le nom du terminal associé à un descripteur.

Exemple :

Code: Select all

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    char *name = ttyname(STDIN_FILENO);

    if (name != NULL) {
        printf("Terminal de stdin : %s\n", name);
    } else {
        perror("ttyname");
    }

    return 0;
}

3.5 Vérifier qu’un descripteur représente un terminal

Code: Select all

#include <unistd.h>

int isatty(int fd);
La fonction isatty() retourne une valeur non nulle si le descripteur correspond à un terminal.

Exemple :

Code: Select all

if (isatty(STDIN_FILENO)) {
    printf("stdin est un terminal\n");
}

4. Premier plan et arrière-plan

4.1 Groupe de processus au premier plan

Dans une session associée à un terminal, un seul groupe de processus peut être au premier plan.

Les processus de ce groupe peuvent normalement :
  • lire depuis le terminal ;
  • recevoir les signaux générés par le clavier ;
  • interagir directement avec l’utilisateur.
Tous les autres groupes de la session sont en arrière-plan.


4.2 Groupe de processus en arrière-plan

Les processus en arrière-plan peuvent continuer à s’exécuter.

Cependant, ils ne peuvent pas toujours utiliser librement le terminal.

Une tentative de lecture depuis le terminal peut provoquer :

Code: Select all

SIGTTIN
Une écriture peut provoquer :

Code: Select all

SIGTTOU
Le comportement de SIGTTOU dépend de la configuration du terminal, notamment du drapeau TOSTOP.


4.3 Obtenir le groupe au premier plan

Code: Select all

#include <unistd.h>

pid_t tcgetpgrp(int fd);
La fonction tcgetpgrp() retourne le PGID du groupe actuellement au premier plan du terminal indiqué.

Exemple :

Code: Select all

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    pid_t pgid = tcgetpgrp(STDIN_FILENO);

    if (pgid == -1) {
        perror("tcgetpgrp");
        return 1;
    }

    printf("PGID au premier plan : %ld\n", (long)pgid);

    return 0;
}

4.4 Modifier le groupe au premier plan

Code: Select all

#include <unistd.h>

int tcsetpgrp(int fd, pid_t pgid);
La fonction tcsetpgrp() donne le premier plan du terminal au groupe indiqué.

Le groupe ciblé doit appartenir à la même session.

Le shell utilise cette fonction lorsqu’il :
  • lance une commande au premier plan ;
  • place une tâche au premier plan avec fg ;
  • récupère le contrôle du terminal après la fin d’une tâche ;
  • reprend le contrôle après la suspension d’une tâche.

5. Les signaux générés par le terminal

5.1 Principe

Les combinaisons de touches du terminal ne ciblent généralement pas un processus isolé.

Elles provoquent l’envoi d’un signal à tout le groupe de processus au premier plan.

Cela est indispensable pour les pipelines.

Exemple :

Code: Select all

cat fichier | grep erreur | less
Si l’utilisateur appuie sur Ctrl+C, tout le pipeline doit être interrompu.


5.2 Signaux principaux
  • Ctrl+C produit généralement SIGINT ;
  • Ctrl+\ produit généralement SIGQUIT ;
  • Ctrl+Z produit généralement SIGTSTP.
Autres signaux importants :
  • SIGSTOP arrête un processus et ne peut être ni intercepté ni ignoré ;
  • SIGCONT reprend un processus arrêté ;
  • SIGTTIN peut arrêter un groupe d’arrière-plan qui tente de lire le terminal ;
  • SIGTTOU peut arrêter un groupe d’arrière-plan effectuant certaines opérations sur le terminal ;
  • SIGHUP signale généralement une déconnexion ou une fermeture du terminal.

6. Le signal SIGHUP

6.1 Signification

SIGHUP signifie historiquement hangup.

Il correspond à la perte de la connexion avec le terminal.

Il peut être envoyé lorsque :
  • une connexion terminal est interrompue ;
  • une fenêtre de terminal est fermée ;
  • le processus contrôlant une session se termine ;
  • un groupe de processus arrêté devient orphelin.
L’action par défaut de SIGHUP est généralement la terminaison du processus.


6.2 Utilisation moderne

Certains services utilisent SIGHUP comme convention pour recharger leur configuration.

Exemple conceptuel :

Code: Select all

kill -SIGHUP PID
Cette utilisation n’est qu’une convention d’application.


6.3 Fermeture du terminal

Lorsqu’un terminal est fermé, le shell peut envoyer SIGHUP aux tâches qu’il contrôle.

Une commande lancée simplement en arrière-plan peut donc être terminée :

Code: Select all

programme &
Pour laisser une commande continuer après la fermeture du terminal, on peut utiliser :

Code: Select all

nohup programme &
D’autres solutions existent, comme tmux, screen ou un gestionnaire de services.


7. Le contrôle des tâches par le shell

7.1 Définition d’une tâche

Une tâche, appelée job, correspond généralement à un groupe de processus contrôlé par le shell.

Un pipeline complet constitue donc souvent une seule tâche.

Exemple :

Code: Select all

grep erreur fichier.txt | less &
Les deux processus appartiennent au même groupe de processus.


7.2 Lancer une tâche en arrière-plan

Le caractère & demande au shell de lancer la commande en arrière-plan.

Code: Select all

commande &
Le shell affiche généralement un numéro de tâche et un PID :

Code: Select all

[1] 18392
Le numéro de tâche est interne au shell.

Il ne faut pas le confondre avec un PID.


7.3 Afficher les tâches

La commande :

Code: Select all

jobs
affiche les tâches connues du shell.

États courants :
  • Running ;
  • Stopped ;
  • Done.

7.4 Suspendre une tâche

Lorsque l’utilisateur appuie sur :

Code: Select all

Ctrl+Z
le terminal envoie SIGTSTP au groupe de processus au premier plan.

La tâche passe alors à l’état arrêté.

Le shell récupère ensuite le contrôle du terminal.


7.5 Reprendre une tâche en arrière-plan

La commande :

Code: Select all

bg
demande au shell de reprendre une tâche arrêtée en arrière-plan.

Le shell envoie généralement :

Code: Select all

SIGCONT
au groupe de processus de la tâche.


7.6 Reprendre une tâche au premier plan

La commande :

Code: Select all

fg
effectue généralement les opérations suivantes :
  • le shell donne le terminal au groupe de la tâche avec tcsetpgrp() ;
  • il envoie SIGCONT si la tâche était arrêtée ;
  • il attend un changement d’état ;
  • il récupère le terminal après la fin ou la suspension de la tâche.

8. États d’une tâche

Une tâche peut se trouver dans plusieurs états :

Code: Select all

Exécution au premier plan
Exécution en arrière-plan
Arrêtée
Terminée
Transitions importantes :

Code: Select all

Premier plan -- Ctrl+Z / SIGTSTP --> Arrêtée

Arrêtée -- bg / SIGCONT --> Arrière-plan

Arrêtée -- fg / SIGCONT --> Premier plan

Arrière-plan -- fg --> Premier plan

Tout état -- terminaison --> Terminée

9. Mise en œuvre du job control

9.1 Création d’un pipeline

Pour lancer un pipeline, un shell doit approximativement :
  • créer les processus avec fork() ;
  • créer les tubes avec pipe() ;
  • placer tous les processus du pipeline dans le même groupe avec setpgid() ;
  • conserver une structure représentant la tâche ;
  • donner le terminal au groupe si la tâche est au premier plan ;
  • lancer les programmes avec une fonction de la famille exec() ;
  • surveiller les changements d’état avec waitpid() ;
  • récupérer le terminal après la fin ou la suspension de la tâche.

9.2 Exemple d’organisation

Code: Select all

Shell :
PID  = 1204
PGID = 1204
SID  = 1204

Pipeline :
Processus 1 : PID = 1228
Processus 2 : PID = 1229
Processus 3 : PID = 1230

PGID commun = 1228
SID commun  = 1204
Le premier processus du pipeline est généralement utilisé comme chef de groupe.

Ainsi :

Code: Select all

PID du premier processus == PGID du pipeline

9.3 Signaux envoyés à tout le pipeline

Si le pipeline est au premier plan, les signaux du terminal sont envoyés à tous ses processus.

Code: Select all

Ctrl+C
provoque l’envoi de SIGINT à tout le groupe.

Code: Select all

Ctrl+Z
provoque l’envoi de SIGTSTP à tout le groupe.

Le contrôle des tâches fonctionne donc au niveau du groupe de processus.


10. Attente et surveillance des tâches

10.1 waitpid()

Code: Select all

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
Le shell utilise waitpid() pour détecter :
  • la terminaison d’un processus ;
  • l’arrêt d’un processus ;
  • la reprise d’un processus.
Pour attendre tous les processus d’un groupe, on peut utiliser une valeur négative :

Code: Select all

waitpid(-pgid, &status, options);
Cela cible les processus appartenant au groupe pgid.


10.2 Options utiles

Code: Select all

WUNTRACED
Permet de signaler les enfants arrêtés.

Code: Select all

WCONTINUED
Permet de signaler les enfants repris par SIGCONT.

Code: Select all

WNOHANG
Demande à waitpid() de ne pas bloquer si aucun changement d’état n’est disponible.


10.3 Macros d’analyse du statut

Code: Select all

WIFEXITED(status)
Indique que le processus s’est terminé normalement.

Code: Select all

WEXITSTATUS(status)
Retourne son code de sortie.

Code: Select all

WIFSIGNALED(status)
Indique qu’il a été terminé par un signal.

Code: Select all

WTERMSIG(status)
Retourne le signal responsable.

Code: Select all

WIFSTOPPED(status)
Indique que le processus est arrêté.

Code: Select all

WSTOPSIG(status)
Retourne le signal responsable de l’arrêt.

Code: Select all

WIFCONTINUED(status)
Indique que le processus a repris son exécution.


11. Envoi de signaux à un groupe

11.1 kill()

Code: Select all

#include <signal.h>

int kill(pid_t pid, int sig);
La fonction kill() peut cibler un groupe de processus lorsque le PID est négatif.

Exemple :

Code: Select all

kill(-pgid, SIGTERM);
Cela envoie SIGTERM à tous les processus du groupe pgid.


11.2 killpg()

Code: Select all

#include <signal.h>

int killpg(pid_t pgrp, int sig);
La fonction killpg() envoie un signal à tous les membres d’un groupe.

Exemple :

Code: Select all

if (killpg(pgid, SIGCONT) == -1) {
    perror("killpg");
}
Le shell peut l’utiliser pour reprendre toute une tâche.


12. Gestion de SIGTSTP et SIGCONT

12.1 Programmes ordinaires

La majorité des programmes n’ont pas besoin de gérer spécialement SIGTSTP et SIGCONT.

L’action par défaut de SIGTSTP arrête le processus.

SIGCONT reprend ensuite son exécution.


12.2 Programmes interactifs

Certains programmes interactifs doivent nettoyer le terminal avant leur suspension.

C’est notamment le cas :
  • des éditeurs de texte ;
  • des interfaces plein écran ;
  • des programmes utilisant un mode terminal brut ;
  • des programmes modifiant l’affichage ou les attributs du terminal.
Avant de se suspendre, le programme peut devoir :
  • restaurer les réglages normaux du terminal ;
  • restaurer l’affichage ;
  • réinstaller l’action par défaut de SIGTSTP ;
  • se renvoyer le signal ;
  • réinstaller son gestionnaire après SIGCONT ;
  • restaurer son propre mode terminal.

12.3 Séquence correcte

Code: Select all

1. Restaurer SIGTSTP à SIG_DFL
2. Débloquer SIGTSTP
3. Se renvoyer SIGTSTP
4. Le processus s’arrête
5. SIGCONT reprend le processus
6. Réinstaller le gestionnaire SIGTSTP
7. Restaurer l’état de l’application

12.4 Exemple simplifié

Code: Select all

#include <signal.h>
#include <unistd.h>

static void handle_sigtstp(int sig)
{
    struct sigaction sa;

    (void)sig;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sa.sa_handler = SIG_DFL;

    sigaction(SIGTSTP, &sa, NULL);

    raise(SIGTSTP);

    sa.sa_handler = handle_sigtstp;
    sigaction(SIGTSTP, &sa, NULL);
}
Dans une vraie application, il faut également restaurer correctement le masque des signaux et l’état du terminal.


13. Sécurité dans les gestionnaires de signaux

13.1 Fonctions async-signal-safe

Un gestionnaire de signal peut interrompre le programme à presque n’importe quel moment.

Il ne doit donc pas appeler n’importe quelle fonction.

Des fonctions comme :

Code: Select all

printf()
malloc()
free()
strsignal()
ne sont pas garanties comme étant sûres dans un gestionnaire de signal.

Une fonction fréquemment utilisable est :

Code: Select all

write()
Une autre technique consiste à modifier uniquement une variable globale de type :

Code: Select all

volatile sig_atomic_t
Le programme réalise ensuite le vrai traitement dans son flot normal.


13.2 Exemple avec sig_atomic_t

Code: Select all

#include <signal.h>
#include <unistd.h>

static volatile sig_atomic_t stopped = 0;

static void handler(int sig)
{
    if (sig == SIGTSTP) {
        stopped = 1;
    }
}
Le gestionnaire se contente ici de modifier un indicateur.


14. Signaux ignorés hérités du shell

14.1 Héritage après exec()

Lorsqu’un processus appelle une fonction de la famille exec() :
  • les gestionnaires personnalisés sont généralement réinitialisés à l’action par défaut ;
  • les signaux configurés avec SIG_IGN restent ignorés.
Le shell peut ignorer certains signaux avant de lancer une commande en arrière-plan.

Le nouveau programme hérite alors de cette disposition.


14.2 Ne pas réactiver un signal ignoré arbitrairement

Une application doit vérifier l’ancienne disposition avant d’installer son propre gestionnaire.

Exemple :

Code: Select all

#include <signal.h>

struct sigaction old_action;
struct sigaction new_action;

if (sigaction(SIGTSTP, NULL, &old_action) == -1) {
    perror("sigaction");
}

if (old_action.sa_handler != SIG_IGN) {
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;
    new_action.sa_handler = handler;

    sigaction(SIGTSTP, &new_action, NULL);
}
Cela respecte la décision du shell.


15. Groupes de processus orphelins

15.1 Définition

Un groupe de processus orphelin n’est pas simplement un groupe dont le parent principal est mort.

La définition dépend des relations entre :
  • les processus parents ;
  • les groupes de processus ;
  • les sessions.
Un groupe devient orphelin lorsqu’aucun processus du groupe ne possède de parent qui soit à la fois :
  • dans la même session ;
  • dans un autre groupe de processus.
Il n’existe alors plus de processus extérieur au groupe, mais situé dans la même session, capable de le superviser.


15.2 Différence avec un processus orphelin

Un processus orphelin est simplement un processus dont le parent s’est terminé.

Un groupe de processus orphelin est une notion liée à toute la structure :

Code: Select all

Session
Groupes de processus
Relations parent-enfant

15.3 Pourquoi c’est important

Un processus arrêté dans un groupe orphelin pourrait rester suspendu indéfiniment.

Il n’existe potentiellement plus de shell ou de parent de la même session pour lui envoyer SIGCONT.


15.4 Réaction du noyau

Lorsqu’un groupe devient orphelin et contient au moins un processus arrêté, le noyau envoie à chaque membre :

Code: Select all

SIGHUP
puis
SIGCONT
SIGHUP signale la perte de l’environnement de contrôle.

SIGCONT garantit que les processus arrêtés reprennent leur exécution afin de pouvoir traiter le signal ou se terminer.


15.5 Cas de SIGTSTP, SIGTTIN et SIGTTOU

Les signaux suivants sont liés à l’arrêt interactif ou au terminal :

Code: Select all

SIGTSTP
SIGTTIN
SIGTTOU
Pour un groupe orphelin, certains de ces signaux peuvent être ignorés ou certaines opérations terminal peuvent échouer avec :

Code: Select all

errno == EIO
L’objectif est d’éviter qu’un processus soit arrêté alors qu’aucun superviseur ne pourrait le reprendre.

SIGSTOP reste particulier : il ne peut être ni intercepté ni ignoré.


16. Exemple conceptuel de groupe orphelin

Avant la mort du parent :

Code: Select all

Session
├── Groupe du shell
│   └── shell
│
└── Groupe de la tâche
    ├── parent
    └── enfant arrêté
Après la mort du parent :

Code: Select all

Session
├── Groupe du shell
│   └── shell
│
└── Groupe orphelin
    └── enfant arrêté
Le noyau peut alors envoyer :

Code: Select all

SIGHUP
SIGCONT
au groupe orphelin.


17. Exemple minimal : créer un nouveau groupe

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    printf("Avant setpgid()\n");
    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());
    printf("SID  : %ld\n", (long)getsid(0));

    if (setpgid(0, 0) == -1) {
        perror("setpgid");
        return EXIT_FAILURE;
    }

    printf("\nAprès setpgid()\n");
    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());
    printf("SID  : %ld\n", (long)getsid(0));

    return EXIT_SUCCESS;
}

18. Exemple minimal : créer une session

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        return EXIT_FAILURE;
    }

    if (pid > 0) {
        _exit(EXIT_SUCCESS);
    }

    if (setsid() == -1) {
        perror("setsid");
        return EXIT_FAILURE;
    }

    printf("Nouvelle session créée\n");
    printf("PID  : %ld\n", (long)getpid());
    printf("PGID : %ld\n", (long)getpgrp());
    printf("SID  : %ld\n", (long)getsid(0));

    return EXIT_SUCCESS;
}

19. Exemple minimal : observer le premier plan

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    if (!isatty(STDIN_FILENO)) {
        fprintf(stderr, "stdin n'est pas un terminal\n");
        return EXIT_FAILURE;
    }

    pid_t foreground = tcgetpgrp(STDIN_FILENO);

    if (foreground == -1) {
        perror("tcgetpgrp");
        return EXIT_FAILURE;
    }

    printf("PID du processus       : %ld\n", (long)getpid());
    printf("PGID du processus      : %ld\n", (long)getpgrp());
    printf("PGID au premier plan   : %ld\n", (long)foreground);

    return EXIT_SUCCESS;
}

20. API essentielles du chapitre

Groupes de processus

Code: Select all

getpgrp()
getpgid()
setpgid()
Sessions

Code: Select all

getsid()
setsid()
Terminal de contrôle

Code: Select all

ctermid()
ttyname()
isatty()
tcgetpgrp()
tcsetpgrp()
ioctl()
Constante importante :

Code: Select all

TIOCSCTTY
Création et exécution

Code: Select all

fork()
execve()
_exit()
pipe()
read()
write()
close()
Attente des processus

Code: Select all

wait()
waitpid()
Options importantes :

Code: Select all

WUNTRACED
WCONTINUED
WNOHANG
Macros importantes :

Code: Select all

WIFEXITED()
WEXITSTATUS()
WIFSIGNALED()
WTERMSIG()
WIFSTOPPED()
WSTOPSIG()
WIFCONTINUED()
Gestion des signaux

Code: Select all

sigaction()
sigemptyset()
sigaddset()
sigprocmask()
kill()
killpg()
raise()
pause()
Dispositions :

Code: Select all

SIG_DFL
SIG_IGN
Signaux essentiels :

Code: Select all

SIGHUP
SIGINT
SIGQUIT
SIGTSTP
SIGSTOP
SIGCONT
SIGTTIN
SIGTTOU
SIGCHLD

21. Résumé final

Sous Linux, les processus sont organisés selon une hiérarchie à deux niveaux :

Code: Select all

Session
└── Groupes de processus
    └── Processus
Un groupe de processus est identifié par un PGID.

Une session est identifiée par un SID.

Le chef d’un groupe possède généralement :

Code: Select all

PID == PGID
Le chef d’une session possède :

Code: Select all

PID == PGID == SID
Une session peut posséder un terminal de contrôle.

Un seul groupe de processus peut être au premier plan de ce terminal.

Les signaux produits par le clavier sont envoyés à tout le groupe au premier plan.

Le shell utilise les groupes de processus pour contrôler les pipelines et les tâches.

Les fonctions principales utilisées sont :

Code: Select all

setpgid()
setsid()
tcgetpgrp()
tcsetpgrp()
waitpid()
killpg()
sigaction()
Les commandes fg et bg reposent principalement sur le changement de groupe au premier plan et sur l’envoi de SIGCONT.

Lorsqu’un groupe arrêté devient orphelin, le noyau lui envoie généralement :

Code: Select all

SIGHUP
SIGCONT
afin d’éviter que ses processus restent suspendus indéfiniment.

Ce mécanisme constitue la base du contrôle des tâches interactives sous Linux et UNIX.

Who is online

Users browsing this forum: No registered users and 1 guest