2009-01-24 9 views
23

Ich möchte einen einfachen, dummen X-Terminal-Emulator in C auf einem Linux-System schreiben.Wie funktionieren * nix Pseudo-Terminals? Was ist der Master/Slave-Kanal?

Zuerst dachte ich nur, ich müsste eine Shell poppen und ihre Ausgabe anzeigen. Ich habe Xterm und RXVT-Code überprüft, und es sieht ein bisschen komplizierter aus.

Zuerst muss ich ein Pseudo-Terminal mit openpty öffnen. Also schaue ich auf die man-Seite und sehe, dass openpty 2 Dateideskriptoren füllt, den Master und den Slave. Sowohl xterm als auch rxvt-Code sind wegen der systemabhängigen Eigenschaften dieser speziellen Dateien unübersichtlich.

Ich verstehe die termios Sachen: es ist nur eine Reihe von 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, meinen eventuellen Fehler entschuldigen)

Edit: Hier ist der Beispielcode kam ich mit:

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

Das Befehlszeilenprogramm namens "Bildschirm" verwendet dies, denke ich. Damit können Sie eine angemeldete Konsole auf dem Host haben, und wenn Sie abgewiesen werden, können Sie sich wieder anmelden und die Verbindung mit dieser Sitzung wiederherstellen und fortfahren. Das ist die Essenz der Pty. Es hat einen Kanal, der mit dem Host-System interagiert, und einen "Rückkanal", der Ihnen extern sagt, was zu tun ist (und die Ergebnisse sehen). Auch ich habe keine wirklichen Erfahrungen mit der Implementierung eines; Ich habe über sie in "Linux Application Development" gelesen. Unter X denke ich, es gibt mehr Schaufensterdekoration, aber das zugrunde liegende Prinzip sollte sein – gbarry

Antwort

19

Hinsichtlich den Master/Slave-Teil Ihre Frage, von dem pty(4) Manpage (die aus dem openpty (3) man-Seite 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 beschriebene Schnittstelle bereitzustellen (4) eine Hardware-Gerät von hinter ihnen eine Art hat der Slave Gerät stattdessen ein anderer Prozess es durch den Master Hälfte des Handhabungs Pseudo-Terminal. Das heißt, alles auf dem Master-Gerät geschrieben an das Slave-Gerät als Eingabe gegeben und alles auf dem Gerät Slave geschrieben wird als Eingabe auf dem Master-Gerät vorgestellt.

Manpages sind Ihre Freunde.

1

Ich habe gerade versucht die Beispiele zu finden this tutorial, sie funktionieren sehr gut für mich 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 es folgen Beispiele.

Das folgende Beispiel zeigt, wie eine neue Pseudo-Terminal zu schaffen, und fork das Verfahren in zwei Teile, einen Schriftzug auf der Master Seite des Pseudo-Terminal, das Lesen der andere von der Slave Seiten des Pseudo-Terminals.

#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