Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Einleitung
TEIL I: Einstieg in Linux
2 Die Installation
3 Erste Schritte
4 Linux als Workstation für Einsteiger
TEIL II: Grundlagen
5 Kernel
6 Grundlagen aus Anwendersicht
TEIL III: Die Shell
7 Die Shell
8 Reguläre Ausdrücke
9 Konsolentools
10 Die Editoren
11 Shellskriptprogrammierung mit der bash
12 Die C-Shell
TEIL IV: System- & Netzwerkadministration
13 Benutzerverwaltung
14 Grundlegende Verwaltungsaufgaben
15 Netzwerkgrundlagen
16 Anwendersoftware für das Netzwerk
17 Netzwerkdienste
18 Mailserver unter Linux
19 LAMP & Co.
20 DNS-Server
21 Secure Shell
TEIL V: Die grafische Oberfläche
22 Die grafische Oberfläche
23 Window-Manager und Desktops
24 X11-Programme
25 Multimedia und Spiele
TEIL VI: Systeminterna
26 Prozesse und IPC
27 Bootstrap und Shutdown
28 Dateisysteme
29 Virtualisierung und Emulatoren
TEIL VII: Programmierung und Sicherheit
30 Softwareentwicklung
31 Crashkurs in C und Perl
32 Einführung in Computersicherheit
33 Netzwerksicherheit überwachen
TEIL VIII: Anhang
A Lösungen zu den einzelnen Aufgaben
B Kommandoreferenz
C X11-InputDevices
D MBR
E Buch-DVDs
F Glossar
G Literatur
Stichwort
Ihre Meinung?

Spacer
Linux von Johannes Plötner, Steffen Wendzel
Das umfassende Handbuch
Buch: Linux

Linux
Rheinwerk Computing
1282 S., 5., aktualisierte Auflage 2012, geb., mit 2 DVDs
49,90 Euro, ISBN 978-3-8362-1822-1
Pfeil 5 Der Kernel
Pfeil 5.1 Grundlagen
Pfeil 5.1.1 Prozessor
Pfeil 5.1.2 Speicher
Pfeil 5.1.3 Fairness und Schutz
Pfeil 5.1.4 Programmierung
Pfeil 5.1.5 Benutzung
Pfeil 5.2 Aufgaben eines Betriebssystems
Pfeil 5.2.1 Abstraktion
Pfeil 5.2.2 Virtualisierung
Pfeil 5.2.3 Ressourcenverwaltung
Pfeil 5.3 Prozesse, Tasks und Threads
Pfeil 5.3.1 Definitionen
Pfeil 5.3.2 Lebenszyklen eines Prozesses
Pfeil 5.3.3 Implementierung
Pfeil 5.4 Speichermanagement
Pfeil 5.4.1 Paging
Pfeil 5.4.2 Hardware
Pfeil 5.4.3 Organisation des Adressraums
Pfeil 5.5 Eingabe und Ausgabe
Pfeil 5.5.1 Hardware und Treiber
Pfeil 5.5.2 Interaktion mit Geräten
Pfeil 5.5.3 Ein-/Ausgabe für Benutzerprogramme
Pfeil 5.5.4 Dateisysteme
Pfeil 5.6 Zusammenfassung
Pfeil 5.7 Aufgaben

Rheinwerk Computing - Zum Seitenanfang

5.5 Eingabe und AusgabeZur nächsten Überschrift

Kommen wir nun zur Ein- und Ausgabe, einer der wichtigsten Funktionen eines Betriebssystems. Wir wollen diese dabei einmal prinzipiell und einmal konkret am Beispiel des Dateisystems behandeln.

Abbildung

Abbildung 5.8 Der Anteil des Treibercodes am Kernel 2.6.10

Die Grafik in Abbildung verdeutlicht bereits, wie wichtig das Ein-/Ausgabe-Sub- system des Kernels ist. Mittlerweile besteht fast die Hälfte des Kernel-Codes aus Quelldateien für verschiedenste Treiber. Im Folgenden wollen wir zunächst erklären, was Sie sich unter Treibern vorstellen können.


Rheinwerk Computing - Zum Seitenanfang

5.5.1 Hardware und TreiberZur nächsten ÜberschriftZur vorigen Überschrift

