:: Enseignements :: Licence :: L2 :: 2009-2010 :: Système ::
[LOGO]

Entrées/Sorties


Le but de ce TP est d'utiliser les principales fonctions de manipulation d'entrées/sorties en C de la bibliothèque standard afin d'illustrer les notions de tampons d'entrées/sorties.

Exercice 1 - Rappels de Shell

Tout processus (programme) possède trois canaux standards stdin (fichier de descripteur 0), stdout (fichier de descripteur 1) et stderr (fichier de descripteur 2). La commande shell A < f exécute la commande A avec stdin ouvert sur le fichier f. Symétriquement, A > f exécute A avec stdout ouvert sur le fichier f. Enfin A | B exécute les deux programmes A et B en parallèle, le canal stdout de A étant redirigé sur le canal stdin de B.

Noter que les commandes <f et >f sont utilisables simultanément. Enfin n>f redirige la sortie du fichier de descripteur n vers le fichier f. Si n = 2, la sortie est redirigée vers stderr.

Essayer l'effet de ces commandes avec la commande ls, par exemple. Attention de ne pas effacer de fichiers importants ! La redirection écrase les fichiers sans préavis.

Il est également possible d'effectuer une redirection en mode ajout avec >> f : les données sont alors ajoutées à la fin du fichier f (qui est créé si nécessaire).

Exercice 2 - Copie élémentaire

On commence ce TD par mettre en place un environnement de travail propre :
  • créer un nouveau répertoire ;
  • télécharger le fichier copy1.c ;
  • écrire un Makefile pour compiler copy1.c en un programme copy1.

Essayer par exemple
./copy1 < foo
avec un fichier texte foo arbitraire.

Exercice 3 - Effet des tampons

Pour pouvoir expérimenter l'efficacité des tampons de fichiers, nous voulons :
  • mesurer le temps d'exécution des lectures/écritures ;
  • faire des mesures sur des fichiers de tailles variables.

  1. Mesures de temps : Modifier le programme copy1.c pour qu'il imprime sur stderr le temps d'exécution de la boucle principale en secondes (man 2 times, penser à diviser par sysconf(_SC_CLK_TCK). Pour utiliser _SC_CLK_TCK, vous devrez inclure unistd.h). Le temps qui nous intéresse est le temps total user+system.
  2. Fichiers de tailles variables : Écrire un programme gen.c qui prend un entier n sur la ligne de commande et affiche n caractères sur sa sortie standard. Faire en sorte que gen imprime aussi son temps d'exécution sur stderr, de la même façon pour pour le programme précédent.
  3. Tampons de tailles variables : Faire man setvbuf, voir aussi le cours 6. Modifier le programme copy1.c pour qu'il prenne des entiers p et q sur la ligne de commande et qu'il affecte :
    • un tampon de taille p sur stdin
    • un tampon de taille q sur stdout
    On conservera l'impression du temps d'exécution sur la sortie d'erreur. Essayer plusieurs valeurs pour n, p, q dans l'exemple suivant:
    ./gen n | ./copy1 p q > logout

Exercice 4 - Le programme head

La commande head n imprime les n premières lignes de stdin sur stdout. Une ligne se termine par '\n'.
  • Réécrire cette commande avec un programme C ;
  • Écrire un programme genline similaire à gen mais qui écrit des lignes contenant "Bonjour" plutôt que des caractères. Par exemple, genline n écrira n fois la ligne Bonjour.
  • Tester vos programmes avec des commandes du type : ./genline p | ./head q
  • Observer lorsque p>q et p<q.

On va maintenant observer l'impact de head sur genline :
  • Modifier genline n pour qu'il imprime sur stderr les lignes "ligne numero i" avec i de 1 à n au fur et à mesure de sa génération.
  • Est-ce que head q modifie le moment où se termine genline p ?

On peut confirmer ou pousser cette observation plus loin:
  • Écrire un programme neverdie.c qui imprime indéfiniment la ligne Bonjour (on l'arrête avec Ctrl-C).
  • Tester ./neverdie | ./head 5

Exercice 5 - Le programme sort

Ce programme lit toutes les lignes de stdin, les range dans un tableau, puis ré-imprime ces lignes sur stdout dans l'ordre alphabétique.

Réécrire cette commande en utilisant une bibliothèque de tri standard (man qsort). Il faut procéder comme suit:
  • allouer un tableau pour stocker les lignes avec malloc ;
  • allouer un buffer pour stocker une ligne avec malloc ;
  • lire des caractères depuis stdin dans le buffer avec fgetc jusqu'à une fin de ligne ou la fin de l'entrée; si nécessaire, augmenter la taille du buffer (man realloc);
  • faire une copie de la ligne dans le tableau des lignes (attention, si votre buffer ne se termine pas par '\0', les fonctions de manipulation de chaînes de caractères vont poser problème) ; éventuellement, agrandir le tableau des lignes si nécessaire ;
  • une fois la dernière ligne lue, trier le tableau (man qsort) en utilisant la fonction suivante:
     
    int comp(const void* a,const void* b) {
    const char** A=(const char**)a;
    const char** B=(const char**)b;
    return strcmp(*A,*B);
    }       
    
  • imprimer le tableau final sur stdout;
  • libérer tout ce qui a été alloué. Pour vérifier que c'est bien le cas, recompilez votre programme avec l'option de compilation -g et lancez ensuite votre programme avec valgrind.

Exercice 6 - Le programme tail

La commande tail n f imprime les n dernières lignes du fichier f sur stdout. C'est beaucoup plus difficile que pour head ou sort car il ne faut surtout pas charger tout le fichier en mémoire. Utilisez fopen pour ouvrir le fichier.

On utilisera l'algorithme suivant. On charge un bloc à la fin du fichier avec fread, et on y cherche les sauts de ligne nécessaires (attention au cas de la dernière ligne!). Si on ne les trouve pas tous (et si on n'a pas déjà lu tout le fichier), on agrandit le buffer, et on recommence. Utilisez fseek et ftell pour vous déplacer dans le fichier.

Peut-on utiliser la même méthode pour travailler avec stdin au lieu d'un fichier f ?