TP3 - Création de processus - Fork

La correction du TP précédent est maintenant en ligne.

Fourche/fourchette se disent fork en anglais.

Comme le nom du TP l'indique, vous allez utiliser l'appel système fork pour créer de nouveaux processus.

Il n'y a presque pas de pointeurs dans ce TP, profitez-en 🙃

Partie 0 - Instructions

Il y a 10 exercices. Chaque exercice rapporte un point. Le TP est sur 9 et le dernier exercice est facultatif.

Tous les fichiers de C seront à rendre au format votrelogin_nomexercice.c

Les réponses aux questions se trouveront dans un fichier texte votrelogin_reponses.md

Exemple pour l'exercice 2.1, avec le login de læ prof : ppompeani_forksync.c


Partie 1. Shell 🐚

Pour cette partie, vous agrandirez votre terminal pour qu'il fasse au moins la moitié de l'écran. Je donnerai des punitions très méchantes à celles et ceux qui laissent la taille de la fenêtre par défaut 👀 (c'est faux, mais agrandissez votre terminal quand même).

Exercice 1.1 - Échauffement 👐🏾

a. Quelle commande utilise-t-on pour lister des fichiers et des dossiers ? Qu'est-ce qu'il passe si lui on passe un fichier en argument ? Et un dossier ?

b. Quelle commande utilise-t-on pour changer de dossier ? Qu'est-ce qui se passe si on l'exécute sans argument ? Avec l'argument - ?

Exercice 1.2 - /proc

Lisez vite fait l'intro et la partie Overview du manuel de /proc : man 5 proc

Exécutez ls -l /proc/self.

Exécutez ls -l /proc/self/.

a. Quel est le type du fichier /proc/self ?

b. Que fait ls /proc/self/cwd/ et quelle différence avec ls ? Pourquoi ?

Exercice 1.3 - man et less

Pour cet exercice seulement, remettez temporairement votre terminal en petit 👶

Exécutez less avec un gros fichier, comme /etc/passwd : less /etc/passwd. Appuyez sur la touche h et déplacez vous dans l'aide.

a. Quelle touche nous permet de chercher dans less ?

b. Quelle touche nous permet de quitter ?

c. Est-ce qu'on peut scroller avec la molette de la souris dans less ? (Essayez)

Exécutez man 5 proc | less : man va afficher la page de manuel sur sa sortie standard, qui va être récupérée et affichée par less.

d. Constatez-vous une différence avec man 5 proc ? Que peut-on en déduire ?

Exercice 1.4 cat, grep et cut

(Remettez votre terminal en grand)

cat permet d'afficher le contenu d'un ou plusieurs fichiers. C'est comme less mais en bête : cat affiche tout d'un seul coup.

grep prend un paramètre supplémentaire, et affiche seulement les lignes qui contiennent ce paramètre :

grep chaine fichier...

a. Quelle commande permet d'afficher la ligne correspondant au user root du fichier /etc/passwd ?


cut permet de découper un fichier en colonnes et prend deux options : -d C et -f N.

cut -d C -f N fichier

b. Quelle commande permet d'afficher la liste des noms d'utilisateur du système (listés dans le fichier /etc/passwd) ?

Partie 2 - Fork


Voici un fichier simple qui fait un fork, dont vous devez pouvez partir pour les exercices suivants :

fork.c :

#include <stdio.h>     /* printf, perror, NULL... */
#include <stdlib.h>    /* exit */
#include <unistd.h>    /* fork, getpid, getppid */
#include <errno.h>
#include <sys/types.h> /* pid_t */
#include <sys/wait.h>  /* wait */

extern int errno;      /* Modifiée en cas d'erreur */

void parent();
void enfant();

// Création d'un processus enfant et exécution d'une fonction particulière
// par chaque processus (parent et enfant).
int main() {
    pid_t id;

    id = fork(); // À partir de cette ligne, deux processus exécutent le code en parallèle

    printf("Cette ligne va être affichée deux fois\n");

    switch (id) {
        case -1:
            perror("fork error");
            return errno;
        case 0:
            // Code exécuté uniquement par l'enfant
            enfant();
            return EXIT_SUCCESS; 
        default:
            // Code exécuté uniquement par le parent
            parent();
    }
    return EXIT_SUCCESS;
}

void parent() {
  printf("Parent :  PID = %d - PPID = %d\n", getpid(), getppid());
  sleep(1);
}

void enfant() {
  printf("Enfant : PID = %d - PPID = %d\n", getpid(), getppid());
}

Exercice 2.1 - Questions de parentalité 🫄🏽

Le code n'est pas attendu dans cet exercice seulement, les réponses aux questions sont attendues dans reponses.md

Le programme ci-dessus (fork.c) réalise pour chacun des processus, parent et enfant, l'affichage de leur PID et du PID de leur parent (fonctions getpid() et getppid()). La durée de vie du parent a été rallongée par un sleep(1) pour laisser au processus enfant le temps de s'exécuter avant la fin de son parent.

Compilez et exécutez le programme fourni au-dessus.

a. Quel est le PID du processus parent ? Quel est le PPID du processus enfant ? Qu'est-ce que cela vous permet de vérifier ?

b. Quel processus est le parent du processus parent ? Retrouvez son nom et son PID avec:

pstree -ps <numéro de processus>

c. Modifiez le programme pour que l'enfant réalise son affichage 4 fois d'affilée avec une pause de 1 seconde entre chaque affichage. Comment évoluent le PID et le PPID du processus enfant une fois que son parent naturel se termine ? Expliquez ce qui arrive à un processus orphelin et quel processus l'adopte (généreusement).


Exercice 2.2. Synchronisations

login_forksync.c

Afin de mettre en œuvre la synchronisation de processus, vous allez créer deux processus enfants puis attendre la fin de leur exécution et afficher de l'information relative à l'enfant qui se termine.

Commencez par faire en sorte que le processus parent ait deux enfants mais pas de petits-enfants. Ce n'est pas trivial.

Les processus enfants affichent leurs PID puis dorment 😴 quelques secondes.

Faites ensuite en sorte que le parent attende la fin de ses deux enfants et affiche leurs PID.

Vous aurez besoin de la fonction wait. Exécutée par un processus, elle bloque ce dernier jusqu'à la fin d'un de ses enfants. Si le processus n'a pas d'enfant actif, wait renvoie -1, dans le cas contraire, elle renvoie le numéro (PID) du processus enfant mort 💀.


Exercice 2.3. Variables globales

login_forkglob.c

En repartant du fichier fork.c d'origine, définissez une variable globale de type entier puis faites en sorte que chaque processus, le parent et l'enfant, incrémente puis affiche la valeur de cette variable un grand nombre de fois (au moins 100). Assurez vous à l'aide d'affichages que les deux processus accèdent à la variable globale de manière simultanée en vérifiant que l'un ne se termine pas avant que l'autre ne commence. Si ce n'est pas le cas, ajoutez des itérations d'incrément.

a. Y a-t-il une interférence entre les deux processus ?

Répondez et expliquez pourquoi dans le fichier reponses.md.

Identifiez clairement la question à laquelle vous répondez dans le fichier reponses.md

Pour finir cette partie, initialisez la valeur de la variable globale à une valeur particulière (autre que 0) puis ajoutez un affichage permettant de vérifier que le fork duplique l'espace mémoire du processus parent dans l'état dans lequel il est au moment du fork.

Question bonus : b. est-ce que remplacer votre variable globale par un int *, initialisé par un malloc(sizeof(int)) avant le fork, change le comportement du programme ? Pourquoi ?


Exercice 2.4. Fichier partagé 📁

login_forkshared.c

Afin d'expérimenter l'accès à des ressources partagées, faites en sorte que deux processus accèdent simultanément à un fichier.

Pour cela écrivez un programme qui ouvre un fichier sharedfile.txt en écriture puis qui crée un processus enfant. Les deux processus devront ensuite écrire simultanément dans le fichier une chaîne de votre choix un grand nombre de fois (~1000 fois).

for (int i = 0; i < 1000; i++) {
   fprintf(sharedFile, "Je suis le pere !![%ld:%d]\n", pid, i);
   fflush(sharedFile);
}

Faites en sorte que la chaîne écrite par les deux processus soit longue. Par exemple "Je suis le fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiils". Ou plus long.

Vous ajouterez également un délai avant la première des écritures du parent dans le fichier pour donner au processus enfant le temps de démarrer avec : usleep(10) (Expérimentez avec la durée de ce délai).

Lancez plusieurs fois votre programme et observez, dans un éditeur de texte, le contenu du fichier sharedfile.txt après chaque lancement (pensez à recharger le fichier après une exécution de votre programme).

Vous devriez constater un mélange des affichages du parent et de l'enfant.

vous utiliserez les fonctions fopen, fprintf et fclose

Vous répondrez aux questions suivantes dans le fichier reponses.md :

a. Expliquez pourquoi il y a un mélange des affichages. (Si ce n'est pas le cas, allongez les chaînes et modifiez le délai de démarrage de l'affichage du parent)

b. Combien de fois il faut fermer le fichier ? Pourquoi ?


Exercice 2.5. Fichier partagé 📁 et synchronisation 🔄

login_forksharedwait.c

Afin d'éviter le mélange des affichages, vous allez ouvrir le fichier une fois les processus créés puis synchroniser les processus.

Ouverture par chaque processus

Dans la question précédente, l'ouverture du fichier partagé se fait, si vous avez respecté les consignes, avant la création du processus enfant. Déplacez l'ouverture du fichier pour qu'elle ait lieu après la création de l'enfant, après le fork donc.

Synchronisation

Attendez que le processus enfant ait terminé son exécution avant de commencer les affichages du parent (wait).

Vous prendrez soin de numéroter les affichages pour vérifier qu'ils ont tous lieu.

Vous répondrez aux questions suivantes dans le fichier reponses.md:

a. Constatez-vous toujours un mélange des affichages ?

b. Est-ce que les affichages des deux processus, notamment ceux du processus enfant, apparaissent dans le fichier ? Expliquez pourquoi.

Pour que les affichages du processus enfant apparaissent, utilisez le mode d'ouverture "a" uniquement pour le parent. Vérifiez que les affichages de l'enfant apparaissent bien dans le fichier partagé.

Exercice 2.6 - Shell 🤝 C

login_minishell.c

Voilà une fonction qui permet de remplacer le programme en cours d'exécution par un autre :

execlp(char *file, char *arg...)

On va l'utiliser pour écrire un mini shell.

Pseudo code :

ouvrir stdin avec fdopen
tant que la dernière commande tapée n'est pas exit :
    afficher le menu
    lire une ligne de stdin avec fgets
    la convertir en nombre avec atoi
    forker
    exécuter la ligne dans l'enfant
    attendre la fin de l'exécution dans le parent

Le menu comportera ces options :

  1. pwd
  2. ls
  3. ls /etc/
  4. cat /etc/passwd
  5. less /home/login/.bashrc (où login est votre login)