Faisons une petite parenthèse sur le sujet du moment (vous ne le savez pas encore, mais j’ai quasi terminée la série jQuery et j’ai entamé une série Java EE) et allons faire un tour dans le monde merveilleux du C avec fork()
et pipe()
! L’idée est de créer plusieurs processus avec fork()
, d’écrire dans le pipe avec le père, et de faire lire le pipe par les processus fils, pour se rendre compte qu’un seul processus fils arrive à lire les informations !
Avant d’écrire ce billet, en plus du man pipe
, et du man fork
, j’ai clarifié mes idées préconçues à propos du pipe en lisant ce très bon tutoriel de Commentçamarche : Que fait un fork.
Un seul processus
Créer un seul processus est très simple, il suffit d’utiliser pid = fork()
. Pour le père (i.e : le processus lançant le fork), pid
sera supérieur à 0. Et cet appel à fork
va créer un deuxième processus, identique au père, mais qui démarre à la ligne suivant le fork
, et dans lequel pid=0
. Je vous renvoie vers man fork
pour en savoir plus.
Du coup, on peut facilement différencier les actions à effectuer par le père (pid!=0
), et par le fils (pid=0
).
Un petit exemple :
#include <sys/types.h> #include <signal.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t fils; assert(argc == 1); fils = fork(); switch(fils) { // Erreur fork case -1: perror("fork"); break; // Action fils case 0: printf("Je suis le fils %d, mon père est %d\n", getpid(), getppid()); break; // Action père default: printf("Je suis le père %d\n", getpid()); sleep(1); // On laisse au fils le temps de s'exécuter kill(fils, SIGUSR1); // On tue le fils printf("Je suis le pere, je meurs\n"); exit(EXIT_SUCCESS); } return 0; }
Ce qui affiche (après gcc -Wall tfork1.c -o fork1
et ./fork1
) :
Je suis le fils 18955, mon père est 18954
Je suis le père 18954
Je suis le pere, je meurs
On va maintenant améliorer un petit peu notre code de telle sorte que le père écrive dans un tube (ah oui, parfois au lieu de « pipe », je dis « tube », allez comprendre…) et que le fils lise dedans. Je vous renvoie vers man pipe
pour en savoir plus. Au passage, ici dans mon code ce n’est plus le père qui demande la fermeture du fils, mais en fermant le pipe (quand on tape « 1 »), il laisse au fils le temps de se terminer, et ensuite seulement le père s’arrête.
#include <sys/types.h> #include <sys/wait.h> #include <signal.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int tube[2]; void actionFils() { char buf; short pid = getpid(); printf("Je suis fils n°%d, mon père est %d\n", pid, getppid()); close (tube[1]); // Fermeture écriture // Lecture du pipe while(read(tube[0], &buf, sizeof(buf))!=0) { printf("fils %d : %c\n", pid, buf); } // Quand il n'y a plus rien à lire : arrêt fils printf("Je suis le fils %d, je meurs\n", pid); close(tube[0]); _exit(pid); } int main(int argc, char *argv[]) { pid_t fils; assert(argc == 1); if (pipe(tube) == -1) { perror("pipe"); exit(EXIT_FAILURE); } fils = fork(); switch(fils) { // Erreur fork; case -1: perror("fork"); break; // Action fils case 0: actionFils(); break; // Action père default: printf("Je suis le père %d\n", getpid()); close(tube[0]); // Fermeture lecture char c; while ((c=getchar()) != EOF) { // Ecriture tube if((c<='z')&&(c>='a')) { write(tube[1], &c, 1); } // Arrêt père dès la mort du fils else if(atoi(&c) == 1) { sleep(1); // On laisse au fils le temps de s'exéctuer close(tube[1]); // Fermeture tube, le fils va donc s'arrêter wait(NULL); // Attente mort des fils printf("Je suis le pere, je meurs\n"); exit(EXIT_SUCCESS); } } } return 0; }
Ce qui affiche cette fois-ci, en ayant demandé au père d’écrire « test » dans le tube :
Je suis fils n°20582, mon père est 20581
Je suis le père 20581
test
fils 20582 : t
fils 20582 : e
fils 20582 : s
fils 20582 : t
1
Je suis le fils 20582, je meurs
Je suis le père, je meurs
Avant d’écrire « 1 », on peut utiliser la commande ps -A
dans un autre terminal pour voir la liste des processus actifs, et on voit qu’on a bien 2 processus qui portent le nom de notre fichier exécutable.
Plusieurs processus
Maintenant, on va faire la même chose sauf qu’il n’y aura plus un seul fils, mais plusieurs. En réfléchissant un peu, on voit que seul le père doit effectuer fork()
dans notre cas pour éviter qu’un fils créé un fils, qui créé un fils, qui créé un fils…
#include <sys/wait.h> #include <sys/types.h> #include <signal.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int tube[2]; void fils(int n) { char buf; short pid = getpid(); printf("Je suis fils n°%d-%d, mon père est %d\n", n, pid, getppid()); close (tube[1]); // Fermeture écriture // Lecture du pipe while(read(tube[0], &buf, sizeof(buf))!=0) { printf("fils %d : %c\n", n, buf); } // Quand il n'y a plus rien à lire : arrêt fils printf("Je suis le fils %d-%d, je meurs\n", n, pid); close(tube[0]); _exit(pid); } int main(int argc, char ** argv) { int numLect = 1; // Numéro des lecteurs int nbLect; // Nb de lecteur int nbEssai = 10; // Nb d'essai pour faire un fork au cas où ça rate int essai; pid_t pid; assert(argc == 2); nbLect = atoi(argv[1]); if (pipe(tube) == -1) { perror("pipe"); exit(EXIT_FAILURE); } // Boucle que fait nbLect fois le père (au moins 1 fois) do { // Le père fait un fork, et retente nbEssai fois si ça rate essai=0; do { pid = fork(); essai++; } while(pid == -1 && essai <= nbEssai); // Action du père if (essai != nbEssai && pid != 0) { // On effectue réellement l'action du père qu'après avoir créé les nbLect lecteurs if (numLect == nbLect) { printf("Je suis le père %d\n", getpid()); close(tube[0]); // Fermeture lecture char c; while ((c=getchar()) != EOF) { // Ecriture tube if((c<='z')&&(c>='a')) { write(tube[1], &c, 1);//ecriture dans tube } // Arrête du père quand les fils sont arrêtés else if(atoi(&c) == 1) { sleep(1); // On laisse les fils terminer close(tube[1]);// On ferme tube, les fils ne liront plus wait(NULL); // On attend la mort des fils printf("Je suis le pere, je meurs\n"); exit(EXIT_SUCCESS); // On meurt } } } // On incrémente nbLect pour savoir où on en est numLect++; } // Action des fils else if (pid == 0) { fils(numLect); } } while(pid != 0 && numLect <= nbLect); return 0; }
Ce qui affiche (après gcc -Wall tpipen.c -o pipen
et ./pipen 3
) en demandant au père d’écrire « test » puis « 1 » :
Je suis fils n°1-21943, mon père est 21942
Je suis fils n°2-21944, mon père est 21942
Je suis fils n°3-21945, mon père est 21942
Je suis le père 21942
test
fils 1 : t
fils 1 : e
fils 1 : s
fils 1 : t
1
Je suis le fils 3-21945, je meurs
Je suis le fils 2-21944, je meurs
Je suis le pere, je meurs
Je suis le fils 1-21943, je meurs
On voit bien qu’un seul des lecteurs arrivent à lire ce qui se trouve sur le pipe. En effet, dès qu’un fils l’a lu, la donnée est effacée du pipe, et donc non lisible par les autres lecteurs.
5 Responses to Créer plusieurs processus avec fork()