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

StackOverflow https://stackoverflow.com/questions/476354

  •  20-08-2019
  •  | 
  •  

Pregunta

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

Al principio, 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 un pseudo-terminal con openpty. Así que miro la página de manual y veo que openpty llena 2 descriptores de archivos, el maestro y el esclavo. Tanto el código xterm como el rxvt son desordenados debido a la dependencia del sistema de esos archivos especiales.

Entiendo el término termios: es solo un montón de información sobre el código de escape del 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 un terminal, inicia sesión, ejecuta un " ls " en el caparazón sería increíble.

(el inglés no es mi lengua materna, 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;
}    
¿Fue útil?

Solución

Con respecto a la parte maestro / esclavo de su pregunta, de página de comando pty (4) (a la que se hace referencia desde la página de comando 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éntico al descrito en tty (4).   Sin embargo, mientras que todos los demás dispositivos   que proporcionan la interfaz descrita   en tty (4) tienen un dispositivo de hardware de   algún tipo detrás de ellos, el esclavo   el dispositivo tiene, en cambio, otro proceso   manipulándolo a través del maestro   La mitad del pseudo terminal. Es decir,   cualquier cosa escrita en el dispositivo maestro   se le da al dispositivo esclavo como entrada   y cualquier cosa escrita en el esclavo   el dispositivo se presenta como entrada en el   dispositivo maestro.

Las páginas de manual son tus amigos.

Otros consejos

Acabo de probar los ejemplos que se encuentran en este tutorial , funcionan muy bien para mí 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 realiza paso a paso y le siguen ejemplos.

El siguiente ejemplo muestra cómo crear un nuevo pseudo-terminal, y bifurcar el proceso en dos partes, una escribiendo en el lado maestro del pseudo-terminal, la otra lectura del lado esclavo 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top