Interrupts (PI4): Unterschied zwischen den Versionen
| Zeile 277: | Zeile 277: | ||
== Bei der Ausführung des Handlers == | == Bei der Ausführung des Handlers == | ||
Da eine Ausnahme zu jeder Zeit geschehen kann, wird das laufende Programm unterbrochen. Die Register, die für das Ausführen des Programms verwendet werden, werden auch in der Ausnahme verwendet. Dies kann zu Problemen kommen, wenn der Code an der Ursprünglichen Adresse wieder ausgeführt wird. Aus diesem Grund ist es wichtig, wenn es um eine Absichtliche Ausnahme geht, die Register zu sichern, bevor die Ausführung des entsprechenden Codes für die Ausnahme abläuft. Aus diesem Grund, werden sämtliche Register auf den Stack abgelegt. Da wir auch Gleitkomma erlauben, müssen auch diese Register entsprechend abgelegt werden. Bei erfolgreichen Abarbeiten der Ausnahme, werden diese Register wieder aus dem Stack gefüllt und das Programm kann seinen | Da eine Ausnahme zu jeder Zeit geschehen kann, wird das laufende Programm unterbrochen. Die Register, die für das Ausführen des Programms verwendet werden, werden auch in der Ausnahme verwendet. Dies kann zu Problemen kommen, wenn der Code an der Ursprünglichen Adresse wieder ausgeführt wird. Aus diesem Grund ist es wichtig, wenn es um eine Absichtliche Ausnahme geht, die Register zu sichern, bevor die Ausführung des entsprechenden Codes für die Ausnahme abläuft. Aus diesem Grund, werden sämtliche Register auf den Stack abgelegt. Da wir auch Gleitkomma erlauben, müssen auch diese Register entsprechend abgelegt werden. Bei erfolgreichen Abarbeiten der Ausnahme, werden diese Register wieder aus dem Stack gefüllt und das Programm kann seinen Dienst fortführen. | ||
Version vom 24. Juli 2025, 12:25 Uhr
Interrupts sind Signale, die den normalen Ablauf eines Prozessors unterbrechen, um spezielle Routineabläufe auszuführen. Sie spielen eine wichtige Rolle bei der Steuerung und Kommunikation in Echtzeitsystemen und bei der Handhabung von Hardware-Ereignissen.
Definition von Interrupts
Ein Interrupt ist ein Signal, das den CPU dazu veranlasst, den aktuell ausgeführten Befehlssatz zu unterbrechen und eine Interrupt Service Routine (ISR) oder Interrupt-Handler auszuführen.
Auslöser für Interrupts
Interrupts können durch verschiedene Ereignisse ausgelöst werden:
Externe Hardware-Ereignisse
- Tastatureingaben: Wenn eine Taste gedrückt wird.
- Mausbewegungen: Wenn die Maus bewegt wird oder eine Taste gedrückt wird.
- Timer: Timer können so konfiguriert werden, dass sie periodische Interrupts auslösen.
Interne Hardware-Ereignisse
- Peripheriegeräte: Ereignisse von Geräten wie Netzwerkadaptern, Festplatten, usw.
- Signale von Sensoren: Daten von Temperatursensoren, Beschleunigungssensoren, usw.
Betriebssystem-Ereignisse
- Systemaufrufe: Bestimmte Anfragen von Software an das Betriebssystem.
- Fehlermeldungen: Wenn Fehler wie Division durch Null oder Speicherzugriffsfehler auftreten.
Typen von Interrupts
Interrupts können auch kategorisiert werden in:
- Maskierbare Interrupts: Diese können vom CPU vorübergehend ignoriert oder maskiert werden.
- Nicht maskierbare Interrupts (NMI): Diese können nicht ignoriert werden und haben höchste Priorität.
Betriebsmodi und Interrupts
Der ARM-Cortex-A72-Prozessor, der im Raspberry Pi 4 verwendet wird, unterstützt verschiedene Ebenen und Modi, die sich auf das Verhalten von Interrupts auswirken können:
Interrupt Enable Stufe (EL1 und niedrigere Ebenen)
Der Prozessor muss sich in einem Modus befinden, in dem Interrupts erlaubt sind (EL1 oder niedriger). Dies bedeutet, dass die globalen Interrupts nicht deaktiviert sein dürfen. Dies wird durch das Setzen bestimmter Bits im Program Status Register (PSTATE) kontrolliert.
DAIF-Flags (erlauben Interrupts)
| D | Debug-Exceptions |
| A | SError-Interrupts |
| I | IRQ-Interrupts |
| F | FIQ-Interrupts |
Diese Flags können im DAIF-Register eingestellt werden. Um IRQ-Interrupts zu ermöglich, sollte das I-Flag gelöscht werden.
Wichtige Befehle und Register
Hier ein Überblick über die relevanten Register und Befehle:
Programmstatusregister (PSTATE): Kontrolliert u.a. die globalen Interrupts.
Beispiel (ARM-Assembly) zur Aktivierung von Interrupts
Folgender Code zeigt, wie man die IRQ-Interrupts explizit zulässt:
.global enable_interrupts
enable_interrupts:
mrs x0, DAIF // DAIF Register lesen
bic x0, x0, #(1<<7) // IRQ-Freigabe (Setze I-Flag auf 0)
msr DAIF, x0 // DAIF Register zurückschreiben
ret
Interrupts mit dem Raspberry 4 im 64-Bit-Modus
Wie bereits zuvor beschrieben, muss der Raspberry Pi 4 in einen bestimmten Level sein.
Wenn kein spezieller Berechtigungslevel im Bare-Metal-Kernel angegeben wurde und der Raspberry Pi 4 direkt nach dem Einschalten startet, hängt der anfängliche Berechtigungslevel davon ab, in welchem Modus der Prozessor von der Firmware initialisiert wurde.
Beim Raspberry Pi ist der typische Ablauf wie folgt:
Der Bootloader (z. B. der von der GPU initialisierte Bootcode) lädt die Firmware und deinen Kernel. Nach der Initialisierung durch die Firmware (häufig start.elf), wird die CPU in EL2 (Hypervisor Mode) gestartet, wenn kein spezieller Wechsel zu einem bestimmten Berechtigungslevel vorgegeben wird.
Damit für uns alles gut funktioniert, müssen wir in den Level 1. Zuvor müssen wir uns noch um die verschiedenen CPUs kümmern. Zur Zeit Laufen alle vier Kerne des ARM-Prozessors parallel.
Umsetzung auf den Raspberry Pi 4 (64-Bit)
Bisher haben wir in unserem Boot-Code nicht viel geschrieben gehabt. Das einzige war, dass wir unsere Kernel-Stackpointer gesetzt haben und anschließend direkt in unseren Code gegangen sind.
Nun hat der Raspberry Pi 4 allerdings 4 Kerne, die unabhängig voneinander Programme ablaufen lassen können. Bei Interrupts, könnte das uns Probleme bereiten, wenn ein bestimmter Kern, den wir eigentlich nicht verwenden, eine Ausnahme verursacht. Dies könnte zu einem Chaotischen Zustand des Raspberrys führen. Damit dies nicht passiert, werden wir unseren Code nur noch auf der Haupt-CPU ausführen und die übrigen CPUs schlafen legen:
.section .init
.global _start
_start:
mrs x1, mpidr_el1 //Lies den Inhalt des Systemregisters MPIDR_EL1 (Multiprocessor Affinity Register) und speichere ihn in Register x1.
and x1, x1, #3
cbz x1, 2f
1: // Core 1-3 schlafen legen
wfe
b 1b
2:
Mit "mrs" lesen wir das Systemregister MPIDR_EL1 aus. Dies beinhaltet die Kennung des CPUs. Diese ID steht in den unteren 2 Bits und kann durch "and #3" herausextrahiert werden. Mit "cbz" Sprung, wenn "NULL" wird die CPU0 verwendet, die hier an Label "2:" weiterspring. Alle anderen CPUs werden mit "wfe" schlafen gelegt und oder in die Dauerschleife "b 1b" versetzt.
Als nächstes prüfen wir das Level, um sicherzustellen, dass wir nicht schon bereits in EL1 sind.
mrs x0, CurrentEL //Überprüfen, ob bereits in EL1
cmp x0, #4
beq 5f
Auch hierfür gibt es ein Systemregister, in dem der Momentane Level steht. Mit "mrs" und "CurrentEL" können wir dieses Register lesen. Wenn hier der Wert 4 steht, sind wir bereits in EL1 und überspringen das Umschalten in Level1.
Wechsel von EL2 nach EL1
//Wechsel von EL2 nach EL1
ldr x0, =EXCEPTION_STACK
msr sp_el1, x0
mrs x0, cnthctl_el2
orr x0, x0, #0x3
msr cnthctl_el2, x0
msr cntvoff_el2, xzr
mrs x0, midr_el1
mrs x1, mpidr_el1
msr vpidr_el2, x0
msr vmpidr_el2, x1
mov x0, #0x33ff
msr cptr_el2, x0
msr hstr_el2, xzr
mov x0, #3 << 20
msr cpacr_el1, x0
mov x0, #(1 << 31)
msr hcr_el2, x0
mov x0, #0x0800
movk x0, #0x30d0, lsl #16
msr sctlr_el1, x0
mov x0, #0x3c4
msr spsr_el2, x0
adr x0, 5f
msr elr_el2, x0
eret
- ldr x0, =EXCEPTION_STACK: Die Adresse des Exception-Stacks wird geladen.
- msr sp_el1, x0: Der Stack des EL1 (sp_el1) wird gesetzt.
Dann folgen verschiedene Register-Konfigurationen, welche die Timer-Funktionalität sowie die virtuelle CPU-Profilierung einrichten:
- msr cnthctl_el2, x0 und msr cntvoff_el2, xzr: Timer-Konfiguration.
- msr vpidr_el2, x0 und msr vmpidr_el2, x1: Virtualisierungs-Konfiguration.
Weitere Einstellungen betreffen die Steuerregister und die HCR_EL2-Konfiguration:
- msr cptr_el2, x0, msr hstr_el2, xzr: Steuerregister.
- msr hcr_el2, x0: Hypervisor Configuration Register (HCR_EL2) wird konfiguriert.
Schließlich wird der Systemsteuer-Register SCTLR_EL1 initialisiert und das SPSR_EL2 gesetzt:
- msr sctlr_el1, x0: Einstellung des System Control Register im EL1.
- msr spsr_el2, x0: Setzen des Saved Program Status Register, damit eret weiß, wohin er springen muss.
Mit eret erfolgt der eigentliche Wechsel auf Exception Level 1:
- eret: Return from exception und Wechsel auf EL1.
Initialisierung im EL1
5:
ldr x1,=_start
mov sp,x1 // Stack setzen
// BSS section reinigen
ldr x1, =__bss_start // Start address
ldr w2, =__bss_size // Size of the section
3:
cbz w2, 4f // Quit loop if zero
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b // Loop if non-zero
// springe in die SystemInitierung
4:
bl sysinit
// Falls man zurückkommt, in die Dauerschleife
b 1b
- ldr x1,=_start: Lade die Startadresse.
- mov sp,x1: Setzen des Stack-Pointers an die Startadresse.
Reinigung der BSS-Sektion (__bss_start bis __bss_size) um sicherzustellen, dass alle uninitialisierten Daten null sind.
Warum wird der Wechsel durchgeführt?
Der Wechsel von EL2 zu EL1 wird durchgeführt, um vom Hypervisor-Modus in den normalen Betriebsmodus des Systems zu wechseln. EL2 ist für Virtualisierung und Hypervisor-Funktionen reserviert, während EL1 für das OS oder Bare-Metal-Anwendungen wie hier vorgesehen ist. EL1 bietet die notwendige Kontrolle und Privilegien für die Initialisierung und den Betrieb des Systems. Der Hypervisor (EL2) wird meist nur zu Beginn für Konfigurationen benötigt oder bei niedriger privilegierten OS-Schichten.
Vektortabelle
Wie im Code zuvor ersichtlich, wird dem Prozessor ein Zeiger auf eine Vektortabelle übergeben. Diese benötigt der Prozessor, um zu sehen, was er machen soll, wenn bestimmte Ausnahmen (Exceptions), wie bereits beschrieben, bei der Ausführung entstehen.
Struktur einer Vektortabelle
Die ARMv8-Architektur, die im Raspberry Pi 4 verwendet wird, verfügt über eine spezielle Vektortabelle für verschiedene Arten von Ausnahmen. Die Tabelle enthält Adressen für die folgenden Ausnahmen:
- Synchronous Exception - Lower Level
- IRQ (Normal Interrupt) - Lower Level
- FIQ (Fast Interrupt) - Lower Level
- SError (System Error) - Lower Level
- Synchronous Exception - Current Level
- IRQ (Normal Interrupt) - Current Level
- FIQ (Fast Interrupt) - Current Level
- SError (System Error) - Current Level
Zusätzlich muss man beachten, dass der Prozessor theoretisch im AArch64 und im AArch32 Modus befinden kann.
Beispiel einer Vektortabelle
Hier ist ein Beispiel, wie eine Vektortabelle für EL1 aussehen könnte:
.align 11
.globl VectorTable
VectorTable:
// Vektoren für EL1t (Current Exception Level SP_el0)
b sync_exception_el1t // Synchronous Exception
b irq_handler_el1t // IRQ - Normal Interrupt
b fiq_handler_el1t // FIQ - Fast Interrupt
b serror_handler_el1t // SError - System Error
// Vektoren für EL1h (Current Exception Level SP_el1)
b sync_exception_el1h // Synchronous Exception
b irq_handler_el1h // IRQ - Normal Interrupt
b fiq_handler_el1h // FIQ - Fast Interrupt
b serror_handler_el1h // SError - System Error
// Vektoren für EL0 64-bit Modus
b sync_invalid_el0_64 // Synchronous EL0 (64-bit)
b irq_invalid_el0_64 // IRQ EL0 (64-bit)
b fiq_invalid_el0_64 // FIQ EL0 (64-bit)
b error_invalid_el0_64 // Error EL0 (64-bit)
// Vektoren für EL0 32-bit Modus
b sync_invalid_el0_32 // Synchronous EL0 (32-bit)
b irq_invalid_el0_32 // IRQ EL0 (32-bit)
b fiq_invalid_el0_32 // FIQ EL0 (32-bit)
b error_invalid_el0_32 // Error EL0 (32-bit)
Erläuterungen zu den Einträgen:
- sync_invalid_*: Diese Handler werden für synchrone Ausnahmen verwendet, wie z.B. system calls oder undefinierte Instruktionen.
- irq_invalid_*: Diese Handler werden für normale Interrupt-Anfragen verwendet.
- fiq_invalid_*: Diese Handler werden für schnelle Interrupt-Anfragen verwendet.
- error_invalid_*: Diese Handler werden für Systemfehler verwendet.
Wichtige Punkte zur Vektortabelle
- Ausrichtung: Die Vektortabelle muss auf eine bestimmte Weise ausgerichtet sein, typischerweise 2^11 Byte (2048 Byte, align 11), um den Anforderungen des ARM-Prozessors zu entsprechen.
- Reihenfolge: Die Reihenfolge der Einträge ist fix und muss den Spezifikationen entsprechend sein. Jeder Vektor muss die korrekte Entsprechung der Ausnahme-Adressen haben.
- Handlers: Jeder Eintrag in der Vektortabelle verweist auf einen spezifischen Handler für jede Art von Ausnahme.
Was ist zu beachten bei Interrupts
Um zu verhindern, dass während der Bearbeitung einer Ausnahme (Interrupt Handling) eine weitere Ausnahme auftritt und zu unerwünschten Zuständen führt, gibt es mehrere Mechanismen, die verwendet werden können. Diese Mechanismen beinhalten das Maskieren von Interrupts, das Setzen von Flags und das Sicherstellen einer richtigen Reihenfolge und Priorität von Ausnahmebehandlungen.
Mechanismen zur Verhinderung von Ausnahmen während der Ausnahmebehandlung
- Maskieren von Interrupts:
Beim Eintritt in einen Ausnahme-Handler kannst du alle weiteren Interrupts maskieren. Dies bedeutet, dass andere Interrupts so lange blockiert werden, bis der aktuelle Handler vollständig abgeschlossen ist. Die ARM-Architektur stellt dafür spezielle Register (DAIF, CPSR) zur Verfügung, mit denen Interrupts individuell oder global maskiert werden können.
- DAIF-Register:
Das DAIF-Register enthält Flags, um Debug-, SError-, IRQ- und FIQ-Interrupts zu maskieren. Um alle Interrupts zu maskieren, wird das DAIF-Register mit den entsprechenden Bits gesetzt.
Bei der Ausführung des Handlers
Da eine Ausnahme zu jeder Zeit geschehen kann, wird das laufende Programm unterbrochen. Die Register, die für das Ausführen des Programms verwendet werden, werden auch in der Ausnahme verwendet. Dies kann zu Problemen kommen, wenn der Code an der Ursprünglichen Adresse wieder ausgeführt wird. Aus diesem Grund ist es wichtig, wenn es um eine Absichtliche Ausnahme geht, die Register zu sichern, bevor die Ausführung des entsprechenden Codes für die Ausnahme abläuft. Aus diesem Grund, werden sämtliche Register auf den Stack abgelegt. Da wir auch Gleitkomma erlauben, müssen auch diese Register entsprechend abgelegt werden. Bei erfolgreichen Abarbeiten der Ausnahme, werden diese Register wieder aus dem Stack gefüllt und das Programm kann seinen Dienst fortführen.
Speicherungen der Registersätze (x0-x30, lr), um das Rückkehren nach der Ausnahme sicherzustellen. Ausführen der eigentlichen Ausnahmebehandlung.
Wiederherstellen Ausgangszustände und Entmaske Interrupts:
POP {x0-x30, lr} stellt die gespeicherten Register wieder her. MRS x0, DAIF liest den aktuellen Zustand des DAIF-Registers. BIC x0, x0, #0x3C0 löscht die Bits 8 (D), 9 (A), 6 (I), und 7 (F), um die Interruptmaskierung aufzuheben. MSR DAIF, x0 schreibt den modifizierten Wert zurück in das DAIF-Register, um die Interrupts wieder zu erlauben. Priority und Reihenfolge von Ausnahmen
Zusätzlich zum Maskieren von Interrupts können auch Priorisieren und die richtige Verwaltung der Reihenfolge helfen:
Priorisieren von Interrupts: Bestimmte Interrupts können kritisch und hoch-priorisierter sein, während andere niederpriorisiert behandelt werden. Der interrupt controller kann verwendet werden, um Prioritäten der Interrupts zu verwalten. Zusammenfassung
Interrupt-Maskierung und richtige Priorisierung sind die Schlüssel, um zu verhindern, dass während des Ausnahme-Handlings weitere Ausnahmen auftreten. Das Maskieren der Interrupts kann über das DAIF-Register erfolgen und sollte vor Austritt aus dem Ausnahme-Handler aufgehoben werden. Diese Mechanismen stellen sicher, dass die Ausnahmebehandlung ohne Störungen abgeschlossen werden kann.
Ja, es gibt Unterschiede im Aufbau der Vektortabellen, insbesondere wenn verschiedene Betriebsmodi und Ausnahmelevel berücksichtigt werden. In der ARMv8-Architektur werden normalerweise 16 Einträge verwendet, um den verschiedenen Ausnahmebedingungen innerhalb der verschiedenen Exception Levels gerecht zu werden. Dies bezieht sich sowohl auf 64-Bit- als auch auf 32-Bit-Modi und auf die verschiedenen Zustände eines Exception Levels.
Erklärung der 16 Einträge
Die ARMv8-Architektur teilt die Exception Level Vektortabelle in vier verschiedene Ausnahmearten und zwei verschiedene Zustände jeder Ausnahmeart (z. B. EL1t und EL1h), und dies sowohl für 64-Bit- als auch für 32-Bit-Modi. Hier ist die detaillierte Aufschlüsselung:
Ausnahmearten und Zustände: Synchronous: Sorgt für synchrone Ausnahmen, z.B. Software-Fehler. IRQ: Normaler Interrupt Request. FIQ: Schneller Interrupt Request. SError: System Error.
Diese Ausnahmen haben jeweils einen Zustand für:
Current Exception Level with SP_el0 (_el1t) Wenn der Stack Pointer SP_el0 verwendet wird. Current Exception Level with SP_elx (_el1h) Wenn der Stack Pointer von SP_elx verwendet wird (z.B. SP_el1).
Zusätzlich dazu müssen die Einträge sowohl für 64-Bit als auch 32-Bit-Untermodi der Ausnahmebehandlung berücksichtigt werden. Dies kommt typischerweise bei der Arbeit in der 64-Bit-Arbeitsumgebung vor. Im Allgemeinen:
64-Bit Mode EL0 (_el0_64) 32-Bit Mode EL0 (_el0_32) Beispiel einer vollständigen 16-Eintrag-Vektortabelle: .align 11 .section .vectors, "a"
vector_table:
// Einträge für EL1t (Current Exception Level SP_el0) ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t
// Einträge für EL1h (Current Exception Level SP_el1) ventry sync_invalid_el1h // Synchronous EL1h ventry handle_el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h
// Einträge für EL0 64-bit Modus ventry sync_invalid_el0_64 // Synchronous EL0 (64-bit) ventry irq_invalid_el0_64 // IRQ EL0 (64-bit) ventry fiq_invalid_el0_64 // FIQ EL0 (64-bit) ventry error_invalid_el0_64 // Error EL0 (64-bit)
// Einträge für EL0 32-bit Modus ventry sync_invalid_el0_32 // Synchronous EL0 (32-bit) ventry irq_invalid_el0_32 // IRQ EL0 (32-bit) ventry fiq_invalid_el0_32 // FIQ EL0 (32-bit) ventry error_invalid_el0_32 // Error EL0 (32-bit)
.macro ventry label b \label .endm
Erläuterungen zu den Einträgen: sync_invalid_*: Diese Handler werden für synchrone Ausnahmen verwendet, wie z.B. system calls oder undefinierte Instruktionen. irq_invalid_*: Diese Handler werden für normale Interrupt-Anfragen verwendet. fiq_invalid_*: Diese Handler werden für schnelle Interrupt-Anfragen verwendet. error_invalid_*: Diese Handler werden für Systemfehler verwendet. Verwendung und Initialisierung der Vektortabelle:
Die Registerzüge VBAR_EL1, VBAR_EL2 und VBAR_EL3 müssen entsprechend initialisiert werden, um die Adresse dieser Vektortabellenstruktur zu verwenden. Hier ist ein Beispiel für die Initialisierung auf EL1:
.global _start
_start:
// Initialisiere Stack Pointer für EL1 LDR x1, =stack_top_el1 MSR SP_EL1, x1
// Lade die Adresse der Vektortabelle ADR x0, vector_table MSR VBAR_EL1, x0 // Setze VBAR_EL1 zur Adresse der Vektortabelle
// Initialise Interrupts BL enable_interrupts
// Hauptprogrammschleife
main_loop:
B main_loop
sync_invalid_el1t:
// Handler für synchronous exception in EL1t RET
irq_invalid_el1t:
// Handler für IRQ in EL1t RET
fiq_invalid_el1t:
// Handler für FIQ in EL1t RET
error_invalid_el1t:
// Handler für System Error in EL1t RET
sync_invalid_el1h:
// Handler für synchronous exception in EL1h RET
handle_el1_irq:
// Hauptinterrupt Handler für IRQ in EL1h RET
fiq_invalid_el1h:
// Handler für FIQ in EL1h RET
error_invalid_el1h:
// Handler für System Error in EL1h RET
sync_invalid_el0_64:
// Handler für synchronous exception in 64-bit EL0 RET
irq_invalid_el0_64:
// Handler für IRQ in 64-bit EL0 RET
fiq_invalid_el0_64:
// Handler für FIQ in 64-bit EL0 RET
error_invalid_el0_64:
// Handler für System Error in 64-bit EL0 RET
sync_invalid_el0_32:
// Handler für synchronous exception in 32-bit EL0 RET
irq_invalid_el0_32:
// Handler für IRQ in 32-bit EL0 RET
fiq_invalid_el0_32:
// Handler für FIQ in 32-bit EL0 RET
error_invalid_el0_32:
// Handler für System Error in 32-bit EL0 RET
.section .bss .stack_area:
.skip 0x1000 // Speicherplatz reservieren
stack_top_el1:
Zusammenfassung
In der ARMv8-Architektur gibt es eine erweiterte Struktur für Vektortabellen mit 16 Einträgen, um verschiedene Arten und Zustände von Ausnahmen vollständig zu unterstützen. Bei der Ausnahmebehandlung können unterschiedliche Modi (64-Bit und 32-Bit) und verschiedene Betriebsarten (_el1t, _el1h, _el0_64, _el0_32) gleichzeitig berücksichtigt werden. Eine korrekte Initialisierung und Einhaltung der Reihenfolge dieser Einträge ist für die zuverlässige Ausnahmebehandlung unerlässlich.