Lyx OS – SMP & Task-API

Diese Seite dokumentiert das Symmetric-Multiprocessing-Modul (kernel/smp.lyx, WP10): LAPIC-Zugriff, AP-Startup und die leichtgewichtige Task-API für Kernel-Code.

Architektur · Sync-Primitive · Kernel-Interna


1. Überblick

smp.lyx stellt drei Funktionsgruppen bereit:

  1. CPU-Erkennung — Anzahl logischer Prozessoren via CPUID
  2. LAPIC-Zugriff — Direkte Lese-/Schreiboperationen auf den Local APIC
  3. AP-Startup — Trampoline-Code in Niedrigspeicher schreiben, INIT+SIPI senden
  4. Task-API — Leichtgewichtige Aufgaben an APs delegieren; auf Single-Core synchron ausführen

Alle Operationen laufen als vmm_op-Aufrufe (mmap mit negativem Protokoll-Marker).


2. CPU-Erkennung

var ncpu: int64 := GetCpuCount();        // CPUID — Anzahl logischer Prozessoren
var ap_count: int64 := GetApStartedCount();  // Wie viele APs sind hochgefahren?

GetCpuCount() gibt die Anzahl der logischen Kerne zurück, die CPUID meldet. Das schließt Hyper-Threading mit ein.

GetApStartedCount() gibt an, wie viele Application Processors (APs) das Trampoline durchlaufen und sich als bereit gemeldet haben. Auf einem echten Single-Core-System oder wenn der Trampoline-Start fehlschlägt, bleibt der Wert 0.


3. LAPIC-Zugriff

var val: int64 := LapicRead(offset);     // LAPIC-Register lesen (offset in Bytes)
LapicWrite(offset, value);               // LAPIC-Register schreiben

Der LAPIC ist memory-mapped. Offset-Konstanten folgen dem Intel-Handbuch (z.B. 0x20 = LAPIC-ID, 0x280 = ESR, 0x300 = ICR-Low für IPIs).


4. AP-Startup

Der Bootstrap Processor (BSP) schreibt einen kleinen 16-Bit-Trampoline-Stub in den Niedrigspeicher (physisch 0x8000) und schickt dann die INIT+2×SIPI-Sequenz an den Ziel-LAPIC.

// Trampoline-Stub schreiben (inkl. CR3/Stack/Entry-Point/GDTR patchen):
var stack: int64 := mmap(0, 8192, 3, 34, -1, 0);
SmpPrepareTrampoline(stack + 8192);   // stack_top = Oberkante des 8-KB-Stacks

// INIT + 2× SIPI an AP mit LAPIC-ID 1 senden:
SmpStartAp(1);

// Warten bis AP sich meldet (Busy-Wait mit Timeout):
var wait: int64 := 0;
while (GetApStartedCount() == 0 && wait < 5000000) { wait := wait + 1; }
var ready: int64 := GetApStartedCount();

Was SmpPrepareTrampoline macht:

  • Kopiert Trampoline-Code nach physisch 0x8000
  • Patcht CR3 (aus aktuellem Kernel-CR3), Stack-Top, Kernel-Entry-Point und GDTR in den Stub
  • Nach dem ersten SIPI läuft der AP im 16-Bit-Real-Mode in 0x8000 und wechselt selbst in den 64-Bit-Long-Mode

5. Task-API

Die Task-API ermöglicht es, einfache Berechnungsaufgaben an APs zu delegieren. Es gibt 16 Task-Slots; jeder Slot hat einen Status (FREE/PENDING/RUNNING/DONE) und ein Ergebnis-Feld.

// Task erzeugen (fn_idx: 0=square(arg), 1=sum(1..arg)):
var t0: int64 := TaskSpawn(0, 7);    // square(7) → erwartet 49
var t1: int64 := TaskSpawn(1, 10);   // sum(10)   → erwartet 55

// Single-Core-Fallback (kein AP verfügbar):
if (GetApStartedCount() == 0) {
    TaskRunPending();   // BSP führt alle ausstehenden Tasks synchron aus
}

// Auf Abschluss warten und Ergebnis holen:
var r0: int64 := TaskAwait(t0);   // spinnt auf DONE, gibt TaskGetResult zurück
var r1: int64 := TaskAwait(t1);

Funktionen im Detail:

Funktion Beschreibung
TaskSpawn(fn_idx, arg) Task anlegen; gibt task_id (0–15) oder −1 (keine freien Slots) zurück
TaskPoll(task_id) Status lesen: 0=FREE, 1=PENDING, 2=RUNNING, 3=DONE
TaskGetResult(task_id) Ergebnis holen (nur gültig wenn Status = DONE)
TaskAwait(task_id) Kombination aus TaskPoll + TaskGetResult; spinnt bis DONE
TaskRunPending() BSP-Fallback: alle PENDING-Tasks sequenziell ausführen

Einschränkungen:

  • Nur zwei vordefinierte Task-Funktionen (fn_idx 0 und 1 — für Demos)
  • Maximal 16 gleichzeitige Task-Slots
  • Kein Scheduling zwischen Tasks — ein Task läuft auf einem AP bis zum Ende durch
  • Erweiterung auf beliebige Funktionen ist für spätere Work-Packages geplant

6. Typischer Ablauf (Multi-Core-Boot)

// In kernel.lyx main():

// 1. Trampoline schreiben und AP hochfahren:
var ap_stack: int64 := mmap(0, 8192, 3, 34, -1, 0);
SmpPrepareTrampoline(ap_stack + 8192);
SmpStartAp(1);

// 2. Auf AP warten (max. ~5 Mio. Iterationen):
var wait: int64 := 0;
while (GetApStartedCount() == 0 && wait < 5000000) { wait := wait + 1; }

// 3. Tasks spawnen:
var t0: int64 := TaskSpawn(0, 7);
var t1: int64 := TaskSpawn(1, 10);

// 4. Single-Core-Fallback:
if (GetApStartedCount() == 0) { TaskRunPending(); }

// 5. Ergebnisse abwarten:
PrintLn(IntToStr(TaskAwait(t0)));   // "49"
PrintLn(IntToStr(TaskAwait(t1)));   // "55"

Letzte Aktualisierung: 2026-06-13