Lyx OS – Kernel-Interna
Diese Seite richtet sich an Beitragende, die den Kernel weiterentwickeln möchten: Build-Ablauf, Modul-Interaktion, Initialisierungsreihenfolge und praktische Debugging-Techniken.
→ Architektur · Syscall-ABI · Übersicht
1. Verzeichnisstruktur
lyx-os/
├── bootloader/
│ ├── boot.asm ← UEFI-Bootloader (~2900 Zeilen NASM)
│ ├── build.sh ← Build-Skript (Assemble + Compile + Image)
│ └── run.sh ← QEMU-Startskript
├── kernel/
│ ├── kernel.lyx ← Kernel-Einstiegspunkt; importiert alle Module
│ ├── exceptions.lyx ← IDT (256 Gates), CPU-Fault-Routing
│ ├── pmm.lyx ← Physical Memory Manager (Bitmap)
│ ├── vmm.lyx ← Virtual Memory Manager (PML4, Huge Pages)
│ ├── process.lyx ← Prozessmodell, Scheduler-Bootstrap
│ ├── sync.lyx ← Mutex, Semaphor, Spinlock
│ ├── smp.lyx ← SMP, LAPIC, AP-Trampoline
│ ├── ata.lyx ← ATA-Disk-I/O (sektorweise)
│ ├── fat32.lyx ← FAT32-Implementierung (~795 Zeilen)
│ ├── vfs.lyx ← Virtual-Filesystem-Layer
│ ├── keyboard.lyx ← PS/2-Tastatureingabe
│ └── ring3.lyx ← Ring-3-Sprungmodul (ELF-Loader + Syscall-Gate)
├── shell/
│ └── shell.lyx ← Minimale Ring-3-Shell
└── doku/
├── syscalls.md ← Syscall-ABI-Spezifikation
└── fahrplan.md ← Meilenstein-Plan
2. Build-Ablauf
bash bootloader/build.sh
Das Skript führt vier Phasen aus:
- Bootloader assemblieren —
nasm -f bin boot.asm -o BOOTX64.EFI - Kernel kompilieren —
lyxc –target=lyxosfür jeden.lyx-Kernel-File, dann Linken zukernel.elf - Disk-Image aufbauen — 128-MB-GPT-Image mit FAT32-EFI-Partition
- ESP befüllen —
BOOTX64.EFInach/EFI/BOOT/,kernel.elfin die Wurzel
Einzelne Module neu kompilieren (ohne Full-Build):
lyxc --target=lyxos kernel/fat32.lyx -o kernel/fat32.lyu
Die .lyu-Dateien sind vorkompilierte Units, die beim Link-Schritt zusammengefasst werden.
3. Kernel-Einstiegspunkt
Der Bootloader springt in pub fn main(boot_info_ptr: int64) in kernel.lyx. Das Argument ist ein Zeiger auf eine BootInfo-Struktur, die der Bootloader vor dem ExitBootServices-Aufruf aufgebaut hat:
// BootInfo-Layout (boot_info_ptr ist der Zeiger)
// Offset 0: mmap_ptr — Zeiger auf UEFI-Memory-Map
// Offset 8: mmap_size — Größe der Memory-Map in Bytes
// Offset 16: desc_size — Größe eines Memory-Deskriptors
// Offset 24: kernel_end — Physische Endadresse des Kernels
// Offset 32: bump_ptr_addr — Adresse des Bump-Allocator-Zeigers
pub fn main(boot_info_ptr: int64): int64 {
var mmap_ptr: int64 := peek64(boot_info_ptr);
var mmap_size: int64 := peek64(boot_info_ptr + 8);
var desc_size: int64 := peek64(boot_info_ptr + 16);
var kernel_end: int64 := peek64(boot_info_ptr + 24);
var bump_ptr_addr: int64 := peek64(boot_info_ptr + 32);
// ...
}
4. Initialisierungsreihenfolge
Die Reihenfolge ist durch Abhängigkeiten erzwungen — sie darf nicht geändert werden ohne die Konsequenzen zu prüfen:
1. PMM — Bitmap-Allocator für physische Pages (braucht UEFI-Memory-Map)
2. VMM — PML4-Page-Tables, CR3 laden (braucht PMM für Page-Allokationen)
3. RNG-Seed — rdtsc() → RandomSeed() (braucht VMM für mmap)
4. Exceptions — IDT mit 256 Gates laden (braucht funktionierende Page-Tables)
5. SMP — LAPIC init, AP-Trampoline, INIT+SIPI senden
6. ATA — ATA-Controller initialisieren (PIO-Modus)
7. FAT32 — BPB lesen, Root-Cluster bestimmen
8. VFS — Virtuellen Filesystem-Layer aufbauen, FAT32 als Root mounten
9. Keyboard — PS/2-Controller initialisieren
10. Scheduler — ProcInit(), ProcActivate() → ab hier preemptiv (100 Hz)
11. Ring-3 — ring3.SysSpawn("shell.elf") → Userspace gestartet
Wichtig für Beitragende: Module dürfenmmap,peek64,poke64undPrintLnerst nach VMM-Init aufrufen. VorVfsInit()darf kein Code auf Dateien zugreifen.
5. Module: Aufgaben und Schnittstellen
PMM (pmm.lyx)
Bitmap-basierter Physical Memory Manager. 4 GB Adressraum, 4-KB-Pages.
PmmInit(mmap_ptr, mmap_size, desc_size, kernel_end); // UEFI-Map parsen
var page: int64 := PmmAllocPage(); // Eine Page allozieren
PmmFreePage(page); // Page zurückgeben
var free: int64 := PmmFreeCount(); // Anzahl freier Pages
Physische Adressen die von UEFI als EfiConventionalMemory markiert sind, werden als frei eingetragen. Kernel-Code und Bootloader-Bereich werden explizit reserviert.
VMM (vmm.lyx)
Verwaltet die PML4-Page-Table-Hierarchie. Alle 4 GB werden als Identity-Map (1:1 physisch ↔ virtuell) mit 2-MB-Huge-Pages eingetragen.
VmmInit(); // PML4 aufbauen und CR3 laden
var cr3: int64 := VmmGetCr3(); // Aktuellen CR3 lesen
Zukünftig (M5): 4-KB-Seiten für Ring-3 mit separaten Page-Tables pro Prozess.
Exceptions (exceptions.lyx)
IDT mit 256 Gates. CPU-Exceptions werden auf panic() oder Fault-Handler weitergeleitet.
ExceptionsInit(); // Wird implizit von VMM aufgerufen; kein manueller Aufruf nötig
SMP (smp.lyx)
LAPIC-Init, AP-Trampoline bei physisch 0x8000, INIT+SIPI-Sequenz.
var ncpu: int64 := GetCpuCount(); // CPUID → Anzahl logischer CPUs
var ap_stack: int64 := mmap(0, 8192, 3, 34, -1, 0);
SmpPrepareTrampoline(ap_stack + 8192); // Trampoline schreiben
SmpStartAp(1); // SIPI an LAPIC-ID 1
var ready: int64 := GetApStartedCount(); // Wieviele APs sind hochgefahren?
Sync (sync.lyx)
Kernel-seitige Synchronisationsprimitive für Ring-0-Code.
var mx: int64 := MutexCreate(MUTEX_PLAIN);
MutexLock(mx);
// kritischer Abschnitt
MutexUnlock(mx);
var sem: int64 := SemCreate(1, 0);
SemWait(sem);
SemPost(sem);
var sl: int64 := GetPrintSpinlockAddr();
SpinlockAcquire(sl);
SpinlockRelease(sl);
ATA (ata.lyx)
PIO-Mode-Disk-I/O. Sektorweises Lesen und Schreiben. Kein DMA in M1-M4.
// ATA wird implizit von VfsMountFat32 genutzt
// Direktzugriff (für Kernel-Code):
AtaRead(lba, sector_count, buf_addr);
AtaWrite(lba, sector_count, buf_addr);
FAT32 (fat32.lyx)
Vollständige FAT32-Implementierung. Cluster-Ketten, BPB-Parsing, Verzeichnis-Einträge (inkl. LFN-Unterstützung).
var root_cluster: int64 := Fat32GetRootCluster();
var n: int64 := Fat32ListDir(root_cluster); // Root-Verzeichnis auflisten
VFS (vfs.lyx)
Abstraktionsschicht über FAT32. Exportiert das Linux-ähnliche open/read/write/close-Interface für Kernel-Code.
VfsInit();
VfsMountFat32(2048); // Partition ab LBA 2048 mounten
var fd: int64 := VfsOpen(AT_CWD, "shell.elf", O_READ);
var n: int64 := VfsRead(fd, buf, 4096);
VfsClose(fd);
// Weitere Operationen:
VfsWrite(fd, buf, n);
VfsStat(AT_CWD, "test.txt", statbuf);
VfsMkdir(AT_CWD, "mydir");
VfsRename(AT_CWD, "old.txt", AT_CWD, "new.txt");
VfsUnlink(AT_CWD, "old.txt", 0);
var dfd: int64 := VfsOpenDir(AT_CWD, ".");
VfsReadDir(dfd, dbuf, 20);
Ring-3 (ring3.lyx)
ELF64-Loader und Userspace-Sprungmodul. Lädt ein ELF-Binary aus dem VFS, setzt Ring-3-Page-Tables auf und springt in main().
var exit_code: int64 := ring3.SysSpawn("shell.elf");
6. Neues Kernel-Modul hinzufügen
- Neue Datei
kernel/meinmodul.lyxanlegen import meinmodul;am Anfang vonkernel.lyxeintragen- Initialisierungsfunktion (z.B.
MeinModulInit()) an der richtigen Stelle inkernel.lyxaufrufen - Im Build-Skript
build.shden Compile-Schritt für das neue Modul ergänzen:
lyxc --target=lyxos kernel/meinmodul.lyx -o kernel/meinmodul.lyu
Konventionen für Kernel-Module:
- Alle exportierten Funktionen beginnen mit dem Modul-Präfix (z.B.
PmmAllocPage,VfsOpen) - Kein Ring-3-Syscall-Interface direkt im Modul — Syscall-Handler werden in
ring3.lyxregistriert PrintLn/PrintStrfür Debug-Ausgaben; nicht im Release-Build verwenden wenn möglich- Kein globaler State außerhalb des Moduls; alle Zustandsvariablen als modul-lokale
vardeklarieren
7. Debugging-Techniken
Serielle Ausgabe
PrintLn, PrintStr und PrintInt schreiben sowohl auf COM1 (serielle Konsole) als auch auf den QEMU-Debug-Port (0xE9 / debugcon). Beides erscheint im Terminal wenn run.sh mit –headless gestartet wird.
bash bootloader/run.sh --headless 2>&1 | tee kernel_log.txt
Debug-Konsole
tail -f /tmp/lyx_debugcon.txt
QEMU-Monitor
Im laufenden QEMU Ctrl-Alt-2 drücken:
info registers # CPU-Registerstand aller VCPUs
info mem # Page-Table-Dump
x/10i $rip # Disassembly ab aktuellem Instruction Pointer
xp /10gx 0x200000 # Physischer Speicher-Dump ab Kernel-Basis
Kernel-Panic und assert
// In Kernel-Code:
assert(condition, "Fehlermeldung"); // Hält die Maschine an wenn condition false
panic("Unbekannter Zustand"); // Sofortiger Halt mit Debug-Ausgabe
Beim Panic wird der Registerstand auf COM1 ausgegeben, bevor die CPU in eine Endlosschleife geht (hlt-Loop). QEMU-Monitor zeigt dann den letzten Stand.
GDB über QEMU
# run.sh mit GDB-Server starten
bash bootloader/run.sh --gdb # wartet auf Port 1234
# In zweitem Terminal:
gdb kernel/kernel.elf
(gdb) target remote :1234
(gdb) break PmmAllocPage
(gdb) continue
8. Prozessmodell (M1–M4)
Im aktuellen Stand (M1-M4) ist das Prozessmodell minimal:
ProcInit()initialisiert den 100-Hz-Preemptiv-SchedulerProcCreate(fn_idx)erzeugt einen Kernel-Thread (Ring-0, nicht Ring-3)ProcActivate()startet den Scheduler; ab hier ist der Kernel preemptivTaskSpawn(fn_idx, arg)erzeugt einen leichtgewichtigen Task im Kernel-KontextTaskAwait(task_id)wartet auf Task-Abschluss und gibt den Rückgabewert zurückTaskRunPending()führt auf Single-Core alle ausstehenden Tasks synchron aus (BSP-Fallback)
Ab M5: vollständige Ring-3-Prozesse mit separaten Adressräumen, sys_spawn-Syscall und ELF-Loader über VFS.
9. Meilenstein-Übersicht
| Meilenstein | Inhalt | Status |
|---|---|---|
| M1 — Boot & Bare-Metal | UEFI-Bootloader, ELF-Loader | ✅ |
| M2 — Kernel-Kern | PMM, VMM, IDT, SMP | ✅ |
| M3 — Runtime & Scheduler | Laufzeit, Mutex, Semaphor, Prozessmodell | ✅ |
| M4 — I/O | ATA, FAT32, VFS, Keyboard, Ring-3-Shell | ✅ |
| M5 — Ring-3 & Shell | Vollständige Userspace-Shell, sys_spawn, ELF-Loader | Offen |
| M6 — Netzwerk | TCP/IP-Stack, Syscall-Gruppe 0x0600 | Offen |
| M7 — Lyra-Agent | Kernel-KI-Subsystem (kernel/ai.lyx), Lyra-Basisschicht | Offen |
| M8–M10 | Semantic OS Layer, Aerospace Safety, Distribution | Offen |
Der vollständige Fahrplan mit Work-Package-Details liegt unter doku/fahrplan.md im Repository.
Letzte Aktualisierung: 2026-06-09