Damit ein Gerät angesprochen werden kann, muss man »seine Sprache sprechen«. Man muss genau definierte Daten in spezielle Hardwareregister oder auch Speicherstellen laden, um bestimmte Effekte zu erzielen. Daten werden hin und her übertragen, und am Ende druckt ein Drucker eine Textdatei oder man liest Daten von einer CD.

Schnittstelle

Zur Übersetzung einer Schnittstelle zwischen den Benutzerprogrammen, die beispielsweise einen CD-Brenner unterstützen, und den eventuell von Hersteller zu Hersteller unterschiedlichen Hardwareschnittstellen, benötigt man Treiber. Für die Anwenderprogramme werden die Geräte unter Unix als Dateien visualisiert. Als solche können sie natürlich von Programmen geöffnet und benutzt werden: Man sendet und empfängt Daten über Syscalls. Wie die zu sendenden Steuerdaten genau auszusehen haben, ist von Gerät zu Gerät unterschiedlich.

Auch das vom Treiber bereitgestellte Interface kann sich von Gerät zu Gerät unterscheiden. Bei USB-Sticks oder CD-ROM-Laufwerken wird es sicherlich so beschaffen sein, dass man die Geräte leicht in das Dateisystem integrieren und auf die entsprechenden Dateien und Verzeichnisse zugreifen kann. Bei einem Drucker jedoch möchte man dem Gerät die zu druckenden Daten schicken; das Interface wird also eine völlig andere Struktur haben. Auch kann ein Gerät durch verschiedene Treiber durchaus mehrere Schnittstellen anbieten: Eine Festplatte kann man sowohl über eine Art Dateisystem-Interface als auch direkt über das Interface des IDE-Treibers ansprechen.

Module

Die meisten Treiber sind unter Linux als Module realisiert. Solche Module werden zur Laufzeit in den Kernel eingebunden und stellen dort dann eine bestimmte Funktionalität zur Verfügung. Dazu sind aber einige Voraussetzungen zu erfüllen:

  • Interface
    Der Kernel muss ein Interface anbieten, über das Module erst einmal geladen werden können. Einmal geladen, müssen sie auch irgendwie in den Kernel integriert werden können.
  • Sicherheit
    Lädt man externe Komponenten in den Kernel, so bedeutet dies immer ein Sicherheitsrisiko, und zwar in doppelter Hinsicht: Einerseits könnten schlecht programmierte Treiber das ganze System zum Absturz bringen, andererseits Hacker durch spezielle Module versuchen, den Kernel zu manipulieren.
  • Gerätemanagement
    Ein Modul beziehungsweise ein Treiber muss beim Laden mitteilen können: Ich bin jetzt für dieses oder jenes Gerät verantwortlich. Vielleicht muss mancher Treiber auch erst erkennen, ob und wie viele von ihm unterstützte Geräte angeschlossen sind.

Was aber wäre die Alternative zu Treibern in Modulform? Treiber müssen teilweise privilegierte Befehle zur Kommunikation mit den zu steuernden Geräten nutzen, daher müssen sie zumindest zum großen Teil im Kernel-Mode ablaufen. Und wenn man sie nicht zur Laufzeit in den Kernel laden kann, müssten sie schon von Anfang an in den Kernel-Code integriert sein.

Modulare versus statische Integration

Würde man jedoch alle verfügbaren Treiber »ab Werk« direkt in den Kernel kompilieren, wäre der Kernel sehr groß und damit langsam sowie speicherfressend. Daher sind die meisten Distributionen dazu übergegangen, ihre Kernel mit in Modulform kompilierten Treibern auszuliefern. Der Benutzer kann dann alle Module laden, die er braucht – oder das System erledigt diese Aufgabe automatisch für ihn. [Fn. Wie man selbst Module lädt und das System so konfiguriert, dass es dies automatisch tut, erfahren Sie in Kapitel 14, »Grundlegende Verwaltungsaufgaben«.]

Zeichenorientierte Treiber

Treiber müssen ins System eingebunden werden, mit anderen Worten: Man benötigt eine einigermaßen uniforme Schnittstelle. Aber kann man zum Beispiel eine USB-Webcam und eine Festplatte in ein einheitliches und trotzdem konsistentes Muster bringen? Nun ja, Unix hat es zumindest versucht. Es unterscheidet zwischen zeichenorientierten und blockorientierten Geräten und klassifiziert damit auch die Treiber entsprechend. Der Unterschied ist dabei relativ simpel und doch signifikant:

Ein zeichenorientiertes Gerät sendet und empfängt Daten direkt von Benutzerprogrammen.

Keine Pufferung

Der Name der zeichenorientierten Geräte leitet sich von der Eigenschaft bestimmter serieller Schnittstellen ab, nur jeweils ein Zeichen während einer Zeiteinheit übertragen zu können. Diese Zeichen konnten nun aber direkt – also ohne Pufferung – gesendet und empfangen werden. Eine weitere wichtige Eigenschaft ist die, dass auf Daten im Allgemeinen nicht wahlfrei zugegriffen werden kann. Man muss eben mit den Zeichen vorlieb nehmen, die gerade an der Schnittstelle anliegen.

Blockorientierte Treiber

Bei blockorientierten Geräten werden im Unterschied dazu meist ganze Datenblöcke auf einmal übertragen. Der klassische Vertreter dieser Gattung ist die Festplatte, bei der auch nur eine blockweise Übertragung der Daten sinnvoll ist. Der Lesevorgang bestimmter Daten gliedert sich nämlich in diese Schritte:

  1. Aus der Blocknummer – einer Art Adresse – wird die physische Position der Daten ermittelt.
  2. Der Lesekopf der Platte bewegt sich zur entsprechenden Stelle.
  3. Im Mittel muss nun eine halbe Umdrehung gewartet werden, bis die Daten am Kopf anliegen.
  4. Der Lesekopf liest die Daten.

Mehrere Daten auf einmal

Die meiste Zeit braucht nun aber die Positionierung des Lesekopfs, denn wenn die Daten einmal am Kopf anliegen, geht das Einlesen sehr schnell. Mit anderen Worten: Es ist für eine Festplatte praktisch, mit einem Zugriff gleich mehrere Daten – zum Beispiel 512 Bytes – zu lesen, da die zeitaufwendige Positionierung dann eben nur einmal statt 512-mal erfolgen muss.

Blockorientierte Geräte haben die gemeinsame Eigenschaft, dass die übertragenen Daten gepuffert werden. Außerdem kann auf die gespeicherten Blöcke wahlfrei, also in beliebiger Reihenfolge, zugegriffen werden. Darüber hinaus können Datenblöcke mehrfach gelesen werden.

Bei einer Festplatte hat diese Tatsache nun gewisse Vorteile wie auch Nachteile: Während des Arbeitens bringen zum Beispiel Schreib- und Lesepuffer eine hohe Performance. Wenn ein Benutzer die ersten Bytes einer Datei lesen möchte, kann man schließlich auch gleich ein Readahead machen und die darauf folgenden Daten schon einmal vorsichtshalber im Hauptspeicher puffern. Dort können sie dann ohne Zeitverzug abgerufen werden, wenn ein Programm – was ziemlich wahrscheinlich ist – in der Datei weiterlesen will. Will es das nicht, gibt man den Puffer nach einiger Zeit wieder frei.

Schreibpuffer

Beim Schreibpuffer sieht das Ganze ähnlich aus: Um performanter zu arbeiten, werden Schreibzugriffe in der Regel nicht sofort, sondern erst in Zeiten geringer Systemauslastung ausgeführt. Wenn ein System nun aber nicht ordnungsgemäß heruntergefahren wird, kann es zu Datenverlusten bei eigentlich schon getätigten Schreibzugriffen kommen. Wenn die Daten nämlich in den Puffer, aber eben noch nicht auf die Platte geschrieben wurden, sind sie weg.

Ein interessantes Beispiel für die Semantik dieser Treiber ist eine USB-Festplatte. Es handelt sich bei diesem Gerät schließlich um eine blockorientierte Festplatte, die über einen seriellen, zeichenorientierten Anschluss mit dem System verbunden ist. Sinnvollerweise wird die Funktionalität der Festplatte über einen blockorientierten Treiber angesprochen, der aber intern wiederum über den USB-Anschluss und damit über einen zeichenorientierten Treiber die einzelnen Daten an die Platte schickt beziehungsweise von ihr liest.

Der wahlfreie Zugriff auf die Datenblöcke der Festplatte wird also über die am seriellen USB-Anschluss übertragenen Daten erledigt. Der Blocktreiber nutzt eine bestimmte Sprache zur Ansteuerung des Geräts und der zeichenorientierte USB-Treiber überträgt dann die »Worte« dieser Sprache und gegebenenfalls zu lesende oder zu schreibende Daten.


Rheinwerk Computing - Zum Seitenanfang

5.5.2 Interaktion mit GerätenZur nächsten ÜberschriftZur vorigen Überschrift

Da wir im letzten Abschnitt die unterschiedlichen Treiber allgemein beschrieben haben, wollen wir im Folgenden den Zugriff auf sie aus dem Userspace heraus betrachten und dabei ihren internen Aufbau analysieren.

Gehen wir also wieder ein paar Schritte zurück und führen wir uns vor Augen, dass Geräte unter Linux allesamt als Dateien unterhalb des /dev-Verzeichnisses repräsentiert sind. Die Frage ist nun, wie man diese Geräte und Ressourcen nutzen kann und wie der Treiber diese Nutzung unterstützt.

Den passenden Treiber finden

Major- und Minor-Nummern

Früher war die Sache relativ einfach: Jeder speziellen Gerätedatei unterhalb des /dev-Verzeichnisses war eine sogenannte Major- und eine Minor-Nummer zugeordnet. Anhand der Major-Nummer konnte festgestellt werden, welcher Treiber für diese spezielle Gerätedatei zuständig war. Die Minor-Nummer stellte für den Treiber selbst eine Hilfe dar, um festzustellen, welches Gerät nun anzusprechen war – schließlich war es gut möglich, dass in einem System zwei baugleiche Komponenten wie beispielsweise Festplatten verwendet wurden, die zwar vom selben Treiber bedient, aber trotzdem unterschieden werden mussten.

Später dachte man sich, dass man die Geräte doch nicht über statische Nummern identifizieren, sondern stattdessen eine namensbasierte Identifizierung verwenden sollte – das Dateisystem devfs war geboren. Der Treiber musste nun beim Laden nicht mehr angeben, welche Major-Nummer er bediente, sondern er registrierte sozusagen den »Namen« des Geräts. Das geschah im Modulcode recht einfach:

Listing 5.23 So wurde beim devfs ein Gerät registriert

#include <linux/devfs_fs_kernel.h>
...
static int __init treiber_init(void)
{
...
/* Ist dieses Gerät schon registriert? */
if(register_chrdev(4, "Treibername", &fops) == 0)
{
/* Können wir uns registrieren? */
if(devfs_mk_cdev( MKDEV(4,64),
S_IFCHR | S_IRUGO | S_IWUGO, "vc/ttyS%d", 0 ))
// Wenn nein, dann Fehlermeldung ausgeben
printk( KERN_ERR "Integration fehlgeschlagen.\n");
}
...
}

Ein Irrweg

In diesem Beispiel wurde das zeichenorientierte Gerät ttyS0 über die Funktion devfs_mk_cdev [Fn. Für blockorientierte Geräte gibt es einen entsprechenden anderen Befehl, der auch mit richtigen Parametern – S_IFCHR steht im Beispiel für zeichenorientierte Geräte – aufgerufen werden muss.] im Verzeichnis vc angelegt. Das devfs hat jedoch nicht zu unterschätzende Nachteile, daher wird es vom 2.6er Linux zwar noch unterstützt, ist aber als deprecated, also nicht mehr unterstützt, gekennzeichnet. Die Nachteile sind unter anderem:

  • Die Implementierung des devfs ist sehr umfangreich und damit schlecht skalierbar, außerdem wird der Code als nicht besonders gut angesehen.
  • Die Gerätedateien haben im devfs neue Namen, die nicht mehr standardkonform sind.
  • Nicht alle Treiber funktionieren mit dem devfs.
  • Die Methode, die Zugriffsrechte für eine von einem Treiber erstellte Datei zu setzen, ist sehr umständlich.

Man musste also wieder eine neue Lösung finden und kehrte schließlich zu einer Identifizierung über Nummern zurück. Jedoch warf man eine der Altlasten von Unix – die Beschränkung auf jeweils 8 Bit für die Major- und Minor-Nummer – über Bord und führte mit Kernel 2.6 die 32 Bit lange Gerätenummer ein. Natürlich kann man, wie im Beispiel gesehen, von den bekannten Major- und Minor-Nummern mittels des MKDEV(major,minor)-Makros diese Nummern auf den 32-Bit-Wert der Gerätenummer abbilden.

