2 votes

Comportement POSIX de SIGSTOP/SIGCONT

Je joue avec les signaux : SIGSTOP et SIGCONT en particulier. Voici un programme de test que j'ai écrit. L'idée est de créer une chaîne de N + 1 processus (y compris le processus principal). Chacun doit attendre que son enfant s'arrête, puis s'arrêter lui-même. Le processus principal doit réveiller son enfant lorsque ce dernier s'est arrêté.

Pour cela, la fonction f crée de manière récursive la chaîne de processus. Chacun des processus utilise sigsuspend sur le signal SIGCHLD sauf le dernier enfant qui s'arrête directement. Lorsque son enfant s'est arrêté, un processus recevra le signal SIGCHLD, puis il peut s'arrêter à son tour. Quand le processus principal reçoit le signal SIGCHLD, cela signifie que tous les processus sont dans l'état d'arrêt, donc il envoie le signal SIGCONT à son enfant. Chaque processus envoie SIGCONT à son propre enfant puis quitte, sauf le dernier enfant qui se contente de quitter.

J'ai essayé d'être clair : j'ai supprimé les tests de code de retour et écrit quelques commentaires.

Lors de l'exécution du programme, tout semble OK sauf la chaîne de SIGCONT. Certains processus se réveillent mais pas tous. En regardant les programmes en cours d'exécution (avec ps par exemple) tout semble normal : aucun processus bloqué. Je ne comprends vraiment pas ce qui pourrait ne pas fonctionner dans ce programme. Toute aide ou indice serait la bienvenue.

Voici une trace d'exemple. Comme vous pouvez le voir, la "chaîne de fork" s'est bien déroulée, où les processus sont en attente sur SIGCHLD. Ensuite, le dernier enfant apparaît et s'arrête. Ce qui crée une "chaîne de SIGCHLD" sur les parents car chaque processus s'arrête. Lorsque le processus principal est notifié d'un SIGCHLD, il envoie SIGCONT à son enfant, qui se réveille et envoie à son tour SIGCONT à son propre enfant, etc. Vous pouvez remarquer que cette chaîne n'est pas complète :

$ ./bin/trycont 
n   pid     log
0   6257    "en attente sur SIGCHLD"
1   6258    "en attente sur SIGCHLD"
2   6259    "en attente sur SIGCHLD"
3   6260    "en attente sur SIGCHLD"
4   6261    "en attente sur SIGCHLD"
5   6262    "dernier enfant - arrêt"
4   6261    "a reçu SIGCHLD"
4   6261    "arrêt"
3   6260    "a reçu SIGCHLD"
3   6260    "arrêt"
2   6259    "a reçu SIGCHLD"
2   6259    "arrêt"
1   6258    "a reçu SIGCHLD"
1   6258    "arrêt"
0   6257    "a reçu SIGCHLD"
0   6257    "enverra SIGCONT à 6258"
1   6258    "réveillé - envoi de SIGCONT à 6259"
2   6259    "réveillé - envoi de SIGCONT à 6260"
# <- pas la trace attendue

Voici le programme : src/trycont.c

#include 
#include 
#include 
#include 

/* nombre de processus créés avec fork
 */
#define N 5

#define printHeader() printf("n\tpid\tlog\n");
#define printMsg(i, p, str, ...) printf("%d\t%d\t" #str "\n", i, p, ##__VA_ARGS__)

void f(int n);
void handler(int sig);

sigset_t set;
struct sigaction action;

int main(int argc, char *argv[])
{
    /* masquer SIGCHLD
     */
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_SETMASK, &set, NULL);

    /* le gestionnaire sera appelé lorsque SIGCHLD sera envoyé au processus
     * pendant le gestionnaire, SIGCHLD sera masqué (sa_mask)
     */
    action.sa_mask = set;
    action.sa_handler = handler;
    action.sa_flags = 0;

    /* SIGCHLD déclenchera l'action
     */
    sigaction(SIGCHLD, &action, NULL);

    /* démarrage
     */
    printHeader();
    f(N);

    exit(EXIT_SUCCESS);
}

