Inhaltsverzeichnis

Lyx OS – Prozesse & Ring-3

Diese Seite beschreibt, wie Ring-3-Prozesse in Lyx OS gestartet werden, wie sie mit dem Kernel kommunizieren und wie das Capability-System mit PLEDGE_*-Bitmasks die Berechtigungen einschränkt. Implementierung: kernel/ring3.lyx (WP12 + WP14).

Architektur · Shell · Syscall-ABI · Kernel-Interna


1. Überblick

Lyx OS kennt genau einen Mechanismus, um ein Programm zu starten: SysSpawn. Es gibt kein fork(), kein exec() und keine Copy-on-Write-Semantik. Jeder Prozess beginnt mit einem sauberen, neu angelegten Adressraum.

Prozess-Lebenszyklus:

Kernel-Start
    │
    └──► SysSpawn("shell.elf", r3sc_addr)
             │
             ├── ELF aus VFS öffnen + PT_LOAD-Segmente nach SHELL_PHYS_BASE laden
             ├── User-Stack anlegen (16 KB, physisch aus Bump-Allokator)
             ├── Per-Prozess PML4 anlegen (VmmAllocProcessPML4)
             ├── r3_sc_block-Seite in Prozess-Page-Table mappen
             ├── r3_launch(entry, stack_top, proc_pml4) → Ring-3
             │
             │   Ring-3 läuft (handle_r3_syscall bei jedem Syscall)
             │
             └── Shell ruft sys_exit → SysSpawn gibt Exit-Code zurück


2. Speicherlayout (Ring-3-Prozess)

Adressbereich Inhalt Anmerkung
Phys. 0x1800000 (24 MB) ELF-Binary (SHELL_PHYS_BASE) PT_LOAD-Segmente 1:1 geladen
Virt. 0x400000 (4 MB) SHELL_VMA_BASE — Linker-Basis des ELF phys = SHELL_PHYS_BASE + (vaddr − VMA_BASE)
Stack 16 KB (USER_STACK_SIZE = 0x4000) Physisch aus Bump-Allokator; nach unten wachsend
Virt. 0x10000000 (256 MB) USER_HEAP_BASE — Heap-Basis für user_mmap Wächst aufwärts; physisch via PmmAllocPage

Der Prozess bekommt ein eigenes PML4 (proc_pml4), das nur den ELF-Bereich, den Stack und den r3_sc_block mappt. Der restliche physische Speicher ist nicht sichtbar.


3. SysSpawn — ELF-Loader

pub fn SysSpawn(path: pchar, r3sc: int64): int64 {
    // ELF öffnen und Header lesen
    // PT_LOAD-Segmente laden: phys_dest = SHELL_PHYS_BASE + (p_vaddr − SHELL_VMA_BASE)
    // User-Stack anlegen (mmap, USER_STACK_SIZE Bytes)
    // VmmSetUserHeapVirt(USER_HEAP_BASE)  → Heap-Pointer zurücksetzen
    // proc_pml4 = VmmAllocProcessPML4(ELF_base, elf_size, stack_phys, stack_size)
    // r3_sc_block-Seite user-zugänglich mappen
    // r3_launch(phys_entry, user_stack_top, proc_pml4)  → Ring-3 starten
    // Syscall-Loop: while ret == -2 { ret = handle_r3_syscall(r3sc) }
    // return r3_exit_code()
}

Was passiert bei jedem Ring-3-Syscall:

  1. Ring-3 schreibt Syscall-Nummer + Argumente in den r3_sc_block
  2. Ring-3 ruft lyx_trigger() auf (mmap(0,-6,…))
  3. Der Kernel erhält Kontrolle, liest Block, führt Aktion aus, schreibt Ergebnis in r3sc[40]
  4. Ring-3 liest Ergebnis aus r3sc[40]

Bekannter lyxc-0.9.7A-Bug: Globale pchar-Variablen mit Literal-Initialisierer werden nicht korrekt emittiert. SysSpawn setzt daher cwd_path_buf und open_path_buf manuell auf temporäre Strings, bevor es sie nutzt.


4. r3_sc_block-Protokoll

Der r3_sc_block ist ein physisch adressierter 48-Byte-Speicherblock, den der Bootloader anlegt und in BootInfo[5] (Offset 40) an den Kernel übergibt.

Offset Feld Typ Bedeutung
0 nr int64 Syscall-Nummer
8 a0 int64 Argument 0
16 a1 int64 Argument 1
24 a2 int64 Argument 2
32 a3 int64 Argument 3
40 result int64 Rückgabewert (Kernel schreibt; Ring-3 liest)

Aus Ring-3-Sicht (shell.lyx):

fn lyx_get_r3sc(): int64 { return mmap(0, -5, 0, 0, 0, 0); }   // Blockadresse holen
fn lyx_trigger(): void   { mmap(0, -6, 0, 0, 0, 0); }           // Syscall auslösen

// Beispiel: sys_read(fd=0, buf, 1)
var sc: int64 := lyx_get_r3sc();   // einmalig
poke64(sc, 0);         // nr = 0 (sys_read)
poke64(sc + 8, 0);     // a0 = fd (0 = stdin)
poke64(sc + 16, buf);  // a1 = Puffer-Adresse
poke64(sc + 24, 1);    // a2 = count
lyx_trigger();
var result: int64 := peek64(sc + 40);

Adress-Übersetzung: User-virtuelle Puffer-Adressen (aus Ring-3-Sicht) sind nicht identisch mit physischen Adressen. Der Kernel übersetzt sie via VmmPhysFromUserVirt(proc_cr3, vaddr) bevor er sie an VFS-Funktionen weitergibt.


5. Syscall-Tabelle (Ring-3)

Der Syscall-Handler handle_r3_syscall in ring3.lyx kennt folgende Nummern:

nr Name Argumente Beschreibung
0 sys_read fd, buf, count Lesen von stdin (fd=0) oder VFS-fd
1 lyx_putchar char, 1 Zeichen auf COM1/FB ausgeben
2 sys_open path, flags, mode Datei oder Verzeichnis öffnen
3 sys_close fd Datei-Deskriptor schließen
9 user_mmap 0, size, … Userspace-Heap allozieren
79 sys_getcwd buf, size Aktuelles Arbeitsverzeichnis
80 sys_chdir path Verzeichnis wechseln
81 sys_locale_info buf Locale-Zusammenfassung lesen
82 sys_diskinfo ATA-Disk-Übersicht ausgeben
83 sys_mkpart disk_id, lba, sectors MBR-Partition anlegen
84 sys_mkfat32 disk_id, lba, sectors FAT32-Partition formatieren
85 sys_mount vol_id, disk_id, lba Disk als Volume mounten
86 sys_vol vol_id Aktives Volume wechseln

FD-Offset: Ring-3-FDs sind VFS-FDs + 3. FD 0 aus Ring-3-Sicht → Tastatur (stdin). FD 1 aus Ring-3-Sicht → VFS-FD 0 (die erste vom Kernel geöffnete Datei).


6. Capability-System (PLEDGE_*)

Capabilities werden als Bitmask pro Prozess verwaltet. SysPledge kann die Maske nur einschränken, nie erweitern — ein Prozess kann sich irreversibel in einen minimal-privilegierten Zustand versetzen.

pub con PLEDGE_STDIO:  int64 := 1;   // sys_read (stdin), lyx_putchar
pub con PLEDGE_VFS:    int64 := 2;   // sys_open, sys_close, sys_getcwd, sys_chdir
pub con PLEDGE_EXEC:   int64 := 4;   // sys_spawn (weiteren Prozess starten)
pub con PLEDGE_BLOCK:  int64 := 8;   // Raw-Block-Device-Zugriff
pub con PLEDGE_NET:    int64 := 16;  // Netzwerk-Sockets
pub con PLEDGE_PROC:   int64 := 32;  // Prozess-Management
pub con PLEDGE_ALL:    int64 := 63;  // alle Capabilities (Initial-Zustand)

// Capabilities eines Prozesses dauerhaft einschränken:
SysPledge(PLEDGE_STDIO | PLEDGE_VFS);
// Ab jetzt: sys_spawn → ERR_CAPVIOL (WP15)

Capability Routing Gate (syscall_capability_class in ring3.lyx): Jede Syscall-Nummer ist einer Capability-Klasse zugeordnet. Der Handler prüft bei jedem Syscall, ob die benötigte Klasse in pledge_mask des Prozesses enthalten ist. Verstoß → ERR_CAPVIOL (Enforcement ab WP15 implementiert).

Syscall Benötigte Capability
sys_read (fd=0, stdin) PLEDGE_STDIO
sys_open, sys_close, sys_getcwd, sys_chdir PLEDGE_VFS
user_mmap, lyx_putchar, sys_locale_info — (immer erlaubt)

7. Per-Prozess Adressraum

Jeder Ring-3-Prozess bekommt ein eigenes PML4. Die Kernel-Identity-Map ist im Prozess-PML4 nicht sichtbar — Ring-3 kann nicht direkt auf Kernel-Speicher zugreifen.

// Kernel-Seite (SysSpawn):
var proc_pml4: int64 := VmmAllocProcessPML4(
    SHELL_PHYS_BASE, elf_sz_pages,      // ELF-Bereich
    stack_phys, USER_STACK_SIZE         // User-Stack
);

// r3_sc_block-Seite user-zugänglich mappen:
var r3sc_page: int64 := r3sc & (~0xFFF);
VmmMapUserPage(proc_pml4, r3sc_page, r3sc_page);

// user_mmap-Syscall (nr=9): neue Pages in Prozess-PML4 eintragen:
var phys: int64 := VmmPmmAllocPage();
VmmMapUserPage(proc_cr3, virt_base, phys);
VmmSetUserHeapVirt(virt_base + 4096);

Letzte Aktualisierung: 2026-06-13