INF583 - Processus et Tubes

Retour au cours.

0. Présentation du sujet

Le but de cette séance est d'étudier le fonctionnement de l'appel système fork. Celui-ci permet de créer un processus fils à partir d'un autre processus dit parent ou père. Les deux processus s'exécutent ensuite de manière concurrente (i.e., en parallèle). Nous étudierons 4 exemples d'une difficulté croissante pour illustrer l'utilisation de fork. Les différences se situent dans l'attente ou non de la terminaison d'un processus fils par son processus père et dans la présence ou non de communications entre les 2 processus. Les exercices du TD traitent successivement les cas suivants :

  1. attente et pas de communication (exercice 1) ;
  2. attente et communication (exercice 2) ;
  3. pas d'attente et pas de communication (exercice 3) ;
  4. pas d'attente et communication (exercice 3).

1. Première exercice : ma fonction system

La fonction system de la bibliothèque standard du C (la libC) permet d'exécuter une commande qui lui est passée en argument. Cette commande est en réalité une commande shell, qui est interprétée par le shell sh avant d'être exécutée, comme si l'utilisateur avait tapé : /bin/sh -c "command arg1 arg2". Lors d'un appel à la fonction system, le programme appelant est interrompu jusqu'à ce que la nouvelle commande ait terminé son exécution et les signaux sont ignorés. Le prototype de cette function est le suivant : int system(const char *command).

Sa gestion des erreurs peut être schématisée de la façon suivante.
Si command vaut NULL, system renvoie :

Si command est différent de NULL, system renvoie :

Exemple d'utilisation de execl et d'execv

Nous souhaitons exécuter la commande : "echo bonjour". Nous utilisons pour cela une fonction de la famille de execve (2). Ce type de fonction impose de reconstruire les arguments de la commande exécutée (echo dans le cas présent) et de terminer ces arguments par un pointeur NULL de type chaîne de caractères. Cette reconstruction pourra se faire soit grâce à une liste d'arguments avec la fonction execl(3), soit grâce à un tableau d'arguments pour execv(3). Comme le premier argument d'une commande est toujours cette commande elle-même, le premier élément de la liste ou du tableau est doublé.

Avec execl :

execl("echo","echo","bonjour",(char*)NULL);

Avec execv :

char * arguments[] = 
{
  "echo",
  "bonjour",
  (char *) NULL
};
execv("echo", arguments);

En vous aidant des appels systèmes : access, fork, waitpid (2), execl, ou execv, (3) implémentez votre propre fonction system à partir du squelette contenu dans l'archive suivante.
La correction est disponible en suivant ce lien.

2. Deuxième exercice : communication entre 2 processus

Première partie : introduction aux tubes

Dans le premier exercice, nous avons créé deux processus et nous avons fait en sorte que le processus père attende son processus fils. Mais nous n'avons pas établit de communication entre les deux. Le but de ce nouvel exercice est de faire communiquer deux processus. Nous allons implémentez un exemple simple où le processus père (l'« écrivain ») se contente d'envoyer un message à son fils (le« lecteur ») avant d'attendre que ce dernier ne se termine. Vous utiliserez pour cela la technique des tubes telle qu'expliquée en cours.

En vous aidant du squelette contenu dans l'archive suivante, utilisez l' appel système pipe pour implémenter cet exercice.

Deuxième partie : introduction à dup

L'appel système int dup2(int source_fd, int destination_fd); permet de dupliquer un descripteur de fichier. Dans notre exemple, source_fd devient l'équivalent de destination_fd et si destination_fd correspondait déjà à un fichier alors destination_fd est automatiquement fermé. Ainsi, il est possible d'utiliser indifféremment destination_fd et source_fd pour lire ou pour écrire. Remarquons également qu'à la fin du programme, il faut fermer les 2 descripteurs et non un seul.
dup est généralement utilisé pour rediriger l'entrée ou la sortie standard sur un autre fichier. En effet, l'entrée et la sortie standard ont un numéro de descripteur de fichier fixé par convention, il s'agit respectivement de 0 et de 1. En appelant dup2(source_fd,1);, on fait de la sortie standard un équivalent de source_fd. Ainsi écrire sur la sortie strandard revient à écrire dans le fichier correspondant à source_fd et inversement.

Modifiez l'exercice précédent pour que le père envoie un message à son fils en écrivant directement sur la sortie standard (printf).

Troisième partie : ma fonction popen

A la manière de system, la fonction popen de la bibliothèque standard du C (la libC) permet d'exécuter une commande qui lui est passée en argument. Mais contrairement à celle-ci, popen permet de communiquer, soit en lecture, soit en écriture, avec la commande qui va être exécutée. En effet, elle ouvre un tube ("pipe") permettant soit de lire ce que la nouvelle commande écrit sur la sortie standard, soit d'écrire des données que la nouvelle commande pourra lire sur son entrée standard.

La fonction popen renvoyant un pointeur de type FILE qui nous est inconnu, nous décidons d'utiliser, comme dans le TD n°1, notre propre type FILE. Toutefois, nous aurions pu utiliser l'appel system fdopen (2) pour effectuer une encapsulation d'un descripteur de fichier dans un descripteur de flux de type FILE.

En vous aidant de tous des exercices précédents, et après avoir lu la documentation de popen (3) complètement, implémentez vos propres fonctions popen et pclose. Vous pouvez vous aider du squelette contenu dans l'archive suivante.
La correction est disponible en suivant ce lien.

3. Troisième exercice et mini-projet : création d'un démon de son

Rendez-vous sur cette page pour commencer tout de suite le démon de son.