Also musste wieder etwas Neues her. Im Zuge der Weiterentwicklung des Powermanagements kam den Entwicklern eine andere Art der Geräteverwaltung in den Sinn: die Verwaltung in Form eines Baumes, der die Zusammenhänge des Prozessors mit den Controller-Bausteinen verschiedener Bussysteme und schließlich mit der über diese Bussysteme angebundenen Peripherie abbildet. Das heißt nichts anderes, als dass ein Treiber wissen muss, ob sein Gerät zum Beispiel am PCI- oder USB-Bus hängt. Für das Powermanagement ist das insofern wichtig, als zum Beispiel der PCI-Bus erst nach der Deaktivierung des letzten PCI-Geräts heruntergefahren werden sollte.

Das sysfs und udev

Visualisieren kann man sich diese Struktur über das sysfs, ein virtuelles, also nicht irgendwo auf einem Medium abgelegtes, sondern vielmehr dynamisch generiertes Dateisystem. Dieses spezielle Dateisystem muss erst gemountet werden, bevor man die zahlreichen Daten auslesen kann:

Listing 5.24 Das Dateisystem sysfs mounten und anzeigen

# mount -t sysfs sysfs /sys
# ls /sys/*
/sys/block:
fd0 hdb hdd ram1 ram11 ram13 ram15 ram3 ...
hda hdc ram0 ram10 ram12 ram14 ram2 ram4 ...

/sys/bus:
ide pci platform pnp usb

/sys/class:
graphics input mem misc net nvidia pci_bus ...

/sys/devices:
pci0000:00 platform pnp0 pnp1 system

/sys/firmware:
acpi

/sys/module:
8139too commoncap ide_disk nvidia ...
af_packet dm_mod ide_floppy ppp_generic ...
agpgart ext3 ide_generic pppoe ...
... ... ... ... ...

/sys/power:
state

An diesem Beispiel kann man schon erkennen, dass das sysfs alle wichtigen Informationen über geladene Module, Geräteklassen und Bussysteme enthält. Ein Gerät kann im sysfs also durchaus mehrfach auftauchen, eine Netzwerkkarte würde zum Beispiel unterhalb des /sys/pci-Verzeichnisses und unterhalb der Geräteklasse /sys/net erscheinen.

udev

Mittlerweile werden die Einträge im /dev-Verzeichnis mit dem Programm udev dynamisch erzeugt, das auf Hotplug-Ereignisse reagiert. udev arbeitet mit sysfs zusammen und ist als Nachfolger des devfs zu betrachten. Im Gegensatz zum devfs-Dateisystem ist udev nicht im Kernel implementiert, sondern läuft im Userspace. Zudem kann udev vom Administrator über Regeln konfiguriert werden.

Intelligenterweise muss man sich als Treiberprogrammierer in den seltensten Fällen mit dem Eintrag seines Geräts ins sysfs beschäftigen, Ausnahmefälle wären aber zum Beispiel eine neue Geräteklasse oder besondere Powermanagement-Funktionen.

[zB]Einen besonders einfachen Fall wollen wir hier noch einmal kurz zeigen: Ein zeichenorientiertes Gerät mit der Major-Nummer 62 soll ins System integriert werden.

Listing 5.25 Ein Gerät registrieren

#include <linux/fs.h>
static struct file_operations fops;

int init_driver(void) {
if(register_chrdev(62, "NeuerTreiber", &fops) == 0)
// Treiber erfolgreich angemeldet
return 0;

// Ansonsten: Anmeldung fehlgeschlagen
return –1;
}

Hier geben wir wieder nur eine Major-Nummer an, denn aus dieser kann der Kernel eine gültige Gerätenummer generieren. Ist die Nummer schon vergeben, wird das Registrieren des Geräts unweigerlich fehlschlagen. Jedoch kann man sich auch über die spezielle Major-Nummer »0« einfach eine beliebige freie Nummer zuweisen lassen. Mit der Zeichenkette »NeuerTreiber« identifiziert sich der Treiber im System, taucht unter dieser Bezeichnung im sysfs auf und kann sich mit dieser Kennung natürlich auch wieder abmelden.

Auf das Gerät zugreifen

I/O-Syscalls

Geräte sind also Dateien, auf die man im Wesentlichen mit den üblichen Syscalls [Fn. Bei der Kommunikation mit Gerätedateien werden die C-Funktionen fopen(), fprintf() usw. in der Regel nicht verwendet. Zwar greifen diese Funktionen intern auch auf die Syscalls zurück, allerdings wird standardmäßig die gepufferte Ein-/Ausgabe benutzt, was im Regelfall für die Kommunikation mit Geräten nicht ideal ist.] zur Dateibearbeitung zugreifen wird:

  • open() – öffnet eine Datei (dies ist notwendig, um in sie zu schreiben und aus ihr zu lesen)
  • write() – schreibt in eine geöffnete Datei
  • read() – liest aus einer geöffneten Datei
  • close() – schließt eine geöffnete Datei
  • lseek() – ändert den Schreib-/Lesezeiger einer geöffneten Datei, also die Stelle in einer Datei, an der ein Programm arbeitet
  • ioctl() – bietet ausführliche Funktionen zur Gerätesteuerung

Callbacks

Diese Schnittstellen müssen nun natürlich vom Treiber als Callbacks bereitgestellt werden. Callbacks sind Funktionen, die genau dann ausgeführt werden, wenn ein entsprechender Event – in diesem Fall der Aufruf des entsprechenden Syscalls auf eine Gerätedatei – auftritt.

Wenn eine Applikation also mittels open() eine Gerätedatei öffnet, stellt der Kernel den zugehörigen Treiber anhand der bereits besprochenen Major/Minor- beziehungsweise der Gerätenummer fest. Danach erstellt er im Prozesskontext eine Datenstruktur vom Typ struct file, in der sämtliche Optionen des Dateizugriffs wie die Einstellung für blockierende oder nichtblockierende Ein-/Ausgabe oder natürlich auch die Informationen zur geöffneten Datei gespeichert werden.

Callbacks

Als nächstes wird der in der file_operations-Struktur vermerkte Callback für den open()-Syscall ausgerufen, dem unter anderem eine Referenz dieser file-Struktur übergeben wird. Anhand dieser Referenz wird auch bei allen anderen Callbacks die Treiberinstanz referenziert.

Eine Treiberinstanz ist notwendig, da ein Treiber die Möglichkeit haben muss, sitzungsspezifische Daten zu speichern.

Solche Daten könnten zum Beispiel einen Zeiger umfassen, der die aktuelle Position in einem Datenstrom anzeigt. Dieser Zeiger muss natürlich pro geöffneter Datei eindeutig sein, selbst wenn ein Prozess ein Gerät mehrfach geöffnet hat.


Rheinwerk Computing - Zum Seitenanfang

5.5.3 Ein-/Ausgabe für BenutzerprogrammeZur nächsten ÜberschriftZur vorigen Überschrift

Für Benutzerprogramme spiegelt sich dieser Kontext im Deskriptor wider, der nach einem erfolgreichen open() als Rückgabewert an das aufrufende Programm übergeben wird:

Listing 5.26 Einen Deskriptor benutzen

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
// Ein Deskriptor ist nur eine Identifikationsnummer
int fd;
char text[256];

// Die Datei "test.c" lesend öffnen und den zurück-
// gegebenen Deskriptor der Variable fd zuweisen
fd = open( "test.c", O_RDONLY );

// Aus der Datei unter Angabe des Deskriptors lesen
read( fd, text, 256 );

// "text" verarbeiten
// Danach die Datei schließen
close( fd );

return 0;
}

ioctl()

Ein wichtiger Syscall im Zusammenhang mit der Ein-/Ausgabe auf Gerätedateien ist ioctl() (I/O-Control). Über diesen Syscall werden alle Funktionalität abgebildet, die sich nicht in das standardisierte Interface einbauen lassen.


Rheinwerk Computing - Zum Seitenanfang

5.5.4 DateisystemeZur nächsten ÜberschriftZur vorigen Überschrift

Ein besonderer Fall der Ein-/Ausgabe ist das Dateisystem, das wir im Folgenden näher behandeln wollen. Eigentlich müssen wir zwischen »Dateisystem« und »Dateisystem« unterscheiden, da Unix mehrere Schichten für die Interaktion mit Dateien benutzt.

Der VFS-Layer

Die oberste Schicht des Dateisystems ist der sogenannte VFS-Layer (engl. virtual filesystem). Das virtuelle Dateisystem ist eine Schnittstelle, die die grundlegenden Funktionen beim Umgang mit Dateien von den physischen Dateisystemen abstrahiert:

Treiberarbeit

  • open() und close()
    Wie Sie schon beim Umgang mit Treibern und Geräten gesehen haben, ist die Möglichkeit zum Öffnen und Schließen von Dateien essenziell. Mit dieser Architektur setzt das VFS jedoch eine zustandsbasierte Funktionsweise des Dateisystems voraus. Beim Netzwerkdateisystem NFS z. B. ist dies aber nicht gegeben: Dort gibt es keine open()- oder close()-Aufrufe, stattdessen müssen bei jedem lesenden oder schreibenden Zugriff der Dateiname sowie die Position innerhalb der Datei angegeben werden. Damit ein NFS-Dateisystem von einem entfernten Server nun in das VFS integriert werden kann, muss der lokale Treiber sich den jeweiligen Zustand einer geöffneten Datei merken und bei jedem Zugriff in die Anfragen für den NFS-Server übersetzen.
  • read() und write()
    Hat man eine Datei einmal geöffnet, kann man über einen Deskriptor Daten an der aktuellen Position in der Datei lesen oder schreiben. Nachdem das VFS bereits beim open() festgestellt hat, zu welchem physischen Dateisystem ein Zugriff gehört, wird jeder read()- oder write()-Aufruf wieder direkt zum Treiber für das entsprechende Dateisystem weitergeleitet.
  • create() und unlink()
    Das VFS abstrahiert natürlich auch Erstellen und Löschen von Dateien. Die Erstellung wird dabei allerdings über den open()-Syscall abgewickelt.
  • readdir()
    Genauso muss auch ein Verzeichnis gelesen werden können. Schließlich ist die Art und Weise, wie ein Dateisystem auf einem Medium abgelegt ist, ebenfalls treiberspezifisch.

Der Benutzer beziehungsweise seine Programme greifen nun über solche uniformen Schnittstellen des VFS auf die Funktionen und Daten des physischen Dateisystems zu. Der Treiber des Dateisystems muss also entsprechende Schnittstellen anbieten, damit er in das VFS integriert werden kann.

Mehr Interna zu Dateisystemen finden Sie in Kapitel 28.

Mounting

Das Einbinden eines Dateisystems in das VFS nennt man Mounting. Eingebunden werden die Dateisysteme unterhalb von bestimmten Verzeichnissen, den sogenannten Mountpoints. Definiert wird das Ganze in einer Datei im Userspace, /etc/fstab:

Listing 5.27 Eine /etc/fstab-Datei

# Proc-Verzeichnis
proc /proc proc defaults 0 0

# Festplatten-Partitionen
UUID=c5d055a1-8f36-41c3-9261-0399a905a7d5
/ ext3 relatime,errors=remount-ro 0 1
UUID=c2ce32e7-38e4-4616-962e-8b824293537c
/home ext3 relatime 0 2

# Swap
/dev/sda7 none swap sw 0 0

# Wechseldatenträger
/dev/scd0 /mnt/dvd udf,iso9660 user,noauto,exec,utf8 0 0

Interessant sind für uns im Moment dabei vor allem die ersten beiden Spalten dieser Tabelle: Dort werden das Gerät sowie der Mountpoint angegeben, wo das darauf befindliche Dateisystem eingehängt werden wird.

Option beim Booten

Besonders interessant ist an dieser Stelle das Root-Dateisystem /. Die /etc/fstab befindet sich, wie gesagt, irgendwo auf dem Dateisystem, auf das man nur zugreifen kann, wenn man zumindest das Root-Dateisystem schon gemountet hat. Man hat also das klassische Henne-Ei-Problem, das nur gelöst werden kann, wenn der Kernel den Ort des Root-Dateisystems als Option beim Booten übergeben bekommt.

So kennen die Bootmanager (bspw. grub und der veraltete lilo) eine Option root, mit der man dem zu bootenden Kernel mitteilt, was sein Root-Dateisystem sein soll. Von diesem kann er dann die fstab lesen und alle weiteren Dateisysteme einbinden.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Linux Handbuch






 Linux Handbuch


Zum Katalog: Linux Server






 Linux Server


Zum Katalog: Raspberry Pi






 Raspberry Pi


Zum Katalog: Ubuntu 14.04 LTS






 Ubuntu 14.04 LTS


Zum Katalog: Roboter bauen mit Arduino






 Roboter bauen
 mit Arduino


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de