Lyx OS – Interaktive Shell

Diese Seite dokumentiert die Lyx OS Shell (shell/shell.lyx, WP13): Built-in-Befehle, das r3_sc_block-Kommunikationsprotokoll und die Implementierungsdetails für Entwickler, die die Shell erweitern oder eigene Ring-3-Anwendungen schreiben möchten.

Prozesse & Ring-3 · Architektur · Kernel-Interna


1. Überblick

Die Shell ist der erste Ring-3-Prozess in Lyx OS. Sie wird von SysSpawn(„shell.elf“, r3sc_addr) gestartet und läuft in einem eigenen Adressraum (separates PML4, SHELL_VMA_BASE = 0x400000).

Start-Ausgabe:

Lyx Shell v0.1 - Ring-3 active
Type 'help' for available commands.
lyx>

Der Prompt ist lyx> . Die Shell liest Zeichen einzeln über sys_read(0, buf, 1) (stdin = Tastatur-fd) und puffert eine Zeile (max. 63 Zeichen). Backspace/Delete (Byte 8 oder 127) löscht das letzte Zeichen inklusive Terminal-Echo.


2. Built-in-Befehle

Befehl Syntax Beschreibung
help help Befehlsübersicht ausgeben
exit exit Shell beenden (gibt 0 zurück)
clear clear Terminal-Screen leeren (ANSI ESC[2J ESC[H)
pwd pwd Aktuelles Arbeitsverzeichnis ausgeben
cd cd <verz> Verzeichnis wechseln
ls ls / ls <pfad> Verzeichnis-Inhalt auflisten
cat cat <datei> Datei-Inhalt ausgeben (63-Byte-Chunks)
echo echo [text] Text ausgeben
locale locale Aktuelle Locale-Einstellungen anzeigen
diskinfo diskinfo Erkannte ATA-Laufwerke auflisten
part part <disk> <lba-start> <lba-end> MBR-Partition anlegen
mkfat32 mkfat32 <disk> <lba-start> <anzahl-sektoren> FAT32 formatieren
mount mount <vol-id> <disk> <lba-start> Disk als Volume mounten
vol vol <vol-id> Aktives Volume wechseln (CWD → /)

Beispiele:

lyx> ls
[DIR] .
[DIR] testdir
[FILE] kernel.elf
[FILE] shell.elf

lyx> cat kernel.elf
(Binärdaten...)

lyx> cd testdir
lyx> pwd
/testdir

lyx> diskinfo
  [0] Primary Master    128 MB  (262144 sectors)
  [1] Primary Slave     256 MB  (524288 sectors)

lyx> part 1 2048 524287
Partitioning disk 1 LBA 2048..524287 (522240 sectors)... OK

lyx> mkfat32 1 2048 522240
Format disk 1 LBA 2048 sz=522240... OK

lyx> mount 1 1 2048
Mount disk 1 LBA 2048 as vol 1... OK

lyx> vol 1
Switch to vol 1... OK

lyx> ls
(Dateien von Disk 1, Partition 1)

lyx> locale
locale=de_DE keyboard=de ...


3. Syscall-Protokoll (r3_sc_block)

Die Shell kommuniziert mit dem Kernel nicht über klassische SYSCALL-Register-Konventionen, sondern über einen gemeinsamen Speicherblock: den r3_sc_block. Dieser Block ist physisch adressiert und in der Prozess-Page-Table als user-zugänglich gemappt.

Ablauf für jeden Syscall aus Ring-3:

1. Shell schreibt Syscall-Nr + Argumente via poke64 in den r3_sc_block
2. Shell ruft lyx_trigger() auf → mmap(0, -6, ...)
3. Kernel (handle_r3_syscall) erhält Kontrolle:
     - liest r3sc[NR], r3sc[A0], r3sc[A1], r3sc[A2]
     - führt Aktion aus (VfsRead, VfsOpen, ...)
     - schreibt Ergebnis nach r3sc[RESULT]
4. Kernel gibt Kontrolle via IRETQ zurück (vmm_op33)
5. Shell liest Ergebnis via lyx_trigger()-Rückgabewert

Implementierung in shell.lyx:

// Einmalig beim Start: Blockadresse holen
g_r3sc := lyx_get_r3sc();   // mmap(0, -5, ...) → &r3_sc_block

// Syscall-Wrapper-Muster:
fn lyx_read(fd: int64, buf: int64, count: int64): int64 {
    poke64(g_r3sc + R3_OFF_NR, 0);    // nr = 0 (sys_read)
    poke64(g_r3sc + R3_OFF_A0, fd);
    poke64(g_r3sc + R3_OFF_A1, buf);
    poke64(g_r3sc + R3_OFF_A2, count);
    return lyx_trigger();              // Ergebnis = r3sc[RESULT]
}

Sentinel-Werte:

  • mmap(0, -5, …) → Kernel gibt physische Adresse des r3_sc_block zurück
  • mmap(0, -6, …) → Kernel führt handle_r3_syscall aus, gibt result zurück
  • Der Bootloader (boot.asm) fängt rsi < 0 in vmm_op ab; −5 und −6 sind spezielle Operationscodes

4. Puffer-Layout

Die Shell alloziert ihre globalen Puffer beim Start via mmap (user_mmap, Syscall 9):

Variable Größe Zweck
g_kbuf 8 B Einzelzeichen-Lese-Puffer (sys_read 1 Byte)
g_line 64 B Eingabezeile (max. 63 Zeichen + Null)
g_lsbuf 1024 B Verzeichnis-Eintrags-Puffer für ls (21 Einträge × 48 B)
g_catbuf 64 B Chunk-Puffer für cat (63 Byte pro Leseaufruf)
g_argbuf 33 B Argument-Extraktions-Puffer (max. 32 Zeichen)
g_chbuf 1 B Einzelzeichen-Ausgabe-Puffer für lyx_putchar

Verzeichnis-Eintrag-Format (g_lsbuf, 48 Bytes pro Eintrag):

Offset Feld Beschreibung
0–31 name[32] Dateiname (null-terminiert, max. 31 Zeichen)
32–39 size Dateigröße in Bytes (int64)
40–47 type Typ: 0 = Datei, 1 = Verzeichnis

5. Eigene Syscall-Wrapper schreiben

Neue Ring-3-Operationen folgen immer dem gleichen Muster. Beispiel: ein Wrapper für sys_locale_info (nr=81):

fn lyx_locale(buf: int64): int64 {
    poke64(g_r3sc + R3_OFF_NR, 81);   // Syscall-Nummer
    poke64(g_r3sc + R3_OFF_A0, buf);  // Argument: Puffer-Adresse (user-virtuell)
    return lyx_trigger();              // Ergebnis
}

Wichtig beim Umgang mit Puffer-Adressen:

  • Adressen in Ring-3 sind user-virtuelle Adressen
  • Der Kernel übersetzt sie intern via VmmPhysFromUserVirt(proc_cr3, vaddr) in physische Adressen
  • Deshalb müssen Puffer via mmap alloziert werden (damit sie in der Prozess-Page-Table stehen) — kein direktes Pointer-Casting auf Stack-Adressen

6. Bekannte Compiler-Einschränkungen (lyxc 0.9.7A)

  • Globale pchar-Literal-Initialisierer werden nicht emittiert. g_argbuf und g_chbuf müssen in main() manuell initialisiert werden:

g_argbuf := "________________________________a";
g_chbuf  := "x";

  • Negative Literale in mmap: mmap(0, -6, …) muss über eine lokale Variable übergeben werden, da der Compiler negative Literale als drittes Argument falsch übergibt. Die Shell nutzt var s: int64 := -6; mmap(0, s, …).
  • Direkte SYSCALL rax=1 (write): Schreibt auf user-Stack und korrumpiert Register. Die Shell umgeht dies, indem alle Ausgaben über lyx_putchar (Syscall nr=1 via r3_sc_block + lyx_trigger) laufen.

Letzte Aktualisierung: 2026-06-13