void f(int n)
{
    pid_t p, pc;
    int myIndex;

    myIndex = N - n;
    p = getpid();

    if (n == 0)
    {
        /* dernier enfant
         */
        printMsg(myIndex, p, "dernier enfant - arrêt");
        kill(p, SIGSTOP);
        printMsg(myIndex, p, "FIN ATTEINTE");
        exit(EXIT_SUCCESS);
    }

    pc = fork();

    if (pc == 0)
    {
        /* récursion
         */
        f(n - 1);

        /* jamais atteint
         * à cause de l'exit
         */
    }

    /* père
     */

    /* en attente sur SIGCHLD
     * besoin de démasquer le signal
     * et de suspendre
     */
    printMsg(myIndex, p, "en attente sur SIGCHLD");

    sigfillset(&set);
    sigdelset(&set, SIGCHLD);
    sigsuspend(&set);

    printMsg(myIndex, p, "a reçu SIGCHLD");

    if (n < N)
    {
        /* processus enfant
         * mais pas le dernier
         */
        printMsg(myIndex, p, "arrêt");
        kill(p, SIGSTOP);

        printMsg(myIndex, p, "réveillé - envoi de SIGCONT à %d", pc);
        kill(pc, SIGCONT);
    }
    else
    {
        /* processus racine
         */
        printMsg(myIndex, p, "envoi de SIGCONT à %d", pc);
        kill(pc, SIGCONT);
    }

    exit(EXIT_SUCCESS);
}

void handler(int sig)
{
    switch (sig)
    {
    case SIGCHLD:
        /* lorsque le processus reçoit SIGCHLD
         * nous pouvons ignorer les prochains SIGCHLD
         */
        action.sa_handler = SIG_IGN;
        sigaction(SIGCHLD, &action, NULL);
        break;
    default:
        break;
    }
}

Voici un Makefile si vous en avez besoin :

CC=gcc
DEFINES=-D_POSIX_C_SOURCE
STD=-std=c11 -Wall -Werror
OPTS=-O2
CFLAGS=$(STD) $(DEFINES) $(OPTS) -g
LDFLAGS=

SRC=src
OBJ=obj
BIN=bin

DIRS=$(BIN) $(OBJ)

.PHONY: mkdirs clean distclean

all: mkdirs $(BIN)/trycont

$(BIN)/%: $(OBJ)/%.o
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<

$(OBJ)/%.o: $(SRC)/%.c
    $(CC) $(CFLAGS) -c -o $@ $<

mkdirs:
    - mkdir $(DIRS)

clean:
    rm -vf -- $(OBJ)/*.o

distclean: clean
    rm -vfr -- $(DIRS)

2voto

pilcrow Points 20628

Certains (tous?) de vos processus descendants meurent d'un SIGHUP généré par le système lorsque le premier processus se termine.

C'est un comportement POSIX attendu dans certaines circonstances.

Lorsque vous démarrez le processus racine à partir de votre shell, il est un chef de groupe de processus, et ses descendants sont des membres de ce groupe. Lorsque ce chef se termine, le groupe de processus est orphelin. Lorsque le système détecte un groupe de processus nouvellement orphelin dans lequel aucun membre n'est arrêté, alors chaque membre du groupe de processus reçoit un SIGHUP suivi d'un SIGCONT.

Ainsi, certains de vos processus descendants sont toujours arrêtés lorsque le chef se termine, et donc tout le monde reçoit un SIGHUP suivi d'un SIGCONT, ce qui signifie en pratique qu'ils meurent de SIGHUP.

Exactement quels descendants sont encore arrêtés (ou même simplement avancent joyeusement vers exit()) est une course contre la montre. Sur mon système, le chef se termine si rapidement que aucun des descendants n'est capable d'imprimer quoi que ce soit.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X