Hintergrund · ARM Cortex-A9 / Cyclone V
Jede Entscheidung in der Implementierung — das Trampolin, die SGIs, die Cache-Kohärenz — folgt direkt aus der Architektur darunter. Dieses Kapitel erklärt sie vollständig: vom SoC bis zum einzelnen Register-Bit.
Der Cyclone V (Altera) besteht aus zwei komplett getrennten Teilen: einem ARM Cortex-A9 Dual-Core mit integrierten Controllern für CAN, Ethernet, DDR3, Flash, SPI, I2C und USB — und einem frei programmierbaren FPGA-Teil. Diese Arbeit betrifft ausschließlich den ARM-Teil; der FPGA bleibt außen vor.
Entscheidend für SMP: jeder Kern hat eigene L1-Caches und eine eigene MMU — beide müssen für SMP identisch konfiguriert werden und brauchen eine aktive Kohärenz-Lösung (dazu mehr unter Snoop Control Unit). Der L2-Cache dagegen ist von Anfang an geteilt und damit automatisch kohärent.
Jeder der beiden Cortex-A9-Kerne ist selbst wieder aus mehreren eigenständigen Bausteinen zusammengesetzt — so sitzen sie tatsächlich zueinander auf dem Die.
Die Pipeline greift für jeden Speicherzugriff über die MMU; die MMU wiederum unterscheidet nicht, ob die Adresse am Ende im I-Cache oder D-Cache landet — das entscheidet sich erst danach. Genau an dieser Stelle setzt die SCU an: sie sitzt außerhalb dieses Kerns, zwischen den D-Caches beider Kerne (siehe Abschnitt 07).
Eine ARM-CPU kennt mehrere Betriebsmodi mit unterschiedlichen Rechten. Die meisten laufen privilegiert (PL1/PL2); nur der User-Modus (PL0) ist eingeschränkt. In eCos läuft praktisch alles im Supervisor-Modus — auch die Applikation selbst, nicht nur der Kernel.
| Modus | Code | Level | Tritt ein bei |
|---|---|---|---|
| User (usr) | 10000 | PL0 | normale Programme |
| FIQ | 10001 | PL1 | Fast Interrupt Request |
| IRQ | 10010 | PL1 | normaler Interrupt |
| Supervisor (svc) | 10011 | PL1 | Start-Modus des Kernels |
| Monitor (mon) | 10110 | PL1 | Security Extensions |
| Abort (abt) | 10111 | PL1 | Data Abort |
| Hyp (hyp) | 11010 | PL2 | Virtualization Extensions |
| Undef (und) | 11011 | PL1 | Undefined Instruction |
| System (sys) | 11111 | PL1 | privilegiert, teilt Register mit User |
Jeder Modus hat eigene, "banked" Stackpointer (r13) und Link-Register (r14) — beim Booten müssen diese Stacks für jeden Modus einzeln initialisiert werden, sonst kracht es beim ersten Moduswechsel.
16 General-Purpose-Register (r0–r15), deren Rollen von der "ARM Architecture Procedure Call Standard" festgelegt sind — wichtig, damit handgeschriebener Assembler und compilierter C-Code zusammenarbeiten können.
| Register | Synonym | Rolle |
|---|---|---|
| r0–r3 | a1–a4 | Funktionsargumente / Rückgabewerte |
| r4–r10 | v1–v7 | Variable Register |
| r12 | ip | Intra-Procedure Scratch |
| r13 | sp | Stack Pointer (banked pro Modus) |
| r14 | lr | Link Register (Rücksprungadresse) |
| r15 | pc | Program Counter |
Zwei Befehlssätze sind relevant: ARM (32 Bit, leistungsoptimiert, immer verfügbar) und Thumb/Thumb-2 (16-Bit-Kern mit optionalen 32-Bit-Erweiterungen, ca. 30% kompakterer Code). Welcher Satz gerade aktiv ist, steht im T-Bit des CPSR — ein Modus-Wechsel flusht die Instruction-Pipeline, damit nichts im falschen Befehlssatz dekodiert wird.
Jede Exception und jeder Interrupt springt an eine feste Adresse. eCos nutzt die "high" Variante dieser Tabelle (0xFFFF0000-Basis).
| Exception / Interrupt | High-Adresse |
|---|---|
| Reset | 0xFFFF.0000 |
| Undefined Instruction | 0xFFFF.0004 |
| Software Interrupt (SVC) | 0xFFFF.0008 |
| Prefetch Abort | 0xFFFF.000C |
| Data Abort | 0xFFFF.0010 |
| Interrupt Request (IRQ) | 0xFFFF.0018 |
| Fast Interrupt Request (FIQ) | 0xFFFF.001C |
Der Data-Abort-Eintrag ist genau die Mechanik hinter der 0x0-Absturz-Geschichte auf der Startseite — wenn ein nicht gemappter oder absichtlich fehlgemappter Speicherbereich angesprochen wird, landet die CPU exakt hier.
Der im Cyclone V verbaute GIC (PL390, GIC-Version 1 mit Security Extensions) unterstützt bis zu 180 Interrupts, aufgeteilt in drei Typen:
| ID-Bereich | Typ | Bedeutung |
|---|---|---|
| 0–15 | SGI | Software Generated — für Kommunikation zwischen Kernen |
| 16–31 | PPI | Private Peripheral — pro Kern, z.B. der private Timer |
| 32–211 | SPI | Shared Peripheral — normale Hardware-Interrupts |
Aus Programmierersicht besteht der GIC aus zwei Teilen: dem Distributor (muss aktiviert sein, damit überhaupt etwas weitergeleitet wird) und dem CPU-Interface — und zwar pro Kern einzeln. Genau dieser zweite Teil war für CPU1 anfangs nicht aktiv: ohne ein aktiviertes CPU-Interface auf CPU1 kommt dort nie ein Interrupt an, egal wie korrekt der Rest konfiguriert ist.
Die MMU übersetzt virtuelle in physikalische Adressen, unterteilt den Speicher in Regionen und legt fest, wie jede Region gecacht wird. Für SMP entscheidend: die Translation Table muss für beide Kerne identisch sein, und Speicher, der zwischen Kernen geteilt wird, muss als "shareable" markiert sein — sonst greift die Cache-Kohärenz gar nicht erst.
Caching-Strategien selbst werden über drei Bits pro Translation-Table-Eintrag konfiguriert:
Das S-Bit (shareable) entscheidet zusätzlich, ob ein Bereich überhaupt zwischen CPUs kohärent gehalten wird — unabhängig von der gewählten Cache-Strategie.
Der L2-Cache ist von Natur aus geteilt und damit kohärent. Die beiden L1-Caches sind es nicht — ohne Gegenmaßnahme könnten CPU0 und CPU1 für dieselbe Adresse unterschiedliche Werte im Cache stehen haben. Die SCU behebt genau das.
Voraussetzung dafür: die SCU muss explizit aktiviert werden, und der betroffene Speicher muss (siehe oben) als shareable markiert sein. Fehlt eines von beiden, bleibt die Hardware-Kohärenz wirkungslos — unabhängig davon, wie korrekt der Rest der SMP-Implementierung ist.