Kapitel 9. Interrupt-Handler

Inhalt
Überblick über die Interrupt-Steuerung
Den Parallel-Port vorbereiten
Einen Interrupt-Handler installieren
Einen Handler implementieren
Tasklets und untere Hälften
Gemeinsames Nutzen von Interrupts
Interrupt-gesteuerte I/O
Race Conditions
Abwärtskompatibilität
Schnellreferenz

Obwohl manche Geräte ausschließlich über ihre I/O-Bereiche gesteuert werden können, sind die meisten Geräte doch etwas komplizierter. Sie müssen mit der Welt da draußen interagieren, wobei es sich oft um sich drehende Platten, bewegende Bänder, Drähte an entfernte Plätze und so weiter handelt. Vieles muß in Zeiträumen erledigt werden, die anders sind und länger dauern als die des Prozessors. Da es fast immer unschön ist, den Prozessor auf externe Ereignisse warten zu lassen, muß es eine Möglichkeit geben, mit der ein Gerät dem Prozessor mitteilen kann, daß etwas passiert ist.

Diese Möglichkeit ist natürlich durch Interrupts gegeben. Ein Interrupt ist einfach ein Signal, das die Hardware schicken kann, wenn sie die Aufmerksamkeit des Prozessors erheischen will. Meistens muß ein Treiber nur einen Handler für die Interrupts seines Geräts registrieren und diese korrekt verarbeiten, wenn sie eintreffen. Unter dieser einfachen Oberfläche liegt natürlich einiges an Komplexität. Insbesondere sind Interrupt-Handler aufgrund der Art und Weise, wie sie ausgeführt werden, etwas eingeschränkt in den ihnen möglichen Aktionen.

Es ist schwierig, die Verwendung von Interrupts zu demonstrieren, ohne ein echtes Hardware-Gerät zur Verfügung zu haben, um sie zu erzeugen. Daher verwendet der Beispiel-Code in diesem Kapitel den Parallel-Port. Wir arbeiten am short-Modul weiter, das wir im vorigen Kapitel eingeführt haben; mit einigen kleinen Erweiterungen kann es Interrupts vom Parallel-Port erzeugen und bearbeiten. Der Name des Moduls short steht eigentlich für short int (schließlich arbeiten wir in C, nicht wahr?), um uns daran zu erinnern, daß wir mit Interrupts arbeiten.

Überblick über die Interrupt-Steuerung

Aufgrund von Änderungen im Design und in der verwendeten Hardware hat sich die Interrupt-Verarbeitung von Linux über die Jahre ziemlich geändert. Das Interrupt-Bild der PCs der frühen Tage war ziemlich einfach; es gab einfach nur sechzehn Interrupt-Leitungen und einen Prozessor, der sich darum kümmerte. Moderne Hardware kann weitaus mehr Interrupts und auch einen fortgeschrittenen Advanced Programmable Interrupt Controller (APIC) haben, der Interrupts auf intelligente (und programmierbare) Weise auf mehrere Prozessoren verteilen kann.

Glücklicherweise hat Linux all diese Änderungen mit relativ wenigen Inkompatibilitäten auf der Gerätetreiber-Ebene bewältigen können. Daher funktioniert die in diesem Kapitel beschriebene Schnittstelle mit wenigen Änderungen auf vielen verschiedenen Kernel-Versionen. Manchmal geht es eben doch gut.

Unix-artige Systeme haben seit vielen Jahren die Funktionen cli und sti verwendet, um Interrupts aus- und einzuschalten. Auf modernen Linux-Systemen sollte man diese allerdings nicht direkt verwenden. Es wird immer schwieriger zu wissen, ob Interrupts gerade eingeschaltet sind; deswegen gilt es als schlechter Stil, einfach die Interrupts mit sti einzuschalten, bevor man aus einer Routine zurückspringt. Ihre Funktion könnte in eine Funktion zurückspringen, die erwartet, daß die Interrupts immer noch abgeschaltet sind.

Wenn Sie Interrupts abschalten müssen, ist es daher besser, die folgenden Aufrufe zu verwenden:


unsigned long flags;

    save_flags(flags);
    cli();

    /* Dieser Code wird mit abgeschalteten Interrupts ausgefuehrt */

    restore_flags(flags);

Beachten Sie, daß save_flags ein Makro ist und daß die Variable, die die Flags aufnimmt, direkt und ohne &-Operator übergeben wird. Es gibt auch eine wichtige Einschränkung hinsichtlich der Verwendung dieser Makros: save_flags und restore_flags müssen aus der gleichen Funktion aufgerufen werden. Sie können also flags nicht an eine andere Funktion übergeben, sofern diese nicht inline ist. Code, der diese Einschränkung nicht beachtet, wird auf manchen Architekturen funktionieren, auf anderen aber nicht.

In zunehmendem Maße wird aber sogar von der Verwendung von Code wie dem eben gezeigten abgeraten. In einem Multiprozessor-System kann kritischer Code nicht einfach durch das Abschalten der Interrupts geschützt werden; man braucht irgendeinen Sperrmechanismus. Funktionen wie spin_lock_irqsave (siehe “the Section called Spinlocks verwenden” weiter hinten in diesem Kapitel) kombinieren Sperren und Interrupt-Steuerung; diese Funktionen sind die einzige sichere Möglichkeit, in Gegenwart von Interrupts Nebenläufigkeit zu steuern.

cli schaltet dagegen die Interrupts auf allen Prozessoren des Systems ab und kann daher die gesamte Systemperformance negativ beeinflussen.[1]

Daher verschwinden explizite Aufrufe von cli und verwandten Funktionen langsam aus großen Teilen des Kernels. Es gibt Gelegenheiten, bei denen man diese in einem Gerätetreiber braucht, aber diese sind selten. Bevor Sie cli aufrufen, überlegen Sie, ob Sie wirklich alle Interrupts auf dem System abschalten müssen.

Fußnoten

[1]

Die Wahrheit ist noch etwas komplizierter. Wenn Sie schon einen Interrupt bearbeiten, schaltet cli nur die Interrupts der aktuellen CPU ab.