Créer plusieurs processus avec fork()

Publié dans C / C++ | Marqué avec , ,
Share

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], &amp;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')&amp;&amp;(c>='a')) {
					write(tube[1], &amp;c, 1);
				}
				// Arrêt père dès la mort du fils
				else if(atoi(&amp;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], &amp;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 &amp;&amp; essai <= nbEssai);
		// Action du père
		if (essai != nbEssai &amp;&amp; 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')&amp;&amp;(c>='a')) {
						write(tube[1], &amp;c, 1);//ecriture dans tube
					}
					// Arrête du père quand les fils sont arrêtés
					else if(atoi(&amp;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 &amp;&amp; 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()

Répondre à Théophile Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *