fork()
, wait()
#include <unistd.h>
pid_t fork(void);
pid_t wait(int *status)
La fonction fork()
crée un nouveau processus (fils)
semblable au processus courant (père). La valeur renvoyée
n'est pas la même pour le fils (0) et pour le père (numéro de
processus du fils). -1 indique un échec.
La fonction wait()
attend qu'un des processus fils soit terminé.
Elle renvoie le numéro du fils, et son status
(voir exit()
)
en paramètre passé par adresse.
Attention. Le processus fils hérite des descripteurs ouverts de son père. Il convient que chacun des processus ferme les descripteurs qui ne le concernent pas.
Exemple :
1 /* biproc.c */
2 /*
3 * Illustration de fork() et pipe();
4 *
5 * Exemple à deux processus reliés par un tuyau
6 - l'un envoie abcdef...z 10 fois dans le tuyau
7 - l'autre écrit ce qui lui arrive du tuyau sur la
8 sortie standard, en le formattant.
9 */
10 #include <unistd.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #define MAXLIGNE 30
16 void genere(int sortie)
17 {
18 char alphabet[26];
19 int k;
20 for (k = 0; k < 26; k++)
21 alphabet[k] = 'a' + k;
22 for (k = 0; k < 10; k++)
23 if (write(sortie, alphabet, 26) != 26) {
24 perror("write");
25 exit(EXIT_FAILURE);
26 };
27 close(sortie);
28 }
29 int lire(int fd, char *buf, size_t count)
30 {
31 /* lecture, en insistant pour remplir le buffer */
32 int deja_lus = 0, n;
33 while (deja_lus < count) {
34 n = read(fd, buf + deja_lus, count - deja_lus);
35 if (n == -1)
36 return (-1);
37 if (n == 0)
38 break; /* plus rien à lire */
39 deja_lus += n;
40 };
41 return (deja_lus);
42 }
43 void affiche(int entree)
44 {
45 char ligne[MAXLIGNE + 1];
46 int nb, numligne = 1;
47 while ((nb = lire(entree, ligne, MAXLIGNE)) > 0) {
48 ligne[nb] = '\0';
49 printf("%3d %s\n", numligne++, ligne);
50 };
51 if (nb < 0) {
52 perror("read");
53 exit(EXIT_FAILURE);
54 }
55 close(entree);
56 }
57 int main(void)
58 {
59 int fd[2], status;
60 pid_t fils;
61 if (pipe(fd) != 0) {
62 perror("pipe");
63 exit(EXIT_FAILURE);
64 }
65 if ((fils = fork()) < 0) {
66 perror("fork");
67 exit(EXIT_FAILURE);
68 }
69 if (fils == 0) { /* le processus fils */
70 close(fd[0]);
71 close(1);
72 genere(fd[1]);
73 exit(EXIT_SUCCESS);
74 };
75 /* le processus père continue ici */
76 close(0);
77 close(fd[1]);
78 affiche(fd[0]);
79 wait(&status);
80 printf("status fils = %d\n", status);
81 exit(EXIT_SUCCESS);
82 }
Exercice : Observez ce qui se passe si, dans la fonction
affiche()
, on remplace l'appel à lire()
par un
read()
? Et si on ne fait pas le wait()
?
waitpid()
La fonction waitpid()
permet d'attendre l'arrêt d'un des
processus fils désigné par son pid (n'importe lequel si
pid=-1), et de récupérer éventuellement son code de retour. Elle
retourne le numéro du processus fils.
L'option WNOHANG
rend waitpid
non bloquant (qui
retourne alors -1
si le processus attendu n'est pas terminé).
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid (pid_t pid, int *status, int options);
Exemple :
int pid_fils;
int status;
if( (pid_fils = fork()) != 0) {
code_processus_fils();
exit(EXIT_SUCCESS);
};
...
if (waitpid(pid_fils,NULL,WMNOHANG) == -1)
printf("Le processus fils n'est pas encore terminé\n");
...
exec()
#include <unistd.h>
int execv (const char *FILENAME, char *const ARGV[])
int execl (const char *FILENAME, const char *ARG0,...)
int execve(const char *FILENAME, char *const ARGV[], char *const ENV[])
int execle(const char *FILENAME, const char *ARG0,...char *const ENV[])
int execvp(const char *FILENAME, char *const ARGV[])
int execlp(const char *FILENAME, const char *ARG0, ...)
Ces fonctions font toutes la même chose : activer un exécutable à la place du processus courant. Elles diffèrent par la manière d'indiquer les paramètres.
execv()
: les paramètres de la commande sont transmis
sous forme d'un tableau de pointeurs sur des chaînes de caractères (le
dernier étant NULL
). Exemple:
1 /* execv.c */
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #define TAILLE_MAX_PREFIXE 10
6 #define TAILLE_MAX_NOMFICHIER 100
7 int main(void)
8 {
9 char prefixe[TAILLE_MAX_PREFIXE];
10 char nomfichier[TAILLE_MAX_NOMFICHIER];
11 char *args[] = {
12 "gcc",
13 NULL, /* le nom du fichier source */
14 "-o",
15 NULL, /* le nom de l'exécutable */
16 NULL /* fin des paramètres */
17 };
18 printf("préfixe du fichier à compiler : ");
19 scanf("%s", prefixe); /* dangereux */
20 snprintf(nomfichier, TAILLE_MAX_NOMFICHIER, "%s.c", prefixe);
21 args[1] = nomfichier;
22 args[3] = prefixe;
23 execv("/usr/bin/gcc", args);
24 perror("execv"); /* on ne passe pas ici */
25 exit(EXIT_FAILURE);
26 }
execl()
reçoit un nombre variable de paramètres.
Le dernier est NULL
). Exemple:
1 /* execl.c */
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #define TAILLE_MAX_PREFIXE 10
6 #define TAILLE_MAX_NOMFICHIER 100
7 int main(void)
8 {
9 char prefixe[TAILLE_MAX_PREFIXE];
10 char nomfichier[TAILLE_MAX_NOMFICHIER];
11 printf("Préfixe du fichier à compiler : ");
12 scanf("%s", prefixe); /* dangereux */
13 snprintf(nomfichier, TAILLE_MAX_NOMFICHIER, "%s.c", prefixe);
14 execl("/usr/bin/gcc", "gcc", nomfichier, "-o", prefixe, NULL);
15 perror("execl"); /* on ne passe jamais ici */
16 exit(EXIT_FAILURE);
17 }
execve()
et execle()
ont un paramètre
supplémentaire pour préciser l'environnement.
execvp()
et execlp()
utilisent la variable
d'environnement PATH
pour localiser l'exécutable à lancer. On
pourrait donc écrire simplement:
execlp("gcc","gcc",fichier,"-o",prefixe,NULL);
getpid()
, getppid()
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
getpid()
permet à un processus de connaître son propre numéro, et
getppid()
celui de son père.
Les démons
Traduction de l'anglais daemon, acronyme de « Disk And Extension MONitor », qui désignait une des parties résidentes d'un des premiers systèmes d'exploitation.sont des processus qui tournent normalement en arrière-plan pour assurer un service. Pour programmer correctement un démon, il ne suffit pas de faire un
fork()
, il faut aussi
s'assurer que le processus restant ne bloque pas de ressources. Par
exemple il doit libérer le terminal de contrôle du processus, revenir
à la racine, faute de quoi il empêchera le démontage éventuel du système de
fichiers à partir duquel il a été lancé.
1 /* demon.c */
2 int devenir_demon(void)
3 {
4 int fd;
5 /* Le processus se dédouble, et le père se termine */
6 if (fork() != 0)
7 exit(EXIT_SUCCESS);
8 /* le processus fils devient le leader d'un nouveau
9 groupe de processus */
10 setsid();
11 /* le processus fils crée le processus démon, et
12 se termine */
13 if (fork() != 0)
14 exit(EXIT_SUCCESS);
15 /* le démon déménage vers la racine */
16 chdir("/");
17 /* l'entrée standard est redirigée vers /dev/null */
18 fd = open("/dev/null", O_RDWR);
19 dup2(fd, 0);
20 close(fd);
21 /* et les sorties vers /dev/console */
22 fd = open("/dev/console", O_WRONLY);
23 dup2(fd, 1);
24 dup2(fd, 2);
25 close(fd);
26 }
Voir FAQ Unix : 1.7 How do I get my program to act like a daemon