¿Cómo funcionan los pseudo-terminales *nix? ¿Cuál es el canal maestro/esclavo?

5 minutos de lectura

avatar de usuario
aptel

Quiero escribir un emulador de terminal X simple y tonto en C en un sistema Linux.

Al principio, solo pensé que tendría que abrir un shell y mostrar su salida. Revisé el código xterm y rxvt, y parece un poco más complicado.

Primero, tengo que abrir una pseudo-terminal con openpty. Así que miro la página de manual y veo que openpty llena 2 descriptores de archivo, el maestro y el esclavo. Tanto el código xterm como el rxvt son complicados debido a la dependencia del sistema de esos archivos especiales.

Entiendo las cosas de termios: es solo un montón de información sobre el código de escape de la terminal. Lo que realmente no entiendo es: ¿qué se supone que debo hacer con el descriptor de archivo maestro/esclavo?

Un programa de ejemplo que abre una terminal, inicia sesión, ejecuta un “ls” en el shell sería increíble.

(El inglés no es mi idioma nativo, disculpe mi eventual error)

Editar: aquí está el código de muestra que se me ocurrió:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pty.h>
#include <utmp.h>
#include <ctype.h>

void
safe_print (char* s)
{
    while(*s) { 
        if(*s == '\n')
            putchar("\n");
        else if(iscntrl(*s))
            printf("\\e(%d)", *s);
        else
            putchar(*s);
        s++;
    }
}


int
main (int argc, char** argv)
{
    char buf[BUFSIZ] = {0};
    int master;
    int ret = forkpty(&master, NULL, NULL, NULL);

    if(ret == -1)
        puts("no fork"), exit(0);

    if(!ret) { 
        execl("/bin/sh", "sh", NULL);
        exit(0);
    }

    sleep(1); /* let the shell run */


    if(argc >= 2) {
        write(master, argv[1], strlen(argv[1]));
        write(master, "\n", 1);
    } else {
        write(master, "date\n", sizeof "date\n");
    }


    while(1) {
        switch(ret = read(master, buf, BUFSIZ)) {
        case -1:
            puts("error!"); 
            exit(1);
            break;
        case 0:
            puts("nothing.."), sleep(1);
            break;
        default:
            buf[ret] = '\0';
            safe_print(buf);

        }
    }

    close(master);

    return 0;
}    

  • Creo que el programa de línea de comandos llamado “pantalla” usa esto. Con eso, puede tener una consola iniciada en el host y, si lo rechazan, puede volver a iniciar sesión y volver a conectarse con esa sesión, y continuar. Esa es la esencia del pty. Tiene un canal que interactúa con el sistema host y un “canal de respaldo” que usted, externamente, le dice qué hacer (y ve los resultados). Yo tampoco tengo ninguna experiencia real con la implementación de uno; Leí sobre ellos en “Desarrollo de aplicaciones Linux”. Debajo de X, supongo que hay más decoración, pero el principio subyacente debería ser el

    – gbarry

    24 de enero de 2009 a las 17:49

avatar de usuario
david c

Con respecto a la parte maestro/esclavo de su pregunta, desde el pty(4) página de manual (a la que se hace referencia desde la página de manual de openpty(3) en mi sistema):

Un pseudo terminal es un par de dispositivos de caracteres, un dispositivo maestro y un dispositivo esclavo. El dispositivo esclavo proporciona a un proceso una interfaz idéntica a la descrita en tty(4). Sin embargo, mientras que todos los demás dispositivos que proporcionan la interfaz descrita en tty(4) tienen algún tipo de dispositivo de hardware detrás, el dispositivo esclavo tiene, en cambio, otro proceso que lo manipula a través de la mitad maestra del pseudo terminal. Es decir, cualquier cosa escrita en el dispositivo maestro se entrega al dispositivo esclavo como entrada y cualquier cosa escrita en el dispositivo esclavo se presenta como entrada en el dispositivo maestro.

Las páginas man son tus amigas.

avatar de usuario
Nilos

Acabo de probar los ejemplos encontrados en este tutorialme funcionan muy bien y creo que son un punto de partida interesante para el problema.

EDITAR: El tutorial explica brevemente la función de pseudo-terminales. La explicación se hace paso a paso y va seguida de ejemplos.

El siguiente ejemplo muestra cómo crear un nuevo pseudo-terminal, y tenedor el proceso en dos partes, una escribiendo en el Maestro lado de la pseudo-terminal, la otra lectura de la esclavo lado del pseudo-terminal.

#define _XOPEN_SOURCE 600 
#include <stdlib.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <unistd.h> 
#include <stdio.h> 
#define __USE_BSD 
#include <termios.h> 


int main(void) 
{ 
int fdm, fds, rc; 
char input[150]; 

fdm = posix_openpt(O_RDWR); 
if (fdm < 0) 
{ 
fprintf(stderr, "Error %d on posix_openpt()\n", errno); 
return 1; 
} 

rc = grantpt(fdm); 
if (rc != 0) 
{ 
fprintf(stderr, "Error %d on grantpt()\n", errno); 
return 1; 
} 

rc = unlockpt(fdm); 
if (rc != 0) 
{ 
fprintf(stderr, "Error %d on unlockpt()\n", errno); 
return 1; 
} 

// Open the slave PTY
fds = open(ptsname(fdm), O_RDWR); 
printf("Virtual interface configured\n");
printf("The master side is named : %s\n", ptsname(fdm));

// Creation of a child process
if (fork()) 
{ 
  // Father
 
  // Close the slave side of the PTY 
  close(fds); 
  while (1) 
  { 
    // Operator's entry (standard input = terminal) 
    write(1, "Input : ", sizeof("Input : ")); 
    rc = read(0, input, sizeof(input)); 
    if (rc > 0) 
    {
      // Send the input to the child process through the PTY 
      write(fdm, input, rc); 

      // Get the child's answer through the PTY 
      rc = read(fdm, input, sizeof(input) - 1); 
      if (rc > 0) 
      { 
        // Make the answer NUL terminated to display it as a string
        input[rc] = '\0'; 

        fprintf(stderr, "%s", input); 
      } 
      else 
      { 
        break; 
      } 
    } 
    else 
    { 
      break; 
    } 
  } // End while 
} 
else 
{ 
struct termios slave_orig_term_settings; // Saved terminal settings 
struct termios new_term_settings; // Current terminal settings 

  // Child

  // Close the master side of the PTY 
  close(fdm); 

  // Save the default parameters of the slave side of the PTY 
  rc = tcgetattr(fds, &slave_orig_term_settings); 

  // Set raw mode on the slave side of the PTY
  new_term_settings = slave_orig_term_settings; 
  cfmakeraw (&new_term_settings); 
  tcsetattr (fds, TCSANOW, &new_term_settings); 

  // The slave side of the PTY becomes the standard input and outputs of the child process 
  close(0); // Close standard input (current terminal) 
  close(1); // Close standard output (current terminal) 
  close(2); // Close standard error (current terminal) 

  dup(fds); // PTY becomes standard input (0) 
  dup(fds); // PTY becomes standard output (1) 
  dup(fds); // PTY becomes standard error (2) 

  while (1) 
  { 
    rc = read(fds, input, sizeof(input) - 1); 

    if (rc > 0) 
    { 
      // Replace the terminating \n by a NUL to display it as a string
      input[rc - 1] = '\0'; 

      printf("Child received : '%s'\n", input); 
    } 
    else 
    { 
      break; 
    } 
  } // End while 
} 

return 0; 
} // main

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad