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).
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.
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:
- Falls nötig, schließt er
newfd(die aktuelle Belegung des gewünschten Dateideskriptors). - Er veranlasst, dass
newfdauf dieselbe offene Datei zeigt wieoldfd.
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?
Beim Öffnen eines neuen Terminalfensters macht ein Terminalprogramm üblicherweise Folgendes:
- 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. - Das Terminalprogramm
forkt sich. - Der Vaterprozess schließt die Slave-Datei und bereitet sich auf die Kommunikation über die Master-Datei vor.
- 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.
Die Bourne-Shell sh sowie ihre Nachfolgerinnen (bash, zsh, dash, ksh, ...) erlauben das Umbiegen von Dateideskriptoren mit einer relativ einfachen Syntax:
< dateinameöffnetdateinamezum Lesen und biegt sie auf Dateideskriptor 0 um> dateinameöffnetdateinamezum (Über-)Schreiben und biegt sie auf Dateideskriptor 1 um2> dateinameöffnetdateinamezum (Über-)Schreiben und biegt sie auf Dateideskriptor 2 um>> dateinameöffnetdateinamezum Anhängen und biegt sie auf Dateideskriptor 1 um2>> dateinameöffnetdateinamezum Anhängen und biegt sie auf Dateideskriptor 2 um6< dateinameöffnetdateinamezum Lesen und biegt sie auf Dateideskriptor 6 um7>> dateinameöffnetdateinamezum 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:
stdinwird von der Shell geerbt und zeigt auf/dev/pty/2.stdoutwurde von der Shell auf/dev/nullumgebogen und zeigt also dorthin.stderrwird von der Shell geerbt und zeigt auf/dev/pty/2.
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.