Skip to content

Instantly share code, notes, and snippets.

@RavuAlHemio
Last active August 29, 2015 14:12
Show Gist options
  • Save RavuAlHemio/4960160676013844401b to your computer and use it in GitHub Desktop.
Save RavuAlHemio/4960160676013844401b to your computer and use it in GitHub Desktop.
Redirection von Dateideskriptoren

Dateideskriptoren

Unix-Kernels speichern für jeden Prozess eine Liste von offenen Dateien. Diese Liste können die Prozesse nicht direkt einsehen; sie verwenden stattdessen Dateideskriptoren (file descriptors oder FDs) – ints, die einen Index in diese Tabelle darstellen.

Es gibt konventionell drei „besondere“ FDs: stdin (0), stdout (1) und stderr (2). Prinzipiell unterscheiden sie sich nicht von anderen FDs, doch ihre zweckmäßige Verwendung wird „stark empfohlen“, und die meisten Programmierumgebungen erleichtern das (z.B. schreibt Cs printf auf stdout, und Java bietet ein „fertig geöffnetes“ stdout über java.lang.System.out an).

Prozessstart

Unter Unix werden Prozesse nicht gestartet, sondern „geklont und ersetzt“. Der Systemaufruf fork sorgt dafür, dass ein Klon des laufenden Prozesses erstellt wird und an derselben Stelle wie der ursprüngliche weiterläuft. Hierbei werden offene Dateideskriptoren übernommen. Die Ersetzung erfolgt durch einen exec-Systemaufruf; unter Linux etwa baut die exec-Funktionsfamilie auf dem execve-Systemaufruf auf. Auch hier bleiben die Dateideskriptoren offen – die Ausnahme bilden FDs, bei denen die Option close-on-exec (O_CLOEXEC) gesetzt ist; diese werden geschlossen. (In der Unix-Welt gilt es als Faux-Pas, die drei wohlbekannten FDs mit dem close-on-exec-Flag zu belegen.)

Grundsätzlich gilt also: FDs werden geerbt. Natürlich ist das nicht immer zweckmäßig.

Umbiegen

Wird eine Datei geöffnet (Systemaufruf open), erhält sie normalerweise den niedrigsten freien Dateideskriptor. Der Systemaufruf dup2 kann für die gewünschte Zuordnung sorgen, indem er zwei Dateideskriptoren (oldfd und newfd) entgegennimmt und Folgendes macht:

  1. Falls nötig, schließt er newfd (die aktuelle Belegung des gewünschten Dateideskriptors).
  2. Er veranlasst, dass newfd auf dieselbe offene Datei zeigt wie oldfd.

Damit zeigen jetzt sowohl oldfd als auch newfd auf dieselbe Datei. Falls meine frisch zum Lesen geöffnete Textdatei den Dateideskriptor 6 erhalten hat, kann ich dafür sorgen, dass sie auch den Dateideskriptor 0 bekommt (und damit zu meinem neuen stdin wird – bzw. zum stdin des Prozesses, den ich gleich execen werde).

Jetzt stellt sich natürlich die Frage: wo zeigt stdin hin, wenn ich ein Programm vom Terminalprogramm aus starte?

Terminalprogramme

Beim Öffnen eines neuen Terminalfensters macht ein Terminalprogramm üblicherweise Folgendes:

  1. Es bittet das Betriebssystem um ein neues virtuelles Terminal. (Die hierzu benötigten Aufrufe können von Unix-Variante zu Unix-Variante unterschiedlich sein.) Es erhält Zugriff auf zwei Dateien unter /dev (eine Master-Datei und eine Slave-Datei), die zuvor ggf. automatisch erstellt werden.
  2. Das Terminalprogramm forkt sich.
  3. Der Vaterprozess schließt die Slave-Datei und bereitet sich auf die Kommunikation über die Master-Datei vor.
  4. Der Kindprozess schließt die Master-Datei, biegt die drei wohlbekannten Dateideskriptoren auf die Slave-Datei um, und exect das gewünschte Programm (meist eine Shell).

Jedes Mal, wo das Programm (etwa die Shell) nun auf stdout oder stderr schreibt, geht der Inhalt in die Slave-Datei hinein und kommt bei der Master-Datei (und damit beim lesenden Terminalfensterprozess) wieder raus. Bei einer Eingabe am Terminalfenster wiederum wird diese in die Master-Datei geschrieben, kommt aus der Slave-Datei heraus, und wird ans stdin des Programms geleitet.

Umbiegen in der Bourne-Shell

Die Bourne-Shell sh sowie ihre Nachfolgerinnen (bash, zsh, dash, ksh, ...) erlauben das Umbiegen von Dateideskriptoren mit einer relativ einfachen Syntax:

  • < dateiname öffnet dateiname zum Lesen und biegt sie auf Dateideskriptor 0 um
  • > dateiname öffnet dateiname zum (Über-)Schreiben und biegt sie auf Dateideskriptor 1 um
  • 2> dateiname öffnet dateiname zum (Über-)Schreiben und biegt sie auf Dateideskriptor 2 um
  • >> dateiname öffnet dateiname zum Anhängen und biegt sie auf Dateideskriptor 1 um
  • 2>> dateiname öffnet dateiname zum Anhängen und biegt sie auf Dateideskriptor 2 um
  • 6< dateiname öffnet dateiname zum Lesen und biegt sie auf Dateideskriptor 6 um
  • 7>> dateiname öffnet dateiname zum Anhängen und biegt sie auf Dateideskriptor 7 um

Hier gilt aber: was nicht umgebogen wird, wird vererbt. Falls mein Terminalprogramm nun für meine Shell das virtuelle Terminal /dev/pty/2 zugewiesen bekommen hat, und ich starte myprog mit dem Befehl myprog >/dev/null, ergibt sich folgende Situation:

  • stdin wird von der Shell geerbt und zeigt auf /dev/pty/2.
  • stdout wurde von der Shell auf /dev/null umgebogen und zeigt also dorthin.
  • stderr wird von der Shell geerbt und zeigt auf /dev/pty/2.

Seekbarkeit

Manche Dateideskriptoren sind seekbar, d.h. es kann eine seek-Funktion (z.B. lseek) darauf verwendet werden, um die aktuelle Lese-/Schreib-Position innerhalb der Datei zu verändern. Dies trifft natürlich nur auf Dateien mit Random-Access-Möglichkeit zu, etwa reguläre Dateien oder Festplatten-Gerätedateien. Andere Dateitypen, etwa Pipes oder virtuelle Terminals, erlauben dies nicht. (Bandlaufwerke sind ein interessanter Fall: meist sind sie seekbar, doch die seek-Operation ist extrem zeitaufwändig und damit möglichst zu vermeiden.)

Seekbarkeit ist keine Eigenschaft eines FDs, sondern eine Eigenschaft der dahinterliegenden Datei. Folglich gilt etwa: wenn die Datei, auf die stdin zeigt, seekbar ist, ist stdin seekbar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment