====== 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. → [[lyxos:architektur|Architektur]] · [[lyxos:syscalls|Syscall-ABI]] · [[lyxos:start|Ü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, per-Prozess CR3) │ ├── process.lyx ← Prozessmodell, Scheduler-Bootstrap │ ├── sync.lyx ← Mutex, Semaphor, Spinlock │ ├── smp.lyx ← SMP, LAPIC, AP-Trampoline, Task-API │ ├── ata.lyx ← ATA-Disk-I/O (Legacy + Multi-Disk) │ ├── disk.lyx ← Disk-Abstraktion (Probing, Modell-String, Sektorzahl) │ ├── mbr.lyx ← MBR-Partition-Tabelle (Lesen/Schreiben) │ ├── fat32.lyx ← FAT32-Implementierung mit LFN-Schreiben │ ├── vfs.lyx ← Virtual-Filesystem-Layer │ ├── keyboard.lyx ← PS/2-Tastatureingabe, Layout-Umschaltung │ ├── initrd.lyx ← In-Memory-Ramdisk (LIXR-Format) │ ├── locale.lyx ← Internationalisierung (5 Locales, /config/locale.cfg) │ ├── xhci.lyx ← XHCI-Treiber (USB 3.x, WP25) │ ├── iofs_alloc.lyx ← IOFS Block-Allocator, mkfs, Page-I/O (WP01+WP09, Syscall 155) │ ├── iofs_rw.lyx ← IOFS Mount/Read/Write/Checkpoint (WP02, Syscalls 156–164) │ ├── iofs_wal.lyx ← IOFS Write-Ahead-Log (WP04, kernel-intern, kein Syscall) │ ├── iofs_graph.lyx ← IOFS Graph/Edges (WP05, Syscalls 165–169) │ ├── iofs_traverse.lyx ← IOFS Graph-Traversal BFS/DFS/Reachable/ShortestPath (WP06, Syscalls 170–173) │ ├── iofs_names.lyx ← IOFS Named Nodes / Name-Store (WP07, Syscalls 174–178) │ ├── ramdisk.lyx ← RAM-Disk-Treiber (IDs 4–7, Syscalls 153–154, WP30) │ ├── net.lyx ← E1000-Treiber, DHCP, ARP, TCP-Stack (WP26, WP16-TCP, Syscalls 180–184) │ └── ring3.lyx ← ELF64-Loader, SysSpawn, Syscall-Handler, Capabilities ├── shell/ │ └── shell.lyx ← Interaktive 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=lyxos'' für jeden ''.lyx''-Kernel-File, dann Linken zu ''kernel.elf'' - **Disk-Image aufbauen** — 128-MB-GPT-Image mit FAT32-EFI-Partition - **ESP befüllen** — ''BOOTX64.EFI'' nach ''/EFI/BOOT/'', ''kernel.elf'' in 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 // Offset 40: r3sc_addr — Physische Adresse des r3_sc_block (Ring-3 ↔ Kernel-Protokoll) 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); var r3sc_addr: int64 := peek64(boot_info_ptr + 40); // ... } ---- ===== 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. SMP — LAPIC init, AP-Trampoline, INIT+SIPI senden 5. VFS — fd-Tabelle anlegen; ruft intern Fat32VolumeInit + MbrInit auf 6. DiskInit — alle 4 ATA-Kanäle proben, Sektorzahl + Modell-String cachen 7. FAT32-Mount — VfsMountFat32(2048): BPB lesen, Root-Cluster bestimmen 8. LocaleInit — /config/locale.cfg lesen, Locale + Tastaturbelegung setzen 9. Initrd-Mount — InitrdBuildEmpty + VfsMountInitrd → /initrd/ mounten 10. Ring-3 — SysSpawn("shell.elf"): ELF laden, per-Prozess CR3, shell ausführen 11. Keyboard — KbdReadChar-Poll nach Shell-Exit (mit Timeout) 12. Sync-Tests — MutexCreate/Lock/Unlock, SemCreate/Wait/Post 13. Scheduler — ProcInit(), ProcActivate() → preemptiv (100 Hz), Endlos-Loop P0 > **Wichtig für Beitragende:** Module dürfen ''mmap'', ''peek64'', ''poke64'' und ''PrintLn'' erst nach VMM-Init aufrufen. Vor ''VfsInit()'' 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. Ab WP14 kommen per-Prozess 4-KB-Page-Tables für Ring-3 hinzu. VmmInit(); // PML4 aufbauen und CR3 laden var cr3: int64 := VmmGetCr3(); // Aktuellen CR3 lesen // WP14: Per-Prozess Address-Space var proc_pml4: int64 := VmmAllocProcessPML4( SHELL_PHYS_BASE, elf_sz_pages, // ELF-Bereich stack_phys, USER_STACK_SIZE); // User-Stack VmmMapUserPage(proc_pml4, virt_addr, phys_addr); // Einzelne 4-KB-Seite mappen VmmSetUserHeapVirt(USER_HEAP_BASE); // Heap-Pointer für user_mmap setzen var virt: int64 := VmmGetUserHeapVirt(); // Heap-Pointer lesen var phys: int64 := VmmPhysFromUserVirt(proc_cr3, vaddr); // Adress-Translation var proc_cr3: int64 := VmmGetCurrentProcCr3(); // Aktiven Prozess-CR3 lesen var page: int64 := VmmPmmAllocPage(); // Physische Page allozieren ==== 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 (512 Bytes/Sektor). Kein DMA. // Legacy (Primary Master, disk_id implizit 0): AtaReadSector(lba, dst_buf); // → vmm_op26 AtaWriteSector(lba, src_buf); // → vmm_op27 // Multi-Disk (disk_id: 0=pri-master 1=pri-slave 2=sec-master 3=sec-slave): var sects: int64 := AtaProbe(disk_id, identify_buf); // IDENTIFY; -1 = nicht vorhanden AtaReadDisk(disk_id, lba, dst_buf); // → vmm_op43; 0 = OK, -1 = Fehler AtaWriteDisk(disk_id, lba, src_buf); // → vmm_op44; inkl. FLUSH CACHE ==== Disk (disk.lyx) ==== Abstraktionsschicht über ATA. Probt beim Start alle 4 ATA-Kanäle und cacht Sektorzahl + Modell-String (byteswapped aus IDENTIFY-Daten extrahiert). DiskInit(); // alle 4 Kanäle proben + cachen var ok: bool := DiskPresent(disk_id); // Disk vorhanden? var n: int64 := DiskSectorCount(disk_id); // Sektorzahl (= Kapazität / 512 B) DiskPrintInfo(); // Auflistung auf COM1/FB ==== MBR (mbr.lyx) ==== Liest und schreibt MBR-Partitions-Einträge (klassische 4-Einträge-Tabelle, Typ 0x0C = FAT32 LBA). Wird von ''VfsMkPart'' und ''VfsMountDisk'' genutzt. MbrInit(); // 512-B-Buffer anlegen (von VfsInit aufgerufen) MbrWritePart(disk_id, part_idx, lba_start, sectors); // Eintrag schreiben (0–3) var out: int64 := mmap(0, 16, 3, 34, -1, 0); MbrReadPart(disk_id, part_idx, out); // lba_start@out+0, sectors@out+8 ==== 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 und Initrd. Exportiert ein posix-artiges ''open/read/write/close''-Interface für Kernel-Code und Ring-3-Syscall-Handler. MAX_FD = 32 gleichzeitig offene Deskriptoren. VfsInit(); // von MbrInit + Fat32VolumeInit aufgerufen VfsMountFat32(2048); // Partition ab LBA 2048 mounten VfsMountInitrd(base, size); // Ramdisk unter /initrd/ mounten VfsMountDisk(vol_id, disk_id, lba_start); // Volume mounten (Multi-Volume) VfsSwitchVolume(vol_id); // Aktives Volume wechseln, CWD → "/" var fd: int64 := VfsOpen(AT_CWD, "datei.txt", O_READ); var n: int64 := VfsRead(fd, buf, 4096); VfsWrite(fd, buf, n); VfsSeek(fd, offset, SEEK_SET); // SEEK_SET=0, SEEK_CUR=1, SEEK_END=2 var fd2: int64 := VfsDup(fd, 0); // fd duplizieren VfsClose(fd); VfsStat(AT_CWD, "test.txt", statbuf); // size@statbuf+0, type@statbuf+8 VfsFstat(fd, statbuf); var t: int64 := VfsFdType(fd); // FD_FILE=1, FD_KBD=2, FD_DIR=3, FD_INITRD=4 VfsSync(fd); // Stub (FAT32 schreibt per Commit) VfsMkdir(AT_CWD, "mydir"); var dfd: int64 := VfsOpenDir(AT_CWD, "."); VfsReadDir(dfd, dbuf, 20); // 48-B-DirEntry-Structs (name[32]+size[8]+type[8]) VfsRename(AT_CWD, "old.txt", AT_CWD, "new.txt"); VfsUnlink(AT_CWD, "old.txt", 0); // UNLINK_DIR=1 für Verzeichnisse VfsGetCwd(buf, size); VfsChdir(AT_CWD, "subdir"); var kbdfd: int64 := VfsOpenKbd(); // Keyboard-fd für IRQ-Binding VfsIrqBind(1, kbdfd); // IRQ 1 (PS/2 Kbd) an fd binden VfsDiskPrintInfo(); // Disk-Übersicht auf COM1/FB VfsMkPart(disk_id, lba_start, sectors); // MBR-Partition anlegen VfsMkFat32(disk_id, lba_start, sectors); // FAT32 formatieren VfsMkFat32ByPart(disk_id, part_no); // FAT32 per Partitionsnummer VfsMountByPart(vol_id, disk_id, part_no); // Mount per Partitionsnummer VfsGptMount(disk_id, vol_id_start); // GPT scannen + alle FAT-Partitionen mounten VfsDiskMap(disk_id); // Partition-Layout mit Lücken (vmm_op41) → Shell-Befehle, die diese Funktionen nutzen: [[lyxos:cmd:diskinfo|diskinfo]] · [[lyxos:cmd:partinfo|partinfo]] · [[lyxos:cmd:part|part]] · [[lyxos:cmd:mkfat32|mkfat32]] · [[lyxos:cmd:mount|mount]] · [[lyxos:cmd:mountp|mountp]] · [[lyxos:cmd:vol|vol]] · [[lyxos:cmd:gpt|gpt]] **FD-Slot-Layout** (48 Bytes pro Eintrag in ''fd_table''): ^ Offset ^ Feld ^ Bedeutung ^ | 0 | type | FD_EMPTY=0, FD_FILE=1, FD_KBD=2, FD_DIR=3, FD_INITRD=4 | | 8 | cluster | Aktueller FAT32-Cluster (oder 0 für Initrd) | | 16 | offset | Byte-Offset innerhalb der Datei | | 24 | size | Dateigröße in Bytes | | 32 | flags | FD_FLAG_CLOEXEC=1, FD_FLAG_APPEND=2 | | 40 | dir_cluster | Start-Cluster des enthaltenden Verzeichnisses | ==== Initrd (initrd.lyx) ==== In-Memory-Ramdisk. Format: ''LIXR''-Magic (4 Bytes) gefolgt von Einträgen (Name + Daten) und einem 4-Byte-Null-Terminator. var base: int64 := InitrdBuildEmpty(); // Minimales 4-KB-Initrd mit Magic + Terminator VfsMountInitrd(base, size); // Mountet unter /initrd/ InitrdFind("dateiname", size_out); // → physische Datenadresse oder -1 InitrdRead("dateiname", buf, offset, count); // Bytes lesen InitrdEntryCount(); // Anzahl Einträge **Initrd-Eintrag-Layout:** [4B name_len uint32 LE, inkl. Null-Terminator] [name_len Bytes Name (inkl. '\0')] [8B data_len uint64] [data_len Bytes Daten] ==== Locale (locale.lyx) ==== Internationalisierung: 5 Locales, Zahlenformat (Tausender-Trennzeichen), Zeitformat (24h oder 12h AM/PM) und Datumsformat (MDY, DMY-Dot, DMY-Slash). Liest ''/config/locale.cfg'' beim Start. LocaleInit(); // /config/locale.cfg lesen + KbdSetLayout aufrufen LocaleFormatInt(1234567, buf); // "1,234,567" (EN) oder "1.234.567" (DE) LocaleFormatTime(14, 5, buf); // "14:05" (24h) oder "2:05 PM" (12h) LocaleFormatDate(12, 6, 2026, buf); // je nach Locale: "06/12/2026" / "12.06.2026" / "12/06/2026" LocaleSummary(buf); // Zusammenfassung als ASCII-String LocalePrintBuf(buf); // Ausgabe auf COM1/FB (char-by-char) ^ Konstante ^ Wert ^ Locale ^ | LOC_EN_US | 0 | Englisch US (MDY, Komma-Tausender, 12h) | | LOC_DE_DE | 1 | Deutsch (DMY-Punkt, Punkt-Tausender, 24h) | | LOC_FR_FR | 2 | Französisch (DMY-Slash, Space-Tausender, 24h) | | LOC_EN_GB | 3 | Englisch GB (DMY-Slash, Komma-Tausender, 24h) | | LOC_ES_ES | 4 | Spanisch (DMY-Slash, Punkt-Tausender, 24h) | ==== Ring-3 (ring3.lyx) ==== ELF64-Loader, Syscall-Handler und Capability Routing Gate. ''SysSpawn'' lädt ein ELF-Binary aus dem VFS, legt per-Prozess-Page-Tables an und startet Ring-3. Der Kernel kommuniziert mit Ring-3 über den ''r3_sc_block'' (physisch adressierter Shared-Memory-Block). // SysSpawn: ELF laden und als Ring-3-Prozess starten var exit_code: int64 := SysSpawn("shell.elf", r3sc_addr); // Capabilities einschränken (Aufruf aus Ring-3 via Syscall 87): SysPledge(PLEDGE_STDIO | PLEDGE_VFS); // irreversibel; kein PLEDGE_EXEC mehr → sys_spawn verboten ==== WP17b: Fenstersystem, Ereignisse und Mauseingabe ==== ''ring3.lyx'' implementiert ab WP17b drei weitere Kernel-Subsysteme: Fenstertabelle, Ereignis-Ring und Mauseingabe. **Fenstertabelle (win_table)** Max. 32 Fenster × 104 Bytes/Slot. Lazy-Allokiert beim ersten ''sys_win_create''-Aufruf aus dem PMM-Bump-Allocator (3 328 Bytes). ^ Offset ^ Feld ^ Bedeutung ^ | 0 | win_id | Monoton steigende ID; 0 = freier Slot | | 8 | owner_pid | PID des erzeugenden Prozesses | | 16 | x | Linke Kante (Pixel) | | 24 | y | Obere Kante (Pixel) | | 32 | w | Breite (Pixel) | | 40 | h | Höhe (Pixel) | | 48 | z_order | Z-Ordnung (höher = weiter vorne) | | 56 | shm_id | (reserviert) | | 64 | flags | Fenster-Flags (derzeit immer 0) | | 72 | state | 0 = frei, 1 = aktiv | | 80 | shm_phys | Physische Basisadresse des Fenster-Framebuffers | | 88 | next_z | Scratch für Z-Order-Berechnung | Framebuffer-Format: 32-bit XRGB, zeilenweise, stride = w × 4 Bytes. ''sys_win_get_fb'' (nr=115) mappt die physischen Seiten in den User-Adressraum des aufrufenden Prozesses. **Ereignis-Ring (ev_table)** Pro-PID-Ringpuffer für asynchrone Ereignisse (Maus, Tastatur, Fensterereignisse). Max. 8 PIDs × 16 Slots × 32 Bytes = 4 096 Bytes. ^ Offset ^ Feld ^ Beschreibung ^ | 0 | type | Ereignistyp | | 8 | a | Parameter A | | 16 | b | Parameter B | | 24 | c | Parameter C (beim Senden via ''sys_event_send'' derzeit immer 0) | **Interne Syscall-Nummern (WP17b)** Diese Nummern beziehen sich auf das ''r3_sc_block''-Protokoll (''nr''-Feld). Sie sind **nicht** identisch mit den öffentlichen Hex-Nummern aus [[lyxos:syscalls|Syscall-ABI]]. ^ Nr ^ Name ^ Argumente (a0, a1, a2, a3) ^ Rückgabe ^ | 110 | ''sys_win_create'' | x, y, w, h | win_id oder −1 | | 111 | ''sys_win_destroy'' | win_id | 0 / −1 | | 112 | ''sys_win_raise'' | win_id | 0 / −1; setzt z_order auf max+1 | | 113 | ''sys_win_move'' | win_id, x, y | 0 / −1 | | 114 | ''sys_win_resize'' | win_id, new_w, new_h | 0 / −1; **nur Metadaten**, kein FB-Realloc | | 115 | ''sys_win_get_fb'' | win_id | user_virt des FB oder −1 | | 121 | ''sys_event_send'' | pid, type, a, b | 0 | | 122 | ''sys_event_recv'' | out_buf_uva | 0 (Ereignis vorhanden) / −1 (Ring leer) | | 126 | ''sys_get_mouse_x'' | — | Maus-X-Koordinate | | 127 | ''sys_get_mouse_y'' | — | Maus-Y-Koordinate | | 128 | ''sys_get_mouse_btn'' | — | Maustasten-Bitmask | | 129 | ''sys_set_mouse_bounds'' | max_x, max_y | 0; einmalig vom Compositor nach ''DisplayInit'' aufgerufen | | 130 | ''sys_win_get_table_phys'' | — | Physische Adresse der win_table (Compositor-Direktzugriff) | | 131 | ''sys_kbd_poll'' | — | Zeichen oder 0 (non-blocking, kehrt sofort zurück) | | 148 | ''sys_win_set_title'' | win_id, title_ptr | 0 / −1; Fenster-Titel setzen (WP27) | | 149 | ''sys_win_find_by_idx'' | idx | win_id oder −1 (WP27) | | 150 | ''sys_win_get_title'' | win_id, buf | 0 / −1; Titel in buf lesen (WP27) | | 151 | ''sys_win_get_geom'' | win_id, out_buf | 0 / −1; out_buf = {x,y,w,h} je int64 (WP29) | | 152 | ''sys_win_get_pid'' | win_id | owner_pid oder −1 (WP29) | Hinweise: * ''sys_win_create'' setzt ''z_order = max_z + 1'' — ein neues Fenster liegt immer zuoberst. ''sys_win_raise'' macht dasselbe explizit. Es gibt kein ''sys_win_lower''. * ''sys_win_resize'' (nr=114) aktualisiert **nur** W/H-Metadaten. Der physische Framebuffer bleibt in der ursprünglichen Größe; nach einem Resize muss ''sys_win_get_fb'' erneut aufgerufen werden. * ''sys_win_get_table_phys'' (nr=130) ist für den Compositor gedacht, der die win_table direkt liest um alle Fenster zu kompositionieren. * ''sys_kbd_poll'' (nr=131) ist nicht-blockierend — gibt 0 zurück wenn kein Zeichen im PS/2-Ring liegt. Gegensatz: ''sys_read(kbd)'' (nr=0) blockiert bis ein Zeichen vorliegt. * Der Ereignis-Ring ist PID-indiziert (max. 8 PIDs); PIDs ≥ 8 werden von ''ev_push''/''ev_pop'' ignoriert. → Vollständige Beschreibung: [[lyxos:prozesse|Prozesse & Ring-3]] ---- ===== 6. Neues Kernel-Modul hinzufügen ===== - Neue Datei ''kernel/meinmodul.lyx'' anlegen - ''import meinmodul;'' am Anfang von ''kernel.lyx'' eintragen - Initialisierungsfunktion (z.B. ''MeinModulInit()'') an der richtigen Stelle in ''kernel.lyx'' aufrufen - Im Build-Skript ''build.sh'' den 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.lyx'' registriert * ''PrintLn'' / ''PrintStr'' für Debug-Ausgaben; nicht im Release-Build verwenden wenn möglich * Kein globaler State außerhalb des Moduls; alle Zustandsvariablen als modul-lokale ''var'' deklarieren ---- ===== 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 ===== **Kernel-Threads (Ring-0):** * ''ProcInit()'' initialisiert den 100-Hz-Preemptiv-Scheduler * ''ProcCreate(fn_idx)'' erzeugt einen Kernel-Thread * ''ProcActivate()'' startet den Scheduler; ab hier preemptiv **SMP-Tasks (leichtgewichtig, Ring-0):** * ''TaskSpawn(fn_idx, arg)'' erzeugt einen Task; max. 16 Slots; 0=square(arg), 1=sum(N) * ''TaskPoll(task_id)'' → 0=FREE, 1=PENDING, 2=RUNNING, 3=DONE * ''TaskAwait(task_id)'' spinnt auf DONE und gibt ''TaskGetResult'' zurück * ''TaskRunPending()'' führt auf Single-Core alle Tasks synchron aus (BSP-Fallback) **Ring-3-Prozesse (Userspace):** * ''SysSpawn(path, r3sc)'' lädt ELF aus VFS, legt per-Prozess CR3 an, startet Ring-3 * Kommunikation über ''r3_sc_block'' (physische Adresse aus BootInfo[5]) * ''SysPledge(mask)'' schränkt Capabilities des laufenden Prozesses ein → Details: [[lyxos:prozesse|Prozesse & Ring-3]] · [[lyxos:smp|SMP & Tasks]] ---- ===== 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 (inkl. LFN-Write), VFS, Keyboard | ✅ | | M5 — Ring-3 & Shell | WP12–WP15 ✅; WP17b ✅; IOFS WP01–WP07+GC ✅; RAM-Disk WP30 ✅; XHCI WP25 ✅ | ✅ Abgeschlossen | | M6 — Netzwerk | TCP-Stack WP16-TCP ✅ (E1000, DHCP, ARP, TCP, Syscalls 180–184) | POSIX-Socket-API offen | | M7 — Lyra-Agent | Lyra-Domäne (Ring-3 privilegierter LLM-Dienst, kein kernel/ai.lyx) | 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-25