HOWTO · C
Dateideskriptor in C
Dieser Artikel erläutert den Dateideskriptor in der Programmiersprache C.
Auf dieser Seite
Dieser Artikel erläutert Dateideskriptoren in der Programmiersprache C.
Funktionsweise von Dateideskriptoren
Ein Dateideskriptor ist eine kleine, nichtnegative Ganzzahl, die ein Prozess als Handle für eine geöffnete E/A-Ressource verwendet. Die Zahl ist ein Eintrag in der Deskriptortabelle dieses Prozesses; sie ist weder die Datei selbst noch identifiziert sie eine Ressource systemweit eindeutig. Das Betriebssystem ordnet dem Deskriptor den Zustand der geöffneten Datei zu, einschließlich der aktuellen Dateiposition und des Zugriffsmodus. Mehrere Deskriptoren können auf denselben Zustand einer geöffneten Datei verweisen, etwa nach einer Duplizierung oder durch Vererbung beim Erzeugen eines Prozesses.
Ein erfolgreiches open() gibt den niedrigsten verfügbaren Deskriptor im aufrufenden Prozess zurück. Ein Wert wie 3 ist in Beispielen üblich, wenn die Standarddeskriptoren bereits belegt sind. Programme müssen jedoch den von dem Aufruf zurückgegebenen Wert verwenden, statt eine bestimmte Zahl anzunehmen. Der Deskriptor bleibt verwendbar, bis er geschlossen oder anderweitig ersetzt wird. (read())
Standarddeskriptoren in C
In Unix-ähnlichen Umgebungen wird ein Prozess üblicherweise mit drei Standarddeskriptoren gestartet:
| Deskriptor | Üblicher Name | Typischer Zweck |
|---|---|---|
0 |
Standardeingabe (stdin) |
Eingaben lesen |
1 |
Standardausgabe (stdout) |
Reguläre Programmausgaben schreiben |
2 |
Standardfehlerausgabe (stderr) |
Diagnosemeldungen schreiben |
Dabei handelt es sich um Konventionen, nicht um Garantien für ein bestimmtes Gerät. Eine Shell kann die Standardeingabe oder -ausgabe auf eine Datei oder eine Pipe umleiten, und ein Programm kann unter diesen Nummern andere Ressourcen erhalten. In C stellt <stdio.h> die Stream-Objekte stdin, stdout und stderr bereit; in POSIX-Umgebungen sind ihnen üblicherweise die Deskriptoren 0, 1 und 2 zugrunde. Verwenden Sie Stream-Funktionen für FILE *-Streams und Deskriptoraufrufe, wenn Sie direkt mit einem ganzzahligen Deskriptor arbeiten. Mischen Sie gepufferte Stream-Operationen und direkte Deskriptoroperationen für dieselbe zugrunde liegende geöffnete Datei nicht ohne Abstimmung, da Pufferung und Dateiposition auseinanderlaufen können.
Eine Datei in C öffnen, lesen und schließen
Die POSIX-Schnittstellen sind in <fcntl.h> und <unistd.h> deklariert. open() nimmt einen Pfad und Flags entgegen und gibt anschließend einen Deskriptor oder bei einem Fehler -1 zurück; im Fehlerfall wird errno gesetzt. Wählen Sie den Zugriffsmodus mit O_RDONLY, O_WRONLY oder O_RDWR. Ergänzen Sie Flags wie O_CREAT nur bei Bedarf. Wenn O_CREAT gesetzt ist, muss ein drittes Argument für den Modus übergeben werden, zum Beispiel 0600; die Prozess-Umask kann Berechtigungsbits aus dem angeforderten Modus entfernen. O_CREAT legt eine fehlende Datei an, kürzt aber keine vorhandene Datei. Verwenden Sie O_TRUNC nur, wenn vorhandene Inhalte absichtlich ersetzt werden sollen.
read(fd, buffer, count) speichert bis zu count Bytes und gibt die tatsächlich gelesene Anzahl zurück. Bei einer regulären Datei bedeutet ein Rückgabewert von 0 bei einem positiven count, dass sich die aktuelle Dateiposition am Dateiende befindet. Bei anderen Deskriptortypen beweist ein Ergebnis von 0 nicht allgemein das Dateiende: Bestimmte Terminaleinstellungen können beispielsweise 0 zurückgeben, ohne dass das Dateiende erreicht ist. Werten Sie das Ergebnis daher entsprechend dem Verhalten des jeweiligen Deskriptortyps aus. Ist count gleich 0, kann read() ebenfalls 0 zurückgeben, ohne damit das Dateiende anzuzeigen. Ein positiver Rückgabewert kann kleiner als die angeforderte Anzahl sein. Verarbeiten Sie deshalb nur diese Anzahl an Bytes und lesen Sie weiter, wenn noch mehr Eingabe benötigt wird. Ein Rückgabewert von -1 weist auf einen Fehler hin. Bei unterbrechbaren Aufrufen sollten Sie es bei errno == EINTR erneut versuchen; andere Fehler behandeln Sie entsprechend den Anforderungen der Anwendung. Der Puffer enthält Bytes und nicht unbedingt eine C-Zeichenkette. Geben Sie ihn daher nicht mit %s aus, es sei denn, Sie haben innerhalb des reservierten Speicherbereichs einen Nullterminator hinzugefügt.
close(fd) gibt den Deskriptor frei und liefert bei Erfolg 0, bei einem Fehler -1. Das Schließen garantiert nicht, dass jeder Schreibvorgang dauerhaft gespeichert wurde; für Anwendungen, die dauerhafte Speicherung benötigen, gelten zusätzliche Synchronisationsanforderungen. Prüfen Sie das Ergebnis von close(), wenn es darauf ankommt. Wiederholen Sie nach einem fehlgeschlagenen close() nicht blindlings denselben Aufruf mit diesem Deskriptor: Sein Zustand kann plattformabhängig sein, und die Nummer wurde möglicherweise bereits wiederverwendet.
Führen Sie dieses eigenständige POSIX-Beispiel aus. Es erstellt eine temporäre Datei, schreibt eine kurze Nutzlast hinein, öffnet die Datei erneut zum Lesen, kopiert die Bytes in die Standardausgabe und entfernt anschließend die temporäre Datei. Jeder fehlgeschlagene Systemaufruf schreibt eine Diagnosemeldung in die Standardfehlerausgabe. main() gibt einen Fehlerstatus zurück, wenn ein Teil der Übertragung oder Bereinigung fehlschlägt. Bei einem erfolgreichen Lauf wird nur die Nutzlast in die Standardausgabe geschrieben.
Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static int write_all(int fd, const char *data, size_t length) {
size_t done = 0;
while (done < length) {
ssize_t n = write(fd, data + done, length - done);
if (n < 0 && errno == EINTR) continue;
if (n == 0) errno = EIO;
if (n <= 0) return -1;
done += (size_t)n;
}
return 0;
}
int main(void) {
char path[] = "/tmp/file-descriptor-example-XXXXXX";
const char payload[] = "File descriptor example\n";
char buffer[16];
int created = mkstemp(path);
if (created < 0) {
perror("mkstemp");
return EXIT_FAILURE;
}
int failed = 0;
if (write_all(created, payload, sizeof(payload) - 1) < 0) {
perror("write temporary file");
failed = 1;
}
if (close(created) < 0) {
perror("close temporary file");
failed = 1;
}
if (failed) {
if (unlink(path) < 0) perror("unlink temporary file");
return EXIT_FAILURE;
}
int fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open temporary file");
if (unlink(path) < 0) perror("unlink temporary file");
return EXIT_FAILURE;
}
failed = 0;
for (;;) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n < 0 && errno == EINTR) continue;
if (n < 0) {
perror("read temporary file");
failed = 1;
break;
}
if (n == 0) break;
if (write_all(STDOUT_FILENO, buffer, (size_t)n) < 0) {
perror("write standard output");
failed = 1;
break;
}
}
if (close(fd) < 0) {
perror("close input file");
failed = 1;
}
if (unlink(path) < 0) {
perror("unlink temporary file");
failed = 1;
}
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
File descriptor example
Die Leseschleife behandelt kurze Lesevorgänge, indem sie den jeweils zurückgegebenen Bytebereich schreibt und anschließend weiterliest, bis das Dateiende erreicht ist. Unterbrochene Lese- und Schreibvorgänge werden erneut versucht. Auf dem normalen Pfad sowie nach Lese- oder Ausgabefehlern wird der Deskriptor geschlossen. Für diese Demonstration wird die temporäre Datei mit mkstemp() erstellt; in einer Anwendung sollten Sie diesen Teil durch den für die jeweilige Aufgabe passenden Pfad und die entsprechende Erstellungsrichtlinie ersetzen. Dieses Beispiel verwendet POSIX-Schnittstellen und ist nicht ausschließlich ISO-C-konform.