Diese Seite richtet sich an Entwickler, die Programme für Lyx OS schreiben möchten. Voraussetzung: Grundkenntnisse der Lyx-Sprache und ein laufendes LyxOS-System (→ Erste Schritte).
→ Syscall-Referenz · Architektur · Übersicht
Jedes Lyx-Programm für LyxOS wird mit dem Flag –target=lyxos kompiliert. Dieses Target unterscheidet sich wesentlich vom Standard-Linux-Target:
| Eigenschaft | Linux-Target (default) | LyxOS-Target (–target=lyxos) |
|---|---|---|
| Laufzeitbibliothek | libc (glibc / musl) | lyxrt_lyxos.lyu (keine libc) |
Ausgabe PrintLn(s) | write(1, …) via libc | sys_write(FD_STDOUT, …) direkt |
Speicher mmap(…) | mmap() via libc | sys_mmap(…) direkt |
| Binärformat | ELF64 (Linux ABI) | nativer LYX!-Container (Magic 4C 59 58 21), vom LX-34-Loader geladen; ELF wird nur noch mitgeladen |
| Einstiegspunkt | main() via libc-Initialisierung | main() direkt (kein CRT0) |
| Syscall-Nummern | Linux-Tabelle | LyxOS-Tabelle (doku/syscalls.md) |
# Kompilieren für LyxOS
lyxc --target=lyxos meinprogramm.lyx -o meinprogramm.elf
Das Resultat ist ein LYX!-Container ohne externe Shared-Library-Abhängigkeiten — nachgemessen mit lyxc 1.1.11B. Die Dateiendung .elf in den Beispielen ist Gewohnheit, kein Formathinweis.
Bis in den August 2026 war –target=lyxos ein Ziel, das vieles still falsch machte: Programme übersetzten, liefen an und rechneten daneben. Diese Serie ist mit lyxc 1.1.8A–1.1.11A abgearbeitet. Der Stand, nachgemessen mit 1.1.11B:
| Was | Stand | Vorher |
|---|---|---|
if/else if-Ketten | trägt (1.1.8C) | jede Kette wurde übersprungen, auch der zutreffende Zweig (#1757) |
| Funktionsargumente ab dem 7. | trägt (1.1.8G) | still verworfen, die Funktion rechnete mit dem 6. weiter (#1761) |
con-Konstanten | trägt (1.1.8A) | jede Modulkonstante war 0 (#1751); negative Werte ebenso (#1798) |
new und Interface-Aufrufe | trägt (1.1.9G) | Seitenfehler mit CR2=0x46; Interface-Aufruf kehrte nicht zurück (#1767, #1779, #1787) |
peek64(param + 0) | trägt | lieferte 0, wenn der Versatz ein literales Null auf einem Parameter war (#1784) |
GetArgC() / argv | trägt | _start ignorierte argc/argv, GetArgC() war immer 0 (#1754) |
| Feldzugriff über Modulvariable | trägt | g_obj.feld lieferte Müll (#1812) |
alloc/free, std.io, std.fs | trägt (1.1.4D) | fehlende Builtins (#1715, #1717, #1718) |
Print/PrintLn mit Variable | trägt (1.1.4D) | nur Zeichenketten-Literale (#1716) |
LyxOS kennt kein Server-Socket-Modell. Die Kernel-Aufrufe haben eine andere Gestalt als BSD-Sockets, nicht bloß andere Nummern:
| LyxOS | Bedeutung |
|---|---|
sys_tcp_connect(ip, port) | liefert den Sockel selbst — kein socket() davor |
sys_tcp_send / sys_tcp_recv | flacher Puffer, kein msghdr |
bind / listen / accept | kein Gegenstück |
Angebunden ist das als eigene Builtins in der Gestalt des Kernels (IDs 414–418, #1734), nicht als BSD-Nachbau: ein erfundenes socket(), das ohne den Kernel einen Deskriptor zurückgibt, sähe richtig aus und träfe nichts. Die zehn BSD-Namen melden sich beim Übersetzen und nennen den Weg, den es gibt.
Aus demselben Grund sind die Notification-Queues nicht angebunden (#1810): unsere Form kennt Flags, Nutzlast und Frist, der Kernel keines davon. Eine 1:1-Zuordnung hätte drei Angaben stillschweigend verworfen und Ereignisse ohne Inhalt zugestellt. Stattdessen kommt eine Meldung, die den Grund nennt — dass es die Nummern gibt, heißt nicht, dass sie sich anbinden lassen.
Das einfachste lauffähige LyxOS-Programm:
fn main(): int64 {
PrintLn("Lyx OS — Ring-3 aktiv!");
return 0;
}
PrintLn wird vom Compiler auf sys_write(FD_STDOUT, …) abgebildet. return 0 ruft sys_exit(0) auf. Kein import nötig.
// Einzeiliger String (mit Newline)
PrintLn("Hello LyxOS");
// Ohne Newline
Print("Zähler: ");
PrintLn(IntToStr(42));
// Fehlerausgabe (FD_STDERR)
EPrintLn("Fehler: Ressource nicht verfügbar");
// Formatierte Ausgabe
import std.io;
PrintfSI("User %s hat ID %d", "admin", 1001);
// Zeichen einlesen via sys_read(FD_STDIN, buf, 1)
var buf: [4]uint8 := [];
var n: int64 := 0;
// (n, err) := syscall(SYS_READ, FD_STDIN, ^buf[0] as int64, 1);
Die FAT32-Partition wird als / gemountet. Alle Pfad-Syscalls nehmen einen dir_fd als erstes Argument. AT_CWD = -1 verweist auf das aktuelle Arbeitsverzeichnis.
// Datei öffnen (sys_open, Gruppe 0x0200)
// var fd: int64 := 0;
// var err: int64 := 0;
// (err, fd) := syscall(SYS_OPEN, AT_CWD, "/data/config.txt", O_RDONLY, 0);
// Konventionell über Lyx-Builtins (werden intern gemappt):
import std.fs;
var fd: int64 := open("/data/config.txt", O_RDONLY, 0);
if (fd < 0) {
EPrintLn("Datei nicht gefunden");
return 1;
}
var buf: [512]uint8 := [];
var n: int64 := read(fd, ^buf[0] as pchar, 512);
close(fd);
Pfad-Konventionen:
LyxOS verwendet Forward-Slashes. Die Wurzel des FAT32-Volumes ist/im VFS. Verzeichnisnamen unterscheiden keine Groß-/Kleinschreibung (FAT32-Einschränkung).
Ring-3 erhält Speicher über sys_mmap. Das Lyx-Builtin mmap wird auf diesen Syscall gemappt:
con PROT_READ: int64 := 1;
con PROT_WRITE: int64 := 2;
con MAP_PRIVATE: int64 := 2;
con MAP_ANONYMOUS: int64 := 32;
// 4096 Byte anonymes Mapping
var buf: int64 := mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == 0) {
EPrintLn("mmap fehlgeschlagen");
return 1;
}
poke8(buf, 42); // schreiben
var val: uint8 := peek8(buf); // lesen
munmap(buf, 4096);
Statische Arrays [N]T und lokale Variablen liegen im Stack (kein explizites Freigeben). mmap/munmap für dynamischen Heap-Speicher.
// sys_spawn: neuer Prozess aus ELF-Binary
// Kein fork() — Prozesse werden immer frisch erzeugt
con SYS_SPAWN: int64 := 0x0003;
con AT_CWD: int64 := -1;
// var err, proc_fd := syscall(SYS_SPAWN, AT_CWD, "/bin/editor", argv, envp, 0);
// sys_task_group: Tasks laufen auf freien Cores ohne Programmierer-Eingriff
con SYS_TASK_GROUP_CREATE: int64 := 0x0B00;
con SYS_TASK_GROUP_ADD: int64 := 0x0B01;
con SYS_TASK_GROUP_AWAIT: int64 := 0x0B02;
// var err, group := syscall(SYS_TASK_GROUP_CREATE, 0);
// for i in range 0..8 { syscall(SYS_TASK_GROUP_ADD, group, worker_fn, args[i], 8); }
// syscall(SYS_TASK_GROUP_AWAIT, group, -1);
@parallel-Annotation im Lyx-Code lässt den Compiler dieses Muster automatisch generieren.
Jeder Syscall gibt rax (Fehlercode) und rdx (Nutzwert) zurück. In Lyx –target=lyxos übersetzt der Compiler diese Zwei-Register-Rückgabe in Tupel-Syntax:
// Zwei-Register-Rückgabe als Lyx-Tupel (ab M5 vollständig unterstützt):
// var err, fd := sys_open(AT_CWD, "/config.txt", O_RDONLY, 0);
// if (err != 0) {
// EPrintLn(StrConcat("Fehler: ", IntToStr(err))); // ERR_NOENT = 2 usw.
// return err;
// }
// Bis M5: Fehlercheck via Rückgabewert
var fd: int64 := open("/config.txt", O_RDONLY, 0);
if (fd < 0) {
EPrintLn("open fehlgeschlagen");
return 1;
}
Vollständige Fehlercode-Tabelle: Syscall-Referenz — Fehlercodes
Bis M5 die Shell fertig ist, muss ein Programm manuell in den Kernel-Startvorgang eingebunden werden. Möglichkeit: direkt in kernel.lyx aufrufen.
// kernel.lyx (nach allen Initialisierungen):
import meinprogramm;
pub fn main(boot_info_ptr: int64): int64 {
// ... Kernel-Init ...
return meinprogramm.main();
}
Oder: Binary auf FAT32 kopieren und im Build-Skript hinzufügen:
lyxc --target=lyxos hello.lyx -o hello.elf
mcopy -i bootloader/lyx_boot.img@@1048576 hello.elf ::/hello.elf
Letzte Aktualisierung: 2026-08-27 — Abschnitt 1b ergänzt (Stand des lyxos-Ziels nach der Fix-Serie 1.1.8A–1.1.11A, gegen lyxc 1.1.11B nachgemessen); Binärformat auf den LYX!-Container richtiggestellt; Netzwerk-Abschnitt aus #1734 und #1810.
Vorherige Aktualisierung: 2026-06-09