pipe, fork, exec und dup/dup2 (Programmieren einer Shell)



  • Hallo,

    ich habe die Aufgabe, eine einfache Shell zu programmieren, welche aber auf jeden Fall das Suspendieren von Prozessen und damit eine einfache Jobverwaltung können muss.

    Suspendiert man einen Prozess und stellt ihn dann in den Hintergrund, so sollte er natürlich seine Standardeingabe nicht mehr direkt vom Filedeskriptor 0 beziehen können. Um dies vom Elternprozess (also der Shell selbst) aus noch manipulieren zu können, ist es notwendig, dass Shell- und Kindprozess über eine Pipe kommunizieren und der Kindprozess seine Standardeingabe aus dieser Pipe bezieht.
    Wird der Kindprozess nun in den Hintergrund gestellt, so muss die Shell nur noch genau diese Kommunikationsverbindung vorrübergehend kappen.

    Wie das Umleiten der Standardeingabe aus einer Pipe (für Kindprozess) funktioniert, ist mir klar und das macht auch keine Probleme.
    Ich habe allerdings noch nicht wirklich verstanden, wie ich denn auf der Elternseite (vor dem fork) die Pipe zu erzeugen und diverse Filedeskriptoren zu duplizieren habe.
    Ich habe schon sehr viel rumexperimentiert, bin aber zu keinem richtigen Ergebnis gekommen. 😞



  • Im Grunde genommen einfach, wenn man es denn mal verstanden hat 😉

    Angenommen wir betrachten erstmal nur eine Pipe, und zwar eine aus der der Elternprozess die Daten lesen kann die der Kindprozess auf stdout schreibt.

    Zunächst wird die Pipe ja erzeugt, mittels pipe() auf einem zwei-elementigen Integer-Array. Danach ist Element 0 die Lese-Seite (Eselsbrücke: 0 ist immer die Nummer von stdin) und Element 1 die Schreib-Seite (1 ist auch immer die Nummer von stdout).

    Danach wird geforked, wodurch der Prozess gespalten wird. Auf der Kindseite möchten wir jetzt dass der Prozess jede Ausgabe auf den Descriptor 1 in Wirklichkeit in unsere Pipe schreibt. Dafür dupen wir die Schreib-Seite der pipe nachdem wir stdout geschlossen haben (das Betriebssystem nimmt immer die niedrigste mögliche Dateinummer beim Öffnen). Die Lese-Seite der Pipe schliessen wir auch, da die ja vom Elternprozess benutzt werden soll.

    Im Elternprozess schliessen wir nun noch die Schreib-Seite der Pipe, aus demselben Grund.

    (Fast) Pseudocode:

    int pipefd[2];
    
    pipe(pipefd);
    
    if (fork() == kind) {
      close(1); // stdout schliessen
      dup(pipefd[1]); // pipefd[1] doppeln, ergibt 1 (niedrigste freie Nummer)
      // Abkürzung für diese beiden Befehle:
      // dup2(1, pipefd[1]);
    
      close(pipefd[0]); // Lese-Seite wird hier nicht gebraucht
      exec(...);
      _exit(127);
    }
    
    // Elternprozess
    close(pipefd[1]); // Schreib-Seite wird hier nicht gebraucht
    read(pipefd[0], buffer, sizeof(buffer); // ...
    


  • Danke für die schnelle Antwort.
    Das Problem ist jedoch, dass der Kindprozess DIE Daten aus der Pipe holen können muss, welche der Elternprozess als Standardeingabe hat.

    Wie gesagt, es muss dem Elterprozess möglich sein, seinem Kindprozess die Standardeingabe zu entziehen und zu einem späteren Zeitpunkt wiederherzustellen.

    Das Kind darf also auf keinen Fall seine Standardeingabe von FD 0 beziehen, sondern muss hierfür einen anderen Filedeskriptor zugewiesen bekommen.
    Der Elternprozess jedoch ist dafür verantwortlich, dass auf diesem Filedeskriptor auch wirklich die Daten ankommen, die jemand auf der Tastatur tippt.

    Es wäre also zusagen keine pipe "aus der der Elternprozess die Daten lesen kann die der Kindprozess auf stdout schreibt".

    Sondern eine pipe, aus der der Kindprozess die Daten lesen kann, die beim Elternprozess auf stdin ankommen.

    Ich kann mir ja selbst nicht so richtig erklären wie das genau funktionieren soll, ohne dass der Elternprozess explizit die Daten von stdin liest und aktiv an das Kind weiterleitet.
    Aber es geht wohl wirklich irgendwie anders.



  • Fällt mir ehrlichgesagt ohne länger drüber nachzudenken auch nichts ein, aber folgendes hilft Dir vielleicht weiter:

    Implementing a Shell - The GNU C Library



  • Danke! Werd ich mir mal anschauen.



  • In dieser Beschreibung wird die Funktion "tcsetpgrp(...)" benutzt, um eine Prozessgruppe in den Vordergrund zu holen. D.h. ihr stdin als Eingabe zuzuweisen.

    int tcsetpgrp(int fd, pid_t pgrp);

    The function tcsetpgrp() makes the process group with process group ID pgrp the foreground process group on the terminal associated to fd, which
    must be the controlling terminal of the calling process, and still be associated with its session. Moreover, pgrp must be a (nonempty) process
    group belonging to the same session as the calling process.

    Das wäre zwar eventuell eine Möglichkeit, aber das muss man auch alles manuell über pipe und mittels dup lösen können.

    Falls also noch jemand eine Idee haben sollte, dann immer her damit. 🙂


Anmelden zum Antworten