Pseudo Terminal Master / Pseudo Terminal Slave

Tu as “pt” ou tu n’as pas “pt”. (Pseudo Terminal)
Les premiers développements de programme C utilisent souvent des structures de données capables de lire le clavier dans une console et d’écrire dans une console. Ses structures sont aux nombres de trois : stdin, stdout, stderr. Ses structures représentent les fameuses entrées/sorties des programmes. Dans mes études, j’ai appris à manipuler ses structures pour comprendre le fonctionnement et maitriser les arcanes des entrées et sorties des programmes consoles. Le sujet va couvrir essentiellement Linux.
Du standard C à la vérité du noyau Linux
Le standard C utilise des structures définies en tant que pointeur FILE pour ses entrées et sorties. Ainsi, toutes les fonctions en relation avec des fichiers sont compatibles avec les entrées/sorties. Il est dit que le comportement attendu de l’entrée est qu’elle est en lecture et que par conséquent, si on souhaite lire le contenu du clavier, il faut utiliser une fonction de lecture. Inversement, les sorties devront être écrites pour afficher du texte. Il n’y a qu’une seule entrée standard, je mettrais toujours au singulier le mot entrée quand je parle d’une seule entrée pour un unique processus.
Sauf que sur Linux, ses trois fichiers sont un seul et unique même fichier. Alors, ce n’est pas toujours le cas, notamment, si vous utilisez un interpréteur de commande comme le programme bash, celui-ci peut rediriger les flux d’entrée et sorties. Mais avant de se lancer dans cette explication, je dois vous dire comment on exécute un programme sous la norme POSIX.
Le lancement d’un programme
Un programme se lance toujours à partir d’un autre programme. S’il exécute directement un programme, son espace mémoire est remplacé. Celui-ci s’arrête pour être un nouveau programme, la configuration de l’environnement, son numéro d’identifiant et de ses fichiers ouverts ne changent pas. Ainsi, un programme comme Bash utilise une autre fonction qui lui permet de se dédoubler (fork). Fork est comme la division cellulaire, c’est un autre processus avec les mêmes caractéristiques excepté certains paramètres comme son identifiant. Ainsi, pour exécuter un programme, les interpréteurs de commande se dupliquent puis se configurent et enfin exécutent le programme.
Les fichiers sous Linux
Linux et d’autres systèmes d’exploitation utilisent ce que l’on nomme des descripteurs de fichier pour donner aux processus la possibilité d’interaction avec des fichiers. Ce sont des nombres, des numéros. Par convention, les numéros 0, 1 et 2 sont l’entrée et les sorties des programmes. Le numéro 0 pour l’entrée, 1 et 2 pour les sorties normales et d’erreur. Ainsi en C, la structure FILE* nommée stdin pour lire le clavier est lié au descripteur de fichier 0. Idem pour stdout qui est lié au numéro 1 et à stderr qui lui est lié au numéro 2. Si un processus essaie d’ouvrir un fichier, alors c’est normalement le numéro le plus bas qui lui sera fourni. Typiquement, si je lance le programme de lecture de fichier “cat” avec en paramètre “/etc/passwd”, voici ce qui se passe : “cat” va demander une ouverture de fichier “/etc/passwd” par le kernel Linux. Le kernel fournit toutes ses opérations et choisis un numéro. Il regarde la liste des numéros disponible et lui fournit le 3, car le 0, 1 et 2 ne sont pas disponibles. Le programme, “cat” va utiliser les opérations de lecture avec le numéro 3.
Sachez que l’on peut manipuler ses numéros de descripteur de fichier. C’est grâce à la fermeture d’un descripteur de fichier que le kernel désalloue le numéro du descripteur de fichier. Et oui ! Fermer un fichier ne signifie pas que celui-ci n’est plus utilisé ! C’est quand tous les numéros de descripteur de fichier lié à un fichier ne sont plus alloués que le fichier est bien fermé ! Une autre fonction permet de dupliquer un descripteur de fichier et de lui mettre un numéro de descripteur de fichier voulu ! C’est la fonction dup2. Si vous avez tout compris, un programme peut choisir son numéro de descripteur de fichier.
Bash utilise cette méthode pour changer les entrées sorties d’un programme. Le programme bash se duplique avec fork et peut choisir de changer les descripteurs de fichier 0,1 et 2 pour que le programme écrive ou reçoive des données depuis une autre source que la console. Ses autres sources peuvent avoir n’importe quelle nature du moment que l’on trouve un descripteur de fichier : une socket réseau, un emplacement de mémoire vive, un fichier, l’entrée et la sortie d’un programme, un tube, etc.
Les vrais STDIN, STDOUT et STDERR
Pour obtenir les vrais fichiers des entrée et sorties, on peut lister les descripteurs de fichier de notre interpréteur de commande :
$ ls -l /proc/$$/fd/
lrwx------ 1 mafilus mafilus 64 sept. 19 06:23 0 -> /dev/pts/6
lrwx------ 1 mafilus mafilus 64 sept. 19 06:23 1 -> /dev/pts/6
lrwx------ 1 mafilus mafilus 64 sept. 19 06:23 2 -> /dev/pts/6
On voit des liens vers /dev/pts/6 pour notre entrée et nos sorties. Quel est ce fichier /dev/pts/6 ?
Pseudo Terminal
Ce “pts” signifie “Pseudo Terminal Slave”. Il s’agit d’un fichier simulé par le kernel Linux. Il n’existe pas sur le disque dur ! Il a la particularité d’être un terminal. El famoso console :
Ouais, tout ce que vous voyez, c’est ce fichier qui simule les caractéristiques d’un terminal. Par contre, il ne s’occupe pas de l’affichage. C’est à l’émulateur de terminal de tout vous afficher.
Alors comment que le bidule s’utilise ?
C’est très simple. Un processus ouvre /dev/ptmx avec la fonction open (ça veut dire ouvrir). Puis c’est bon, il a le terminal maître. Il va faire quelques modifications pour qu’ils soient accessibles. Enfin, le processus appelle ptsname pour obtenir le nom du fichier du terminal esclave et l’ouvre. Il y a deux descripteurs de fichier liés à un terminal !
Deux descripteurs de fichier sont nécessaires pour les pseudos terminaux. Voici un schéma naïf qui représente les composants des pseudos terminaux :
Pourquoi naïf ? Car dans les faits, un pseudo terminal n’est pas attaché au clavier et à l’écran directement. Non ! Il est rattaché à un processus ! N’oubliez pas que les pseudo terminaux doivent toujours être ouverts par le fichier ptmx ! Voici un exemple plus proche de la vérité :
Le processus xterm est un émulateur de terminal. Il utilise donc les pseudos terminaux… Les serveurs SSH utilisent aussi les pseudos terminaux. Ils jouent un role de multiplexeur entre la partie réseau et terminal.
Mais pourquoi un maître et un esclave ?
Le rôle de l’esclave est d’être attaché à un pseudo terminal qui quand on écrit dedans simule une sortie sur la console. En lecture, il attend le clavier. Le rôle du maître est de simuler l’entrée du clavier en écrivant dessus et de lire le contenu du terminal pour lire sa sortie. Le maître est l’opposé de l’esclave.
D’autres fonctions des pts
Les pseudos terminaux peuvent avoir des modes de fonctionnement différents pour simuler des comportements différents. Il existe des modes comme le mode RAW, qui permet au terminal de ne plus avoir de comportement spécial comme le fait de fournir un retour lors d’une touche frappé au clavier. Les pseudos terminaux servent aussi d’interface pour gérer les colonnes et les lignes.
Petite conclu là, des !
On a vu le fonctionnement des PTS et c’est déjà pas mal ! Il existe des petites configurations des terminaux. Par exemple, on peut s’en servir pour lire des ports séries. Je n’ai pas abordé le mode canonique. Donc la seule lecture de ma page ne vous fournira pas toutes les informations nécessaires sur les terminaux. N’empêche que pour trouver des informations à ce sujet, il faut savoir chercher et expérimenter. Je vous laisse avec ce bout de code pour faire joue-joue avec les pts !
#define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fdm, fds;
char *pts_name;
fdm = posix_openpt(O_RDWR); // ouvre le /dev/ptmx
if (fdm < 0) {
perror("Erreur ouverture maître PTY");
exit(1);
}
if (grantpt(fdm) < 0) {
perror("Erreur grantpt");
exit(1);
}
if (unlockpt(fdm) < 0) {
perror("Erreur unlockpt");
exit(1);
}
pts_name = ptsname(fdm);
if (pts_name == NULL) {
perror("Erreur ptsname");
exit(1);
}
printf("PTY esclave : %s\n", pts_name);
fds = open(pts_name, O_RDWR);
if (fds < 0) {
perror("Erreur ouverture esclave PTY");
exit(1);
}
printf("Pseudo-terminal alloué avec succès\n");
// ici vous pouvez fork exec
// dans le nouveau processus fermer 0,1,2 avec close(0);close(1);close(2)
// faite dup2(fds,0); dup2(fds,1);dup2(fds,2);
// puis exec
// dans le processus parent. Vous pouvez envoyer des données avec write pour controler le nouveau processus
// ceci fonctionne même avec des programmes qui alloue du curses comme vim
close(fdm);
close(fds);
return 0;
}
Des bisous !