Wie findet der Prozessor den Kernel-Code nach einem Interrupt?

13

Wenn ein Interrupt auftritt, gibt der Prozessor den aktuellen Prozess vor und ruft den Kernel-Code auf, um den Interrupt zu behandeln. Woher weiß der Prozessor, wo er den Kernel eingeben muss?

Ich verstehe, dass es Interrupt-Handler gibt, die für jede Interrupt-Leitung installiert werden können. Da der Prozessor jedoch nur "festverdrahtete Logik" ausführt, muss eine vordefinierte Stelle vorhanden sein, die entweder auf einen Interrupt-Handler selbst verweist, oder ein Code, der vor dem Handler ausgeführt wird (da es für eine Interrupt-Zeile mehrere Handler geben kann, gehe ich davon aus, dass letztere).

Philipp Murry
quelle

Antworten:

13

Beim Start initialisiert der Kernel eine Interrupt-Vektortabelle ( auf x86 als Interrupt-Deskriptor-Tabelle oder IDT bezeichnet), die auf einen Interrupt-Handler für jede Zeile verweist.

Vor dem 80286 war das IDT immer an einer festen Adresse gespeichert; ab dem 80286 wird der IDT mit der LIDTAnweisung geladen .

Interrupt-Vektortabellen verweisen auf einen einzelnen Handler pro Interrupt-Zeile. Davon abgesehen könnte ein Kernel beispielsweise einen Interrupt-Handler bereitstellen, der mehrere andere Interrupt-Routinen ausführt, oder einen einzelnen Handler, der einige oder alle Interrupts abdeckt. Linux bietet hierfür einen generischen Interrupt-Handler, der festlegt, welche Interrupt-Leitung aufgerufen wurde, und den geeigneten Downstream-Handler findet, der aufgerufen werden kann.

Adam Maras
quelle
1
Der Prozessor verwendet also die Interrupt-Leitung als Index für den IDT, schreibt den Eintrag in den PC und startet die Ausführung. Gibt es nicht eine generische Funktion, die vor allen Interrupt-Handlern ausgeführt wird? für Linux wäre es do_IRQ (). Ist dies die Funktion, auf die jeder IDT-Eintrag zeigt, unabhängig von der Interrupt-Leitung?
Philipp Murry
@PhilippMurry ja. Der Kernel verwendet dann einen eigenen Satz von Interrupt-Handlern (von denen es mehr als einen pro Zeile geben kann), um den Interrupt tatsächlich zu behandeln, bevor er zum zuvor ausgeführten Code zurückkehrt.
Adam Maras
Okay, es gibt also zwei Arten von Interrupt-Handlern: die, die der Prozessor aufruft (immer do_IRQ ()), und die, die der Kernel aufruft (diejenige, die ich über request_irq () registriert habe). könnten Sie dies vielleicht zu Ihrer Antwort hinzufügen? Ich denke, dann werde ich es akzeptieren :) Vielen Dank
Philipp Murry
1
@PhilippMurry Ich bin kein Experte dafür, wie Linux mit Interrupts umgeht (und wie Kernel-Entwickler auf dieses System zugreifen), aber ich habe im weiteren Sinne einige weitere Informationen hinzugefügt, wie Kernel ihr eigenes ISR-Management haben können.
Adam Maras
12

Ja, es gibt einen vordefinierten Ort, der die Adresse des Codes enthält, zu dem gesprungen werden soll: einen Interrupt-Vektor . Abhängig vom Prozessor kann dies ein bestimmter Ort im physischen Speicher (8088), ein bestimmter Ort im virtuellen Speicher, ein Prozessorregister, ein Ort im Speicher sein, der durch ein Register angegeben wird (ARM, 386), ...

Die Details variieren auf verschiedenen Prozessoren, aber die wichtigsten gemeinsamen Elemente zur Behandlung eines Interrupts im Prozessor sind:

  • Maskiere Interrupts (damit jeder nachfolgende Interrupt warten muss).
  • Stellen Sie den Prozessormodus auf Kernel- oder Interrupt-Modus ein (falls der Prozessor über solche Modi verfügt).
  • Speichern Sie den Wert des Programmzählers an einem bekannten Ort (Register oder Speicher).
  • Speichern Sie möglicherweise den Wert anderer Register oder wechseln Sie zwischen den Registerbänken.
  • Führen Sie die nächste Anweisung aus (mit dem neuen PC-Wert).
Gilles 'SO - hör auf böse zu sein'
quelle
1

Die beiden anderen Antworten (zum Zeitpunkt des Schreibens) beziehen sich auf Interrupts und das IDT. Dies ist jedoch richtig. Auf einer modernen Intel-ähnlichen CPU gibt es nicht weniger als drei Möglichkeiten, einen Kernel aufzurufen.

Methode 1: Interrupts.

Dies ist oben erklärt. Sie richten einen Eintrag in der Interrupt-Deskriptor-Tabelle / dem Interrupt-Vektor ein und führen dann einen Software-Interrupt aus, um in den Kernel einzutreten.

Der Hauptvorteil dieser Methode besteht darin, dass ein typischer Kernel ohnehin Interrupts verarbeiten muss und auf archaischer Hardware arbeitet.

Methode 2: Rufen Sie Gates auf.

Ein Call-Gate ist eine spezielle Art von Segmentauswahl. Das Ziel des Aufrufs muss in die globale oder lokale Segmentdeskriptortabelle (GDT bzw. LDT) geladen werden. Wenn Sie dann eine Ferngesprächsanweisung unter Verwendung des Anruffensters als Segment ausführen (der Versatz des Anrufs wird ignoriert), können Sie privilegierteren Code aufrufen. Call Gates sind extrem flexibel. Die IA-32-Architektur verfügt über vier Berechtigungsstufen, und mit Call Gates können Sie eine beliebige Stufe aufrufen.

Ich glaube nicht, dass Linux jemals Call Gates verwendet hat, aber Windows 95 hat es getan. Die Win95-Kerneldienste ( krnl386.exeund kernel.dll) wurden tatsächlich im Benutzermodus (Ring 3) ausgeführt. Die höchste Berechtigungsstufe (Ring 0) wurde nur für Treiber und einen Mikrokernel verwendet, der nur die Prozessumschaltung ausführte. Das Anrufen von Fahrern erfolgte über Callgates. Dies ermöglichte es älteren 16-Bit-Codes (von denen es eine Menge gab!), Win95-Treiber unter Verwendung eines normalen Fernaufrufs zu verwenden, so wie sie es immer taten.

Ein unzureichender Schutz der globalen Deskriptortabelle war die Ursache für mehrere Windows 95-Exploits, die es schafften, ihre eigenen Call-Gates durch Schreiben über den Speicher zu installieren.

Methode 3: SYSCALL / SYSRET und SYSENTER / SYSEXIT

Dies sind zwei Befehlssätze, die von AMD und Intel unabhängig voneinander erfunden wurden, aber im Wesentlichen dasselbe tun. SYSCALL / SYSRET stand an erster Stelle und war nur für AMD, SYSENTER / SYSEXIT war Intel, aber AMD implementiert es jetzt. Also beschreibe ich SYSENTER / SYSEXIT.

Im Gegensatz zu Call Gates kann SYSENTER nur zum Weiterleiten an Ring 0 und nur an einen Standort verwendet werden. Es hat jedoch den Vorteil einer extrem geringen Latenz, da es im Gegensatz zu einem Aufruf oder einer Unterbrechung den Stapel nicht berührt.

Der Übertragungsort wird unter Verwendung von drei modellspezifischen Registern eingerichtet: eines für die Segmentinformation und eines für den Befehlszeiger und den Stapelzeiger des Kernel-Codes. Da nichts auf den Stapel "geschoben" wird, ist der Benutzermoduscode dafür verantwortlich, dem Kernel mitzuteilen, wohin er zurückkehren soll, indem der Rückkehrbefehlszeiger und der Stapelzeiger in Registern übergeben werden. Der Kernel ist für die Wiederherstellung des Stapelzeigers verantwortlich, und der SYSEXIT-Befehl stellt den Befehlszeiger wieder her.

Weitere Informationen zu den Anweisungen SYSENTER und SYSEXIT.

Pseudonym
quelle