Wie funktionieren *nix-Pseudo-Terminals? Was ist der Master/Slave-Kanal?

Lesezeit: 5 Minuten

Benutzer-Avatar
aptel

Ich möchte einen einfachen, dummen X-Terminal-Emulator in C auf einem Linux-System schreiben.

Zuerst dachte ich nur, ich müsste eine Shell öffnen und ihre Ausgabe anzeigen. Ich habe den xterm- und rxvt-Code überprüft, und es sieht etwas komplizierter aus.

Zuerst muss ich ein Pseudo-Terminal mit openpty öffnen. Also schaue ich auf die Manpage und sehe, dass openpty 2 Dateideskriptoren füllt, den Master und den Slave. Sowohl der xterm- als auch der rxvt-Code sind chaotisch, da diese speziellen Dateien systemabhängig sind.

Ich verstehe das Termios-Zeug: Es sind nur ein paar Informationen über den Escape-Code des Terminals. Was ich wirklich nicht verstehe, ist: Was soll ich mit dem Master/Slave-Dateideskriptor machen?

Ein Beispielprogramm, das ein Terminal öffnet, sich anmeldet und ein “ls” auf der Shell ausführt, wäre großartig.

(Englisch ist nicht meine Muttersprache, entschuldigen Sie meinen eventuellen Fehler)

Bearbeiten: Hier ist der Beispielcode, den ich mir ausgedacht habe:

#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;
}    

  • Das Befehlszeilenprogramm namens “screen” verwendet dies, denke ich. Damit können Sie eine angemeldete Konsole auf dem Host haben, und wenn Sie abgeworfen werden, können Sie sich wieder anmelden und sich wieder mit dieser Sitzung verbinden und fortfahren. Das ist die Essenz des pty. Es hat einen Kanal, der mit dem Host-System interagiert, und einen “Rückkanal”, über den Sie ihm von außen mitteilen, was es tun soll (und die Ergebnisse sehen). Auch ich habe keine wirkliche Erfahrung mit der Implementierung; Ich habe darüber in “Linux Application Development” gelesen. Unter X gibt es wohl mehr Schaufensterdekoration, aber das zugrunde liegende Prinzip sollte das sein

    – gbarry

    24. Januar 2009 um 17:49 Uhr

Benutzer-Avatar
David C

In Bezug auf den Master / Slave-Teil Ihrer Frage aus dem pty(4) Manpage (auf die von der Manpage openpty(3) auf meinem System verwiesen wird):

Ein Pseudo-Terminal ist ein Paar Zeichengeräte, ein Master-Gerät und ein Slave-Gerät. Das Slave-Gerät stellt einem Prozess eine Schnittstelle zur Verfügung, die mit der in tty(4) beschriebenen identisch ist. Während jedoch alle anderen Geräte, die die in tty(4) beschriebene Schnittstelle bereitstellen, eine Art Hardwaregerät hinter sich haben, hat das Slave-Gerät stattdessen einen anderen Prozess, der es durch die Master-Hälfte des Pseudo-Terminals manipuliert. Das heißt, alles, was auf dem Master-Gerät geschrieben ist, wird dem Slave-Gerät als Eingabe gegeben, und alles, was auf dem Slave-Gerät geschrieben ist, wird als Eingabe auf dem Master-Gerät präsentiert.

Manpages sind deine Freunde.

Benutzer-Avatar
Nils

Ich habe gerade die Beispiele ausprobiert, die auf gefunden wurden dieses Tutorialsie funktionieren für mich sehr gut und ich denke, sie sind ein interessanter Ausgangspunkt für das Problem.

EDIT: Das Tutorial erklärt kurz die Pseudo-Terminals-Funktion. Die Erklärung erfolgt Schritt für Schritt und wird von Beispielen gefolgt.

Das folgende Beispiel zeigt, wie ein neues Pseudo-Terminal erstellt wird, und Gabel der prozess in zwei teile, eine schrift auf die Meister Seite des Pseudo-Terminals, die andere Lesung von der Sklave Seite des Pseudoterminals.

#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

1376100cookie-checkWie funktionieren *nix-Pseudo-Terminals? Was ist der Master/Slave-Kanal?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy