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:
- Ring-3 schreibt Syscall-Nummer + Argumente in den r3_sc_block
- Ring-3 ruft
lyx_trigger()auf (mmap(0,-6,…)) - Der Kernel erhält Kontrolle, liest Block, führt Aktion aus, schreibt Ergebnis in
r3sc[40] - 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
