Différences entre versions de « C fork »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
Ligne 166 : Ligne 166 :
 
</pre>
 
</pre>
 
On voit bien que le contexte est recopié car les compteurs sont modifié indépendamment.
 
On voit bien que le contexte est recopié car les compteurs sont modifié indépendamment.
 +
=Démonisation=
 +
Il peut être intéressant pour un programme, lorsque l'on cherche à écrire un serveur, que celui-ci se détache du processus qui l'a démarré : ce procédé s'appelle la démonisation.

Version du 14 octobre 2018 à 19:39

Introduction

Un fork est un processus lourd, comprenez par là que l'intégralité du contexte d'exécution du processus père est recopié dans le nouveau processus fils. Ne cherchez pas, comme avec les threads, a échanger des variables entre les processus car cela ne marchera pas. Il faut utiliser des techniques de communication inter-processus comme les tubes, socket ou encore sémaphores...

Fonctionnement

Pour créer un fork, il suffit d'appeler la fonction du même nom. Cette fonction aura différentes valeurs de retour en fonction du processus dans lequel on se trouve:

  • -1 → il y a une erreur ;
  • 0 → on est dans le processus fils ;
  • Le PID du fils → on est dans le processus père.

La valeur retournée par la fonction fork est de type pid_t, c'est pourquoi il faut obligatoirement inclure le fichier <sys/types.h>.

Dans le cas d'une erreur, celle-ci sera accessible grâce à la variable globale errno, il faudra donc inclure le fichier entête <errno.h>.

Fonctions annexes

Voici quelques fonctions annexes bien pratiques:

  • getpid → retourne le PID du processus appelant, de type pid_t :
#include <unistd.h>
#include <sys/types.h>

printf("Mon PID est %i\n", getpid()); // affichera par exemple "Mon PID est 14804"
  • getppid → retourne le PPID du processus appelant, de type pid_t :
#include <unistd.h>
#include <sys/types.h>

printf("Mon PPID est %i\n", getppid()); // affichera par exemple "Mon PPID est 14403"
  • getuid → retourne l'UID du processus appelant, de type uid_t :
#include <unistd.h>
#include <sys/types.h>

printf("Mon UID est %i\n", getuid()); // affichera par exemple "Mon UID est 0"
  • getgid → retourne le GID du processus appelant, de type gid_t :
#include <unistd.h>
#include <sys/types.h>

printf("Mon GID est %i\n", getgid()); // affichera par exemple "Mon GID est 0"
  • exit → permet de quitter le programme peut importe où dans le code :
#include <stdlib.h>

exit(EXIT_SUCCESS); // quitte le programme en retournant la valeur de succès
  • wait → permet au processus fils d'être libéré pour éviter les zombies:
#include <stdlib.h>
#include <sys/wait.h>

int status;

wait(&status); // la valeur de retour du processus fils sera dans la variable status

Création d'un fork

Ci-dessous un exemple de création d'un processus lourd :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
	pid_t pid = fork();
	if (pid == -1) {
		// Il y a une erreur
		perror("fork");
		return EXIT_FAILURE;
	} else if (pid == 0) {
		// On est dans le fils
		printf("Mon PID est %i et celui de mon père est %i\n", getpid(),	getppid());
	} else {
		// On est dans le père
		printf("Mon PID est %i et celui de mon fils est %i\n", getpid(), pid);
	}
	return EXIT_SUCCESS;
}

Cette exemple peut retourner la sortie suivante :

Mon PID est 15150 et celui de mon fils est 15151
Mon PID est 15151 et celui de mon père est 15150

Cependant, on peut très bien avoir cela:

Mon PID est 15197 et celui de mon fils est 15198
Mon PID est 15198 et celui de mon père est 1

On voit que le numéro de processus du père est devenu 1. Cela vient du faite que, lorsque le père se termine, le fils est automatiquement rattaché au processus de PID 1 pour ne pas devenir un processus orphelin.

Création de plusieurs forks

Lorsque l'on a plusieurs forks à gérer, il faut garder la trace de chacun des numéros de processus et également attendre la fin de chacun des fils.

Ci-dessous un code qui démarre plusieurs forks qui vont incrémenter un compteur.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// Nombre total de thread
#define NB_FORK 2
// Limite de l'incrément
#define INCREMENT_LIMIT 2
// Initialisation de la donnée
int data = 0;

// Fonction exécutée dans le thread
void job() {
	int tid = getpid();
	while (data < INCREMENT_LIMIT) {
		data++;
		printf("fork [ %i ] data [ %i ]\n", tid, data);
		// Pause l'exécution du thread pendant 1 seconde
		sleep(1);
	}
	printf("Fin du fork %i\n", tid);
	exit(EXIT_SUCCESS);
}
// Fonction qui attend chacun des processus fils
void waitForAll() {
	int status;
	pid_t pid;
	int n = 0;
	while (n < NB_FORK) {
		pid = wait(&status);
		printf("Fork [%i] terminé avec le code %i\n", pid, status);
		n++;
	}
}

int main() {
	for (int i = 0; i < NB_FORK; i++) {
		pid_t pid = fork();
		if (pid == -1) {
			// Il y a une erreur
			perror("fork");
			return EXIT_FAILURE;
		} else if (pid == 0) {
			// On est dans le fils
			job();
		} else {
			// On est dans le père
		}
	}
	waitForAll();
	return EXIT_SUCCESS;
}

Ce code donne, par exemple, le résultat suivant:

fork [ 15964 ] data [ 1 ]
fork [ 15963 ] data [ 1 ]
fork [ 15964 ] data [ 2 ]
fork [ 15963 ] data [ 2 ]
Fin du fork 15964
Fork [15964] terminé avec le code 0
Fin du fork 15963
Fork [15963] terminé avec le code 0

On voit bien que le contexte est recopié car les compteurs sont modifié indépendamment.

Démonisation

Il peut être intéressant pour un programme, lorsque l'on cherche à écrire un serveur, que celui-ci se détache du processus qui l'a démarré : ce procédé s'appelle la démonisation.