L'encapsulation
Les pseudo-terminaux

--ooOoo--

          Les "pseudo-terminaux" font partie des outils standard du système Unix/Linux. Cette fonctionnalité permet de préfacer des outils ou utilitaires existants, systèmes ou autres, qui se servent essentiellement du "stdin", "stdout" et "stderr" :

          Les développeurs sous Unix disposent d'un debugger "gdb" fourni avec le système. Il est très peu prisé en général car il fonctionne en mode ligne ce qui oblige à  beaucoup de saisie. Il faut aimer !... C'est pourquoi il est proposé des debuggers plus conviviaux comme "cgdb" (debugger-console avec les curses"), "xdbx" et "ddd" (debuggers graphiques) qui encadrent la commande "gdb" de base suivant la technique des pseudo-terminaux.

          Sous Windows, j'avoue ne pas connaître d'outil équivalent tout en restant persuadé qu'il existe une technique pour faire le même travail. Durant mon activité, je n'était qu'un simple utilisateur de ce système. Mon PC était un terminal connecté au réseau Ethernet de l'usine pour pouvoir accéder aux différents calculateurs sur lesquels je travaillais grâce à différents émulateurs de terminaux : 3279 pour l'IBM OS/390, VT200 pour les 2 VAX/VMS, Questar DKU 7100 pour Honeywell Bull DPS 6, VT100 et X11 pour les diverses stations de travail de type Unix, SUN, HP, DEC, Silicon Graphics. Durant ma préretraite, je n'étais mis un peu à programmer en C, mais c'était sous MSDOS, avec Windows 3.1, lancé par une commande "win".

- Rappel :

          La description du fonctionnement des pseudo-terminaux est largement commentée sur la Toile. Aussi, je n'entrerai pas trop dans le détail sur ce sujet. Mais pour faciliter l'exposé, j'en fais un petit rappel :

      Le schéma ci-contre en illustre le principe. L'encapsulation implique la présence de deux processus :

          Pour que ces deux processus puissent converser entre eux, il faut obligatoirement que l'un "écoute" pendant que l'autre "parle". Le processus-père crée alors une paire de pseudo-terminaux appelés "PTY", terminal-maître "PTM" et terminal-esclave "PTS" pour lesquels la sortie de l'un est d'emblée connectée à l'entrée de l'autre grâce à une fonction-système "getpt()".

          Mais, à cet instant, la paire de pseudo-terminaux n'appartient qu'au processus-père. Il n'existe pas encore de processus-fils. Le processus-père crée un processus-fils avec la fonction-système "fork()". Ce processus-fils est un clone du processus-père c'est-à dire, qu'à partir de l'instruction qui appelle la fonction "fork()", le processus-père est purement et simplement dupliqué en mémoire et le processus-fils ainsi créé hérite des caractéristiques du processus-père, en particulier des pseudo-terminaux.

          Dès lors, le processus-père va pouvoir dialoguer avec son processus-fils. Sur la Toile, il y a beaucoup d'exemples de dialogue processus-père - processus-fils. Comme c'est le même programme qui est processus-père et processus-fils, la fonction "fork()" renvoie un code qui indique dans quel processus on se trouve.

          Enfin, le but recherché n'est pas d'avoir un doublon en mémoire, mais la possibilité de dialoguer avec un programme externe choisi, prêt à l'emploi. Comment donc remplacer le processus-fils par ce programme externe voulu ? Le système nous fournit toute une série de fonction "exec()" qui remplit cette tâche.

          Il reste une dernière chose : le processus-fils a en compte, son "stdin", son "stdout" et la paire de pseudo-terminaux "PTY" héritée . Le processus-père, en faisant un WRITE sur sa sortie "PTM" envoie les données sur l'entrée "PTS" du processus-fils. Or c'est dans le "stdin" de ce dernier qu'il s'agit de les envoyer. Le processus-père, en faisant un READ sur son entrée "PTM" veut recevoir des données en provenance de la sortie "PTS" du processus-fils. Or c'est à partir du "stdout" de ce dernier qu'il s'agit de les récupérer. Cette substitution de la sortie "PTS" du processus-fils par son "stdout" et la substitution de son entrée "PTS" par son "stdin" se fera à l'aide de la fonction-système "dup()" ou "dup2()".

          Un exemple de programmation est donnée sur la Toile vers la fin de la page web :

http://www.unixgarden.com/index.php/programmation/
utilisation-des-pseudo-terminaux-pour-piloter-les-programmes-interactifs
 "

Une remarque sur la création de la paire de pseudo-terminaux :

          Pour créer une paire de pseudo-terminaux, trois manières de faire sont possible comme on peut le voir dans le programme "encaps.c" au §II :

  1. On fait appel à la fonction "getpt()" comme indiquée plus haut,

  2. On explicite la recherche d'une entrée par open() successifs sur "/dev/pty??" où "??" peut prendre les valeurs allant de "p0" à "sf",

  3. On fait appel à la fonction "posix_openpt()" telle qu'elle est proposée dans l'exemple de programmation donné sur la Toile juste au dessus.

          Il y en aurait une quatrième qui fait appel à la fonction "pty_fork()" ainsi que les fonctions "openpty(), forkpty()". Je ne les ai pas essayées mais elle ne me semble pas aussi simple qu'elles y paraissent.

- La première proposition de programme :

          Ce fut une petite galère qui a duré pas mal de temps à cause d'un modérateur au jugement quelque peu hâtif qui me reprocha de vouloir faire faire mon "travail par les autres" et qui me fila au passage un avertissement, ce que je n'ai pas du tout apprécié :

          Donc,  !... Mais bon, la fougue de la jeunesse.... Je me suis débrouillé tout seul et avec un peu de persévérance et un peu de chance aussi, j'ai fini par tomber un peu par hasard sur la solution en analysant les sources de GPM (General Purpose Mouse Interface). Mon problème s'est vite réglé avec la magique fonction "select()" que j'ai découverte.

          En fin de compte, je ne suis pas sûr que cela mérite réponse !... Bref, après cet aparté, "revenons à nos moutons...sssss" comme disait Topaze.

          Pour mettre au point ma petite fonction d'encapsulation, je me suis basé sur un petit prototype de programme écrit en C à encapsuler, nommé "mon_programme.c" (source visible). Il ne fait que des "printf()" et un "fgets()". Ce petit programme, comme je l'ai déjà dit, pourrait être écrit par un élève dans le langage de son choix.

          L'image ci-dessous montre le compte rendu de l'exécution du programme exécuté directement dans la console :

          Le commande "encaps.c" (source visible) permet d'encapsuler ce programme utilisateur pour en traiter les entrées et les sorties. Il crée tout d'abord l'environnement de dialogue en particulier la paire de pseudo-terminaux. Au passage, le sélecteur "nop = 1/2/3" permet de choisir l'une des manières de créer la paire de pseudo-terminaux (j'ai préféré le switch à l'#ifdef pour une question de lisibilité).

          La commande "encaps.c" ne peut pas préjuger du traitement des entrées et des sorties du programme utilisateur. Ce dernier est le seul à savoir les interpréter. L'utilisateur aura donc la charge de programmer le traitement de ses entrées et sorties dans 5 fonctions dont les noms sont imposés par convention. Dans l'exemple "encaps_init.c" (source visible), on ne fait qu'afficher un message préfixe pour montrer que l'on entre bien dans chaque point d'entrée :

  1. le point d'entrée " int encaps_sigint(int signum)" : ce point d'entrée permet de traiter l'appui sur la touche CTRL/C.

  2. le point d'entrée " int encaps_init(int argc , char argv[])" : ce point d'entrée permet éventuellement d'effectuer certaines initialisations préliminaires.

  3. le point d'entrée "int encaps_recu(char ligne, int lg)" : ce point d'entrée traite la ligne courante sortie sur le stdout du programme encapsulé.

  4. le point d'entrée "int encaps_envoi(char buffer, int lg)" : ce point d'entrée traite la saisie effectuée sur le stdin du programme encapsulé.

  5. le point d'entrée "int encaps_term()" : ce point d'entrée permet éventuellement de clore proprement la commande.

          Cela n'empêche pas l'utilisateur de travailler directement dans le source "encaps.c". L'image ci-après donne le compte rendu de la sortie du programme encapsulé par la commande "encaps.c" :

          Voici donc la proposition que j'avais faite au professeur de mathémétiques, il y a quelques temps déjà. Après quelques explications complémentaires verbales, nous nous êtions quittés en convenant de nous revoir un peu plus tard. Apparement, cela avait l'air de lui convenir. Je m'étais bien gardé d'anticiper sur ce qu'il voulait faire, je m'étais seulement limité à sa demande brute. La proposition était donc basique, un peu trop basique peut-être.....

          ... Car, quelques deux ou trois semaines plus tard, le troisième trimestre scolaire étant bien entamé, nous nous rencontrâmes et là, à voir un peu sa mimique, je compris qu'il avait quelques soucis. Il avait du mal à démarrer, il avait quelques difficultés à apréhender le problème pour ce qu'il aurait aimé faire.

Alors, voici donc la dernière proposition : la commande "lpgrp.c"

--ooOoo--