====== Nebenläufigkeit auf Embedded-Targets & RTOS ====== Auf Linux und macOS stellt ''std.thread'' POSIX-Threads bereit. Auf **Bare-Metal-Targets** (ARM Cortex-M, RISC-V ohne Betriebssystem) und **RTOS-Umgebungen** (FreeRTOS auf ESP32) gelten andere Regeln. Dieses Dokument beschreibt die Muster und Werkzeuge, die Lyx für diese Umgebungen bereitstellt. → POSIX-Threads (Linux/macOS): [[lyx_-_programmiersprache:sprache:threads|Threads & Nebenläufigkeit]]\\ → Hardware-Pointer und @volatile: [[lyx_-_programmiersprache:sprache:pointer-inlining|Low-Level: Speicherzugriff & Pragmas]]\\ → Safety-Pragmas: [[lyx_-_programmiersprache:sprache:attributes-pragmas|Attribute & Pragmas]] · [[lyx_-_programmiersprache:guides:do-178c|DO-178C Compliance]] ---- ===== 1. POSIX-Threads vs. Bare-Metal ===== ^ Kriterium ^ std.thread (POSIX) ^ Bare-Metal ohne OS ^ RTOS (FreeRTOS) ^ | Zielplattform | Linux, macOS, BSD | ARM Cortex-M, RISC-V | ESP32, STM32 mit FreeRTOS | | Lyx-Target | ''x86_64'', ''arm64'' | ''arm_cm'', ''riscv'' | ''xtensa'' | | Scheduler | Betriebssystem | Kein Scheduler — manuell | FreeRTOS-Scheduler (preemptiv) | | Kontextwechsel | OS-Kernel | SysTick-ISR oder kooperativ | FreeRTOS-Tick | | Mutex / Sync | ''MutexLock'', ''CondWait'' | Interrupts sperren | FreeRTOS-Queue, Semaphore | | Heap-Allokation in ISR | Erlaubt (aber riskant) | Verboten | Verboten | | ''std.thread'' nutzbar | ✅ | ✗ (kein pthread) | ✗ (andere ABI) | > **Faustregel:** ''std.thread'' setzt einen POSIX-kompatiblen Kernel voraus. Auf Bare-Metal und in RTOS-Tasks wird Nebenläufigkeit durch Interrupt-Service-Routinen, kooperative Zustandsautomaten und RTOS-Primitiven umgesetzt. ---- ===== 2. Superloop — kooperatives Multitasking ===== Das einfachste Nebenläufigkeitsmodell für eingebettete Systeme ohne RTOS ist der **Superloop**: eine Endlosschleife, die mehrere Aufgaben reihum aufruft. Jede Aufgabe muss schnell enden (nicht blockieren), damit die anderen nicht verhungern. unit superloop; import std.io; // Zustandsvariablen für jede Task var sensor_ready: bool := false; var display_dirty: bool := false; var last_tick: int64 := 0; // Task 1: Sensor lesen (alle 10 ms) fn TaskSensor(now_ms: int64) { if (now_ms - last_tick < 10) { return; } last_tick := now_ms; // ... Sensor abfragen ... sensor_ready := true; } // Task 2: Display aktualisieren (wenn Daten neu) fn TaskDisplay() { if (sensor_ready == false) { return; } // ... Display schreiben ... sensor_ready := false; display_dirty := false; } // Task 3: Watchdog zurücksetzen // (@volatile gilt fuer VARIABLEN; der Registerzugriff selbst laeuft ueber // poke32 und wird nicht wegoptimiert.) fn TaskWatchdog(wdt_base: int64) { var feed: int64 := (wdt_base + 0x04); poke32(feed, 0xFEED0000); // WDT-Feed-Sequenz } fn main(): int64 { con WDT_BASE: int64 := 0x6001_5000; var now: int64 := 0; while (true) { now := now + 1; // Vereinfacht — in Praxis: GetTick() TaskSensor(now); TaskDisplay(); TaskWatchdog(WDT_BASE); } return 0; } ==== Tick-Zähler aus dem Hardware-Timer ==== Für genaues Timing liest der Superloop einen Hardware-Tick-Zähler: @volatile fn GetSysTickMs(): int64 { // ARM Cortex-M: SysTick Current Value Register con SYST_CVR: int64 := 0xE000E018; return peek32(SYST_CVR) as int64; } ---- ===== 3. ISR — Interrupt Service Routinen ===== Eine ISR ist eine Funktion, die der Hardware-Interrupt-Controller aufruft, wenn ein Ereignis eintritt (Timer-Tick, UART-Byte empfangen, GPIO-Flanke). Sie läuft außerhalb des normalen Kontrollflusses — asynchron zur Hauptschleife. ==== ISR in Lyx deklarieren ==== > **Die Platzierung im Interrupt-Vektor ist derzeit nicht möglich.** Die Attribute ''@section("name")'' und ''@no_opt'' gibt es nicht — der Compiler weist beide mit ''unbekanntes Attribut'' ab. Das ist seit [[https://github.com/SEOLizer/LyX-Compiler/issues/1099|#1099]] (erledigt) **gewolltes** Verhalten: erfundene Attributnamen werden abgewiesen statt stillschweigend angenommen. Ein Issue für die beiden Attribute selbst gibt es bislang nicht. Ohne sie lässt sich eine Funktion keinem ELF-Abschnitt zuordnen, und der Interrupt-Vektor braucht genau das. > Die Handler selbst sind gewöhnliche Funktionen und übersetzen; die Verdrahtung mit dem Vektor muss bis auf Weiteres über ein externes Assembler-Stub oder das Linker-Skript erfolgen. Der Rumpf eines Handlers ist gewöhnlicher Lyx-Code: // ARM Cortex-M: SysTick-Handler fn SysTick_Handler(): int64 { tick_counter := tick_counter + 1; return 0; } // ARM Cortex-M: USART2-Empfangs-IRQ fn USART2_IRQHandler(): int64 { con USART2_RDR: int64 := 0x4000_4424; var byte: int64 := peek8(USART2_RDR); RingBufferPush(byte); // Lock-freier Push (ISR schreibt, Main liest) return 0; } ==== Regeln für ISRs ==== ^ Regel ^ Begründung ^ | Kein ''MutexLock'' / ''pthread'' | Mutex kann blockieren — ISR darf nicht blockieren | | Kein dynamischer Speicher (''alloc'') | Heap-Allokation ist nicht re-entrant ohne OS-Schutz | | Kein ''Print'' / I/O | I/O-Funktionen können intern sperren oder langsam sein | | Alle geteilten Variablen als ''@volatile'' | Compiler darf Zugriffe sonst wegoptimieren | | Minimale Logik | ISR soll schnell enden — aufwändige Arbeit in Hauptschleife auslagern | | Geteilte Variablen als ''@volatile'' | Einziges verfügbares Mittel gegen Wegoptimieren — ''@no_opt'' gibt es nicht | ==== @volatile auf gemeinsame Variablen ==== Variablen, die sowohl im Hauptprogramm als auch in einer ISR benutzt werden, müssen ''@volatile'' sein — sonst darf der Compiler Caching-Optimierungen anwenden, die den ISR-Schreibzugriff unsichtbar machen: @volatile var tick_counter: int64 := 0; @volatile var uart_rx_byte: uint8 := 0; @volatile var uart_rx_ready: bool := false; ---- ===== 4. ISR ↔ Hauptschleife: Lock-freier Ring Buffer ===== Der häufigste Weg, Daten von einer ISR an die Hauptschleife zu übergeben, ist ein **SPSC-Ring-Buffer** (Single Producer, Single Consumer). Die ISR schreibt (Producer), die Hauptschleife liest (Consumer). Weil es genau einen Schreiber und einen Leser gibt, sind keine Mutexe nötig — nur korrekte Speicherreihenfolge. unit spsc_ring; // Ring Buffer: ISR schreibt, Hauptschleife liest con RING_SIZE: int64 := 256; @volatile var ring_buf: [256]uint8 := []; @volatile var ring_head: int64 := 0; // Schreibzeiger (ISR) @volatile var ring_tail: int64 := 0; // Lesezeiger (Hauptschleife) // Aufruf NUR aus ISR — kein Mutex nötig (Single Writer) fn RingBufferPush(byte: uint8): bool { var next: int64 := (ring_head + 1) % RING_SIZE; if (next == ring_tail) { return false; } // Voll — Byte verwerfen ring_buf[ring_head] := byte; ring_head := next; return true; } // Aufruf NUR aus Hauptschleife — kein Mutex nötig (Single Reader) fn RingBufferPop(): (uint8, bool) { if (ring_tail == ring_head) { return (0, false); } // Leer var byte: uint8 := ring_buf[ring_tail]; ring_tail := (ring_tail + 1) % RING_SIZE; return (byte, true); } // Hauptschleife: verarbeitet empfangene UART-Bytes fn ProcessIncoming() { var byte, ok := RingBufferPop(); while (ok) limit(RING_SIZE) { // ... Byte verarbeiten ... var b2, ok2 := RingBufferPop(); byte := b2; ok := ok2; } } > **Invariante**: ''ring_head'' wird **nur von der ISR** geschrieben, ''ring_tail'' **nur von der Hauptschleife**. Solange diese Konvention eingehalten wird, ist kein Lock nötig. Würden mehrere ISRs schreiben oder mehrere Consumer lesen, wäre ein kritischer Abschnitt notwendig. ---- ===== 5. Kritische Abschnitte auf Bare-Metal ===== Wenn eine Race Condition zwischen ISR und Hauptschleife unvermeidbar ist (z.B. mehrteilige Struktur atomar lesen), müssen Interrupts kurzzeitig gesperrt werden. Das ist das Bare-Metal-Äquivalent zum Mutex. ==== ARM Cortex-M: PRIMASK ==== Auf ARM Cortex-M sperrt die ''CPSID i''-Instruktion alle maskierbaren Interrupts. Lyx kennt dafür **Inline-Assembly**: Ein ''asm''-Block nimmt je Zeichenkette eine Instruktion auf. Der Vorrat ist architekturabhängig und wird über ''--target'' gewählt; ein fremdes Mnemonic ist ein harter Übersetzungsfehler, kein stilles NOP. fn DisableIRQ(): int64 { asm { "cpsid i" } return 0; } fn EnableIRQ(): int64 { asm { "cpsie i" } return 0; } Übersetzt mit ''--target=arm-cm4''. Für ''arm64'' stehen unter anderem ''wfi'', ''isb'', ''dsb'' und ''dmb'' bereit, für RISC-V ''wfi'', ''fence'' und ''mret''. ==== Werte hinein- und herausreichen ==== Seit **1.0.20A** kann ein ''asm''-Block Operanden binden (#1324) — GCC-artig, aber mit ausgeschriebenem Registernamen statt Constraint-Buchstaben. Erst die Ausgaben, dann die Eingaben: asm { "in eax, dx" : out("eax", wert) : in("dx", port) } ^ Form ^ Bedeutung ^ | ''in("<reg>", <Ausdruck>)'' | wird **vor** dem Block ausgewertet und ins Register gelegt | | ''out("<reg>", <Variable>)'' | wird **nach** dem Block in die Variable geschrieben — lokal oder global | Erkannt werden ''rax'', ''rcx'', ''rdx'', ''rbx'', ''rsi'' und ''rdi'' samt ihren 32-, 16- und 8-Bit-Namen. Ein unbekannter Name ist ein Übersetzungsfehler, kein stiller Fehlgriff: error: asm: unbekanntes Register in out(...) Neu in der Mnemonic-Tabelle sind die Portbefehle ''in eax, dx'', ''in al, dx'', ''out dx, eax'' und ''out dx, al'' — damit ist der Zugriff auf den x86-Portadressraum möglich. > **Nur auf dem x86-64-Weg** (ELF, macOS, Windows). Auf den IR-Zielen weist ein Block mit Portbefehl ab — für LyxOS und bare metal bleibt der Legacy-Zugriff auf den PCI-Konfigurationsraum damit verschlossen. > > Die Grammatik steht in ''ebnf.md'' unter ''AsmStmt'', ''AsmOperandList'' und ''AsmOperand''. Alternativ lassen sich Assembler-Stubs als externe Funktionen einbinden: // Assembler-Stubs (z.B. irq_stubs.s): // DisableIRQ: CPSID i / BX lr // EnableIRQ: CPSIE i / BX lr // GetIPSR: MRS r0, IPSR / BX lr (0 = kein IRQ aktiv) @cap(system.exit) extern fn DisableIRQ(): void link "libfreertos.a"; @cap(system.exit) extern fn EnableIRQ(): void link "libfreertos.a"; @cap(system.exit) extern fn GetIPSR(): int64 link "libfreertos.a"; // > 0 wenn in ISR // Atomares Lesen einer mehrteiligen Struktur fn ReadSensorAtomic(out_val: int64, out_ts: int64) { DisableIRQ(); poke64(out_val, sensor_value); poke64(out_ts, sensor_timestamp); EnableIRQ(); } > **Achtung:** Interrupts so kurz wie möglich sperren. Lange kritische Abschnitte erhöhen die Interrupt-Latenz und verletzen WCET-Garantien. ==== RISC-V: mstatus.MIE ==== Auf RISC-V (Machine Mode) wird das MIE-Bit im ''mstatus''-Register gelöscht, um Interrupts zu sperren: // Assembler-Stubs: // DisableIRQ: CSRC mstatus, 8 / RET (MIE-Bit löschen) // EnableIRQ: CSRS mstatus, 8 / RET (MIE-Bit setzen) @cap(system.exit) extern fn DisableIRQ(): void link "libfreertos.a"; @cap(system.exit) extern fn EnableIRQ(): void link "libfreertos.a"; ---- ===== 6. ARM Cortex-M: SysTick & NVIC ===== Der SysTick-Timer erzeugt den periodischen System-Tick (typisch 1 ms) — die Zeitbasis für kooperative Scheduler und Timeouts. ==== SysTick initialisieren ==== unit arm_cortexm; // SysTick-Register (ARM Cortex-M, adressiert ab 0xE000E010) con SYST_CSR: int64 := 0xE000E010; // Control & Status con SYST_RVR: int64 := 0xE000E014; // Reload Value con SYST_CVR: int64 := 0xE000E018; // Current Value // NVIC: Interrupt Set Enable Register (für IRQ 0..31) con NVIC_ISER0: int64 := 0xE000E100; @volatile var sys_tick_ms: int64 := 0; // SysTick auf 1 ms konfigurieren (72 MHz Takt als Beispiel) fn SysTickInit(cpu_hz: int64) { var reload: uint32 := (cpu_hz / 1000 - 1) as uint32; poke32(SYST_RVR, reload); poke32(SYST_CVR, 0); // Zähler zurücksetzen poke32(SYST_CSR, 0x07); // Enable | TickInt | AHB-Takt } // SysTick-Handler (im Interrupt-Vektor) fn SysTick_Handler(): void { sys_tick_ms := sys_tick_ms + 1; } // Aktuellen Tick-Wert lesen (Hauptschleife) fn GetTickMs(): int64 { return sys_tick_ms; } // Busy-Wait für N Millisekunden fn DelayMs(n: int64) { var start: int64 := GetTickMs(); while (GetTickMs() - start < n) limit(100000) { } } ==== NVIC-Priorität setzen ==== // NVIC Interrupt Priority Register (IPR) — 8-Bit-Priorität pro IRQ // IPR[n] liegt bei 0xE000E400 + n fn NvicSetPriority(irq: int64, prio: uint8) { var ipr: int64 := (0xE000E400 + irq); poke8(ipr, prio); } // IRQ 37 (USART2 auf STM32F4) aktivieren und Priorität setzen fn EnableUSART2IRQ() { NvicSetPriority(37, 0xA0); // Priorität 10 (0xA0 bei 4-Bit-Prio) poke32(NVIC_ISER0, peek32(NVIC_ISER0) | (1 << 5)); // ISER[37/32] Bit 5 } ---- ===== 7. RISC-V: CLINT Timer-Interrupt ===== Der CLINT (Core Local Interruptor) auf RISC-V-Mikrocontrollern erzeugt den Machine-Timer-Interrupt (MTI), wenn ''mtime >= mtimecmp''. Er ist das RISC-V-Äquivalent zum SysTick. unit riscv_timer; // CLINT-Register-Basis (SiFive E-Series, GD32VF103) con CLINT_BASE: int64 := 0x0200_0000; con MTIME: int64 := (CLINT_BASE + 0xBFF8); con MTIMECMP: int64 := (CLINT_BASE + 0x4000); con TICKS_PER_MS: int64 := 32768; // 32.768 kHz CLINT-Takt (typisch) @volatile var systick_ms: int64 := 0; // Timer-Interrupt initialisieren: alle 1 ms auslösen fn TimerInit() { poke64(MTIMECMP, peek64(MTIME) + TICKS_PER_MS); } // Machine-Timer-Interrupt-Handler // (Eintrag im Machine-Trap-Vector, je nach Linker-Script) fn MachineTimerHandler(): void { systick_ms := systick_ms + 1; // Nächsten Interrupt planen poke64(MTIMECMP, peek64(MTIMECMP) + TICKS_PER_MS); } fn GetTickMs(): int64 { return systick_ms; } ---- ===== 8. ESP32 (Xtensa): FreeRTOS via FFI ===== Der ESP32 läuft mit FreeRTOS und bietet zwei Xtensa LX6-Kerne. Lyx bindet FreeRTOS über ''extern fn''-Deklarationen ein ([[lyx_-_programmiersprache:sprache:ffi|FFI]]) — mit ''link''-Klausel, die die FreeRTOS-Bibliothek benennt. ==== FreeRTOS-Typen und Tasks ==== unit esp32_rtos; import std.io; // FreeRTOS-Handle-Typen (intern: Pointer auf OS-Strukturen) type TaskHandle = int64; type QueueHandle = int64; type SemHandle = int64; // FreeRTOS Task-API @cap(system.exit) extern fn xTaskCreate( pvTaskCode: int64, // Funktionszeiger (als int64) pcName: pchar, usStackDepth: int64, // Stack-Größe in Words pvParameters: int64, // Argument (beliebiger Pointer) uxPriority: int64, // Priorität (0 = niedrigst) pxCreatedTask: int64 // Rückgabe-Handle (Adresse oder 0) ): int64 link "libfreertos.a"; // pdPASS = 1 bei Erfolg @cap(system.exit) extern fn vTaskDelay(xTicksToDelay: int64): void link "libfreertos.a"; @cap(system.exit) extern fn vTaskStartScheduler(): void link "libfreertos.a"; @cap(system.exit) extern fn vTaskDelete(task: TaskHandle): void link "libfreertos.a"; // FreeRTOS Queue-API @cap(system.exit) extern fn xQueueCreate(uxQueueLength: int64, uxItemSize: int64): QueueHandle link "libfreertos.a"; @cap(system.exit) extern fn xQueueSend(queue: QueueHandle, pvItemToQueue: int64, xTicksToWait: int64): int64 link "libfreertos.a"; @cap(system.exit) extern fn xQueueReceive(queue: QueueHandle, pvBuffer: int64, xTicksToWait: int64): int64 link "libfreertos.a"; con pdPASS: int64 := 1; con portMAX_DELAY: int64 := 0xFFFF_FFFF; ==== Zwei Tasks + Queue ==== import std.alloc; // alloc() var g_queue: QueueHandle := 0; // Producer-Task (Kern 0) fn ProducerTask(arg: int64): void { var zelle: int64 := alloc(8); // Adresse einer Zelle statt Adresse einer Variablen var value: int64 := 0; while (true) { value := value + 1; poke64(zelle, value); xQueueSend(g_queue, zelle, portMAX_DELAY); vTaskDelay(100); // 100 Ticks ≈ 100 ms warten } } // Consumer-Task (Kern 1) fn ConsumerTask(arg: int64): void { var zelle: int64 := alloc(8); while (true) { var ok: int64 := xQueueReceive(g_queue, zelle, portMAX_DELAY); if (ok == pdPASS) { PrintLn(StrConcat("Empfangen: ", IntToStr(peek64(zelle)))); } } } fn main(): int64 { g_queue := xQueueCreate(16, 8); // 16 Elemente à 8 Byte xTaskCreate(ProducerTask as int64, "producer", 2048, 0, 5, 0); xTaskCreate(ConsumerTask as int64, "consumer", 2048, 0, 5, 0); vTaskStartScheduler(); // gibt nie zurück return 0; } ==== ESP32 Dual-Core: Pinning ==== Auf dem ESP32 können Tasks an einen bestimmten Kern gepinnt werden: // xTaskCreatePinnedToCore: wie xTaskCreate, letztes Argument ist Core-ID (0 oder 1) @cap(system.exit) extern fn xTaskCreatePinnedToCore( pvTaskCode: int64, pcName: pchar, usStackDepth: int64, pvParameters: int64, uxPriority: int64, pxCreatedTask: int64, xCoreID: int64 // 0 = PRO_CPU, 1 = APP_CPU ): int64 link "libfreertos.a"; var g_queue: QueueHandle := 0; // wie im Block oben fn main(): int64 { g_queue := xQueueCreate(32, 8); xTaskCreatePinnedToCore(ProducerTask as int64, "prod", 2048, 0, 5, 0, 0); xTaskCreatePinnedToCore(ConsumerTask as int64, "cons", 2048, 0, 5, 0, 1); vTaskStartScheduler(); return 0; } ---- ===== 9. Allgemeines RTOS-Muster: Semaphor ==== Binäre Semaphore sind das RTOS-Äquivalent zur Bedingungsvariable: Eine ISR signalisiert, der Task wartet. // FreeRTOS Binary Semaphore @cap(system.exit) extern fn xSemaphoreCreateBinary(): SemHandle link "libfreertos.a"; @cap(system.exit) extern fn xSemaphoreGive(sem: SemHandle): int64 link "libfreertos.a"; @cap(system.exit) extern fn xSemaphoreTake(sem: SemHandle, timeout: int64): int64 link "libfreertos.a"; // Aus ISR heraus signalisieren (spezielle ISR-Variante!) @cap(system.exit) extern fn xSemaphoreGiveFromISR(sem: SemHandle, pxHigherPriorityTaskWoken: int64): int64 link "libfreertos.a"; @cap(system.exit) extern fn portYIELD_FROM_ISR(xHigherPriorityTaskWoken: int64): void link "libfreertos.a"; var g_sem: SemHandle := 0; // UART-ISR signalisiert Semaphor fn UART_RxISR(): void { var woken: int64 := alloc(8); poke64(woken, 0); xSemaphoreGiveFromISR(g_sem, woken); portYIELD_FROM_ISR(peek64(woken)); } // Verarbeitungs-Task wartet auf Semaphor fn UartProcessTask(arg: int64): void { while (true) { xSemaphoreTake(g_sem, portMAX_DELAY); // ... UART-Daten auslesen und verarbeiten ... } } ---- ===== 10. Safety & DO-178C auf Embedded-Targets ===== Safety-Anforderungen für Embedded-Targets nach DO-178C: ==== Stack-Limit pro Task ==== Jede ISR und jede RTOS-Task hat ihren eigenen Stack. ''@stack_limit'' begrenzt den Stack-Verbrauch zur Compile-Zeit: @stack_limit(512) fn SysTick_Handler(): void { sys_tick_ms := sys_tick_ms + 1; } @stack_limit(2048) fn ControlTask(arg: int64): void { // ... max. 2048 Byte Stack ... } ==== WCET-Annotation für ISRs ==== @wcet(10) // Maximale Ausführungszeit: 10 µs @stack_limit(128) fn ADC_IRQHandler(): void { // Liest einen Messwert und legt ihn in den Ring Buffer RingBufferPush(AdcRead()); } ==== DAL-Tabelle für Embedded-Nebenläufigkeit ==== ^ Konstrukt ^ DAL-D (Minor) ^ DAL-C (Major) ^ DAL-B (Gefährlich) ^ DAL-A (Katastrophal) ^ | Superloop | ✅ | ✅ | Timing-Nachweis | Formaler WCET-Beweis | | ISR ohne ''@wcet'' | ✅ | Dokumentation | ✗ | ✗ | | ISR mit ''@wcet'' | ✅ | ✅ | ✅ | + MC/DC + Lockstep | | Kritischer Abschnitt | ✅ | + Max-Dauer dok. | + Latenz-Nachweis | + Formal verifiziert | | FreeRTOS-Task | ✅ | + Stack-Nachweis | @stack_limit zwingend | RTOS selbst DAL-A | | Shared Memory ISR↔Main | SPSC-Ring | SPSC + ''@volatile'' | + Timing-Invarianten | + TMR auf Buffer | ==== Compile-Flags für Embedded-Safety ==== // DO-178C DAL-B Build für ARM Cortex-M: // lyxc main.lyx \ // --target=arm_cm \ // --call-graph \ // --static-analysis \ // --runtime-checks \ // --lint \ // --mcdc-report \ // --target-energy=1 \ // -o firmware.elf // // Nicht vorhanden: --stack-check, --wcet, --coverage-report, -O2. // @stack_limit und @wcet werden ohne Flag bei jeder Uebersetzung geprueft; // -O0..-O3 und --no-opt sind wirkungslos (#1525). Fuer die Codeerzeugung // wirkt allein --target-energy. → Vollständige Flag-Referenz: [[lyx_-_programmiersprache:tools:compiler-parameter|CLI-Referenz]] ---- ===== 11. Checkliste: Embedded-Nebenläufigkeit ===== Checkliste für sicheren und korrekten Einsatz: * ''@volatile'' auf alle Variablen, die ISR und Hauptschleife teilen * Interrupt-Vektor über Linker-Skript oder Assembler-Stub verdrahtet (''@section'' gibt es nicht) * SPSC-Ring-Buffer: sicherstellen, dass nur **ein** Schreiber und **ein** Leser existieren * Kein ''malloc'' / ''std.alloc'' in ISRs * Kritische Abschnitte so kurz wie möglich halten * ''@stack_limit'' für alle ISRs und RTOS-Tasks gesetzt * ''@wcet'' für zeitkritische ISRs gesetzt und vom Compiler verifiziert * FreeRTOS-Task-Stack-Größe großzügig bemessen (Überläufe sind schwer zu debuggen) * Watchdog-Reset in der Hauptschleife — nie vergessen ---- ===== Zusammenfassung ===== ^ Szenario ^ Empfohlenes Muster ^ | Linux / macOS, mehrere Threads | ''std.thread'' — [[lyx_-_programmiersprache:sprache:threads|Threads & Nebenläufigkeit]] | | Bare-Metal, eine Aufgabe | Superloop mit Hardware-Tick | | Bare-Metal, ISR → Hauptschleife | SPSC-Ring-Buffer + ''@volatile'' | | Bare-Metal, kritische Sektion | Interrupts sperren per ''asm''-Block (''cpsid i'' / ''cpsie i'') | | ARM Cortex-M, Zeitbasis | SysTick-ISR + ''sys_tick_ms'' | | RISC-V, Zeitbasis | CLINT MTIMECMP + Machine-Timer-Handler | | ESP32, zwei Tasks | FreeRTOS via FFI + ''xTaskCreatePinnedToCore'' | | ESP32, ISR → Task | FreeRTOS Binary Semaphor + ''xSemaphoreGiveFromISR'' | | Safety-kritisch (DAL-B/A) | ''@stack_limit'' + ''@wcet'' + SPSC | ---- Letzte Aktualisierung: 2026-05-22 Codebeispiele geprüft: gegen **lyxc 1.2.5C** übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die ''pub fn''-Signaturen in ''aurum/std'' gehalten, 0 Abweichungen).