Avant de commencer

Cette suite de travaux pratique nécessite l’utilisation d’un environnement UNIX. Une machine virtuelle est mise à disposition des étudiants.

Si vous avez un environnement Linux ou Mac OS, vous pouvez vous passer de la machine virtuelle. Veillez dans ce cas à avoir:

  • un environnement de compilation fonctionel

  • un IDE ou un éditeur de texte.

Évaluation

Aucun compte rendu n’est demandé en fin de scéance, l’évaluation sera réalisé sur la dernière scéance de TP (2h).

Les travaux pratiques ainsi que l’évaluation peuvent être réalisés en binôme ou seul à votre convenance.

1. Fork

1.1. Duplication de processus

1.1.1. Premier pas avec les processus

  1. Compilez et exécutez le code en page suivante.

  2. Que fait la fonction getpid()?

  3. Que fait la fonction getppid()?

  4. Quel est l’effet de la fonction fork ?

  5. Quelles sont les trois valeurs de retour possible de la fonction fork ? (Détaillez les valeurs de retour).

  6. Quelle est la valeur retournée par getppid() lorsque la fonction est exécutée par le processus dupliqué ?

  7. Pourquoi le processus dupliqué n’exécute pas les lignes situées avant l’appel à fork ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {

    printf("pid=%d, parent_pid:%d, retour de la fonction fork: "
           "N/A. Début du processus\n", getpid(), getppid());
    const int pid = fork();
    if (pid == -1) {
        perror("Impossible de dupliquer le processus courant");
        return errno;
    }

    if (pid > 0) {
        printf("pid=%d, parent_pid:%d, retour de la fonction fork: %d."
               " Début du traitement\n", getpid(), getppid(), pid);

        // On simule un traitement de trois secondes
        sleep(3);

        printf("pid=%d, parent_pid:%d, retour de la fonction fork: %d. "
               "Fin du traitement\n", getpid(), getppid(), pid);
    }

    if (pid == 0) {
        printf("pid=%d, parent_pid:%d, retour de la fonction fork: 0. "
               "Début du traitement\n", getpid(), getppid());

        // On simule un traitement d'une seconde
        sleep(1);

        printf("pid=%d, parent_pid:%d, retour de la fonction fork: 0. "
               "Fin du traitement\n", getpid(), getppid());
    }

    printf("pid=%d, parent_pid:%d, retour de la fonction fork: %d. "
           "Fin du processus\n", getpid(), getppid(), pid);
    return EXIT_SUCCESS;
}

1.1.2. Création d’un petit fils

  1. Modifiez le code précédent pour que le processus fils se duplique (créant ainsi un autre processus appelé ici petit fils). Le processus petit fils suivra l’algorithme suivant :

    • Affichage des informations contextuelles (son PID, le PID de son processus parent et le nouveau retour de la fonction fork) et de "Début du traitement du petit fils\n"

    • Attente de 5 secondes

    • Affichage de "Fin du traitement du petit fils\n"

    • Retourne la valeur 42 (return ou exit)

  2. Décrivez le comportement du programme lorsqu’il est exécuté depuis un terminal.

  3. Quel est l’impact de la fin du processus parent sur les processus fils et petit fils ?

1.2. Attente de la fin d’un processus

  1. A l’aide de l’appel system wait attendez la fin du processus fils depuis le processus père

  2. A l’aide de l’appel system wait attendez la fin du processus petit fils depuis le processus fils

Les deux questions se basent sur la résolution de l’exercice Création d’un petit fils. Si vous n’avez pas réussi l’exercice précédent, basez-vous sur le code fourni dans le TP et ne réalisez que la première question.

1.3. Récupération de la valeur de retour d’un fils

  1. A l’aide des macros préprocesseur définies dans le fichier d’entête sys/wait.h. Récupérez la valeur de retour du processus petit fils depuis le processus fils.

La question se base sur la résolution de l’exercice Création d’un petit fils. Si vous n’avez pas réussi l’exercice précédent, basez-vous sur le code fourni dans le TP et essayez de récupérer le code de retour du processus fils depuis le processus père.

2. Transformation de processus

  1. Recopiez le code ci-dessous dans deux fichiers distincts: launcher.c et èxecuted.c.

  2. Compilez le fichier executed.c.

  3. Modifiez le code du fichier launcher.c afin de remplacer REPLACE_BY_THE_PATH_TO_THE_EXECUTED_PROGRAM par le chemin absolu de votre programme compilé.

  4. Compilez, exécutez le fichier launcher.c et expliquer le résultat obtenu.

  5. Commentez la valeur de argv[0] par rapport à une exécution classique (lancez le programme èxecuted.c dans un terminal).

  6. Modifiez le fichier launcher.c pour que l’appel système execve soit réalisé depuis un *processus fils (fork).

  7. Modifiez le fichier launcher.c afin d’ajouter une boucle infinie (while(1){} juste après l’appel système execve. Expliquez le résultat.

launcher.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    printf("Trying to run execve system call...");

    char *args[] = {
        "Execve ran program",
        "My Name",
        NULL
    };

    if (execve("<REPLACE_BY_THE_PATH_TO_THE_EXECUTED_PROGRAM>", args, NULL) == -1) {
       perror("Error returned by execve: ");
    } else {
       printf("Error in system call");
    }
    return EXIT_SUCCESS;
}
executed.c
1
2
3
4
5
6
7
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    printf("I am ran by an execve system call.\nProgram: %s\nWell done %s!\n", argv[0], argc > 1 ? argv[1] : "dear");
    return EXIT_SUCCESS;
}

3. Redirections

3.1. Lecture depuis l’entrée standard

Compilez et exécutez le code

  • Note: le programme attend que quelque chose soit écrit sur l’entrée standard (écrivez dans la console et appuyez sur Enter)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    // Lecture de L'entrée standard
    char tab[200] = {};
    printf("Please enter something: \t");
    fgets(tab, sizeof(tab) / sizeof(char), stdin);
    printf("\nRead data from STDIN: %s\n", tab);

    return EXIT_SUCCESS;
}

3.2. Changement de l’entrée standard

Le but de cet exercice et de changer l’entrée standard du programme. Par défaut, l’entrée standard d’un programme est le terminal qui le lance. On peut alors interagir avec le programme en utilisant le clavier. Ici, le but est de changer l’entrée standard du programme précédent afin de ne plus lire depuis le clavier mais depuis un fichier.

Créez un fichier nommé input.txt situé à côté de votre binaire.

Ajoutez le texte suivant dans le fichier:

1
2
Hello world from input.txt file.
Bye.

3.2.1. Ouverture du fichier

L’ouverture d’un fichier en utilisant un appel système UNIX se fait par l’utilisation de la fonction open.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int text_file_descriptor = open("input.txt", O_RDONLY); (1)
    if (text_file_descriptor == -1) { (2)
        perror("Unable to open text.txt file: "); (3)
        exit(errno); (4)
    }

    return EXIT_SUCCESS;
}
1 Ouverture du fichier input.txt en lecture seule (O_RDONLY)
2 Comme tous les appels systèmes -1 est le retour d’erreur
3 Affichage du message d’erreur sur la sortie d’erreur
4 Fin du programme en retournant le code d’erreur

Question : Modifiez le programme précédant afin d’ouvrir le fichier input.txt au début du programme.

3.2.2. Fermeture du fichier

La fermeture d’un fichier en utilisant un appel système UNIX se fait par l’utilisation de la fonction close.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {

    int text_file_descriptor = ...

    if (close(text_file_descriptor) == -1) { (1)
        perror("Unable to close text.txt file: "); (2)
        exit(errno); (3)
    }

    return EXIT_SUCCESS;
}
1 Fermeture du fichier et test de la valeur de retour. Comme tous les appels systèmes -1 est le retour d’erreur
2 Affichage du message d’erreur sur la sortie d’erreur
3 Fin du programme en retournant le code d’erreur

Question : Modifiez le programme de l’exercice Ouverture du fichier afin de fermer le fichier input.txt en fin de programme.

3.3. Redirection de l’entrée standard

Tout programme, est initialisé avec trois descripteurs de fichiers dans la table des descripteurs de fichiers:

  • L’entrée standard (dans la case 0)

  • La sortie standard (dans la case 1)

  • La sortie d’erreur (dans la case 2)

Comme vu ensemble lors de l’introduction de ce TP, la fonction dup permet de dupliquer un descripteur de fichier. Le nouveau descripteur de fichier est alors inséré dans la table des descripteurs de fichiers dans la première place disponible.

En faisant une analogie avec le TP sur l’utilisation de Bash, les redirections sont réalisées avec les opérateurs < ou > :

  • echo Hello > my_file : redirection de la sortie standard vers le fichier 'my_file' (descripteur de fichier 1)

  • cat < my_file : redirection de l’entrée standard depuis le fichier 'my_file' (descripteur de fichier 0)

  • echo Hello 2> my_file : redirection de la sortie d’erreur vers le fichier 'my_file' (descripteur de fichier 2)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(int argc, char *argv[]) {

    int text_file_descriptor = open("some-file.txt", O_RDONLY);

    int new_file_descriptor = dup(text_file_descriptor); (1)
    if (new_file_descriptor == -1) { (2)
        perror("Unable to duplicate file descriptor:"); (3)
        exit(errno); (4)
    }

    return EXIT_SUCCESS;
}
1 Duplication du descripteur du fichier input.txt
2 Comme tous les appels systèmes -1 est le retour d’erreur
3 Affichage du message d’erreur sur la sortie d’erreur
4 Fin du programme en retournant le code d’erreur

Question : Modifiez le programme de l’exercice Fermeture du fichier afin qu’il suive l’algorithme suivant:

  1. Ouverture du fichier input.txt

  2. Fermeture de l’entrée standard (identifié par le descripteur de fichier 0)

  3. Duplication du descripteur du fichier input.txt

  4. Lecture depuis l’entrée standard

  5. Fermeture du fichier input.txt

3.4. Redirection de la sortie standard

Tout programme, est initialisé avec trois descripteurs de fichiers dans la table des descripteurs de fichiers:

  • L’entrée standard (dans la case 0)

  • La sortie standard (dans la case 1)

  • La sortie d’erreur (dans la case 2)

Comme vu ensemble lors de l’introduction de ce TP, la fonction dup permet de dupliquer un descripteur de fichier. Le nouveau descripteur de fichier est alors inséré dans la table des descripteurs de fichiers dans la première place disponible.

En faisant une analogie avec le TP sur l’utilisation de Bash, les redirections sont réalisées avec les opérateurs < ou > :

  • echo Hello > my_file : redirection de la sortie standard vers le fichier 'my_file' (descripteur de fichier 1)

  • cat < my_file : redirection de l’entrée standard depuis le fichier 'my_file' (descripteur de fichier 0)

  • echo Hello 2> my_file : redirection de la sortie d’erreur vers le fichier 'my_file' (descripteur de fichier 2)

Question : Écrivez un programme redirigeant sa sortie standard vers un fichier et suivant l’algorithme suivant :

  1. Ouverture du fichier output.txt en le créant s’il n’existe pas (attention aux droits et aux paramètres)

  2. Fermeture du descripteur de fichier de la sortie standard

  3. Duplication du descripteur du fichier output.txt

  4. Écriture sur la sortie standard (via printf("…​…​\n"))

  5. Fermeture du fichier output.txt

4. Bonus

Question : Écrivez un programme suivant l’algorithme ci-dessous :

  • Le processus père

    • Créé un processus fils

    • Affiche un message sur sa sortie standard

    • Se termine

  • Le processus fils

    • Redirige sa sortie standard vers un fichier en écrivant à la fin du fichier

    • Affiche un message sur sa sortie standard