Inhaltsverzeichnis

Software Lockstep

Software Lockstep ist ein Mechanismus zur Erkennung von Berechnungsfehlern — verursacht durch kurzzeitige Prozessorstörungen (SEUs in der CPU, Latch-Fehler, Timing-Anomalien). Der Compiler dupliziert kritische Rechenoperationen und vergleicht die Ergebnisse beider Berechnungen vor jedem dauerhaften Speicherzugriff oder return. Stimmen sie nicht überein, greift ein kontrollierter Recovery-Pfad.

Im Unterschied zu Hardware-Lockstep (zwei physische Kerne, synchron getaktet, Hardware vergleicht Ausgaben) läuft Software Lockstep auf einem einzelnen Kern — der Compiler fügt Instruktions-Redundanz in den Maschinencode ein.

→ Verwandt: TMR · Memory Scrubbing · DO-178C Hauptseite


1. Aktivierung

Software Lockstep wird per @integrity-Pragma auf Funktions- oder Unit-Ebene aktiviert:

// Auf Funktionsebene — nur diese Funktion ist geschützt
@dal(A)
@flight_crit
@integrity(mode: software_lockstep, interval: 50)
@stack_limit(2048)
@wcet(200)
fn ComputeControlOutput(state: ^FlightState): FlightCommands {
    // Alle ALU/FPU-Operationen hier werden dupliziert
    var elevator: int64 := Clamp(state.pitch_error / 10, -30, 30);
    var throttle: int64 := Clamp(500 + state.alt_error / 5, 0, 1000);
    return FlightCommands { elevator: elevator, throttle: throttle };
}

// Auf Unit-Ebene — schützt alle Funktionen der Unit
@integrity(mode: software_lockstep, interval: 50)
unit autopilot.core;

Der interval-Parameter (in ms) gibt an, wie häufig der Lockstep-Check implizit ausgelöst wird, wenn die Funktion in einem Polling-Kontext aufgerufen wird. Bei reinen Einzel-Aufrufen ist er ohne Wirkung — der Check findet immer statt.


2. Was der Compiler generiert

Für eine Funktion mit software_lockstep erzeugt der Compiler konzeptuell folgenden Code:

Quellcode

@integrity(mode: software_lockstep, interval: 50)
fn ComputeAltitudeError(current: int64, target: int64): int64 {
    var delta: int64 := target - current;
    var clamped: int64 := Clamp(delta, -5000, 5000);
    return clamped;
}

Generierter Maschinencode (konzeptuell)

// Primäre Berechnung
var p_delta:   int64 := target - current;
var p_clamped: int64 := Clamp(p_delta, -5000, 5000);

// Redundante Berechnung (separat in anderen Registern)
var r_delta:   int64 := target - current;
var r_clamped: int64 := Clamp(r_delta, -5000, 5000);

// Vergleich vor return
if (p_clamped != r_clamped) {
    LockstepFault("ComputeAltitudeError: mismatch");
    // → Safety-Panic oder Recovery-Handler
}

return p_clamped;

Der Compiler wählt unterschiedliche physische Register für primäre und redundante Berechnung, damit ein einzelner Latch-Fehler in einer Registerbank nicht beide Berechnungen gleichzeitig trifft.


3. Einschränkungen und Regeln

Regel Begründung
Kein @extern fn in lockstep-geschützten Funktionen Compiler hat keinen Zugriff auf externen Binärcode — kann ihn nicht duplizieren
@wcet muss etwa 2× den normalen Wert annehmen Jede Operation wird zweimal ausgeführt
Kein software_lockstep auf Funktionen mit Seiteneffekten (I/O, HW-Register) I/O-Operationen dürfen nicht doppelt ausgeführt werden
Empfohlen: Kombination mit @redundant für Variablen Schutz der Berechnung (Lockstep) + Schutz des Speichers (TMR)
Kein software_lockstep auf ISR-Funktionen ohne @no_opt ISR-Code muss deterministisch bleiben

// FALSCH — I/O in lockstep-Funktion
@integrity(mode: software_lockstep, interval: 50)
fn BadFunction(): void {
    WriteHardwareRegister(UART_TX, 'A');   // Würde Zeichen doppelt senden — Fehler
}

// RICHTIG — I/O auslagern, nur reine Berechnung schützen
@integrity(mode: software_lockstep, interval: 50)
fn ComputeOutput(sensor: int64): int64 {
    return Clamp(sensor * 3 / 2, 0, 1000);   // Reine Berechnung — sicher
}

fn SendOutput(val: int64): void {
    WriteHardwareRegister(UART_TX, val);   // I/O außerhalb des Lockstep-Schutzes
}


4. WCET-Interaktion

Software Lockstep verdoppelt den Instruktionsaufwand. @wcet muss entsprechend angepasst werden:

// Ohne Lockstep: geschätzte WCET 80 µs
@wcet(80)
fn NormalFunction(x: int64): int64 {
    return x * x + x / 2;
}

// Mit Lockstep: WCET auf ~2× erhöhen
@integrity(mode: software_lockstep, interval: 50)
@wcet(180)   // 2× + Overhead für Vergleich und Recovery-Pfad
fn ProtectedFunction(x: int64): int64 {
    return x * x + x / 2;
}

Der Compiler zeigt bei fehlerhaft gesetztem @wcet auf lockstep-Funktionen eine Warnung:

warning[W0244]: @wcet(80) on software_lockstep function ComputeControlOutput
   Estimated protected WCET: 167 µs exceeds declared budget: 80 µs
   Consider: @wcet(180) or higher


5. Vergleich der Integritätsmodi

Merkmal software_lockstep scrubbed hardware_ecc
Schützt ALU/FPU-Berechnungen Code-Segment im RAM RAM-Daten (Hardware)
Erkennt Rechenfehler Bit-Flips im Code Bit-Flips in Daten
WCET-Impact ~2× Gering (Hintergrund) Keiner
Hardware-Anforderung Keine Keine ECC-RAM zwingend
Einsatz DAL-A Kernberechnungen DAL-A/B auf Embedded Safety-kritischer ECC-RAM
Komb. mit @redundant Empfohlen Nicht nötig Redundant zu ECC

6. Recovery-Handler

Wenn eine Lockstep-Abweichung erkannt wird, greift der Standard-Recovery-Pfad (panic). Für kontrolliertes Verhalten kann ein Custom-Handler gesetzt werden:

// Recovery-Handler: wird bei Lockstep-Fehler aufgerufen
// Signatur fest: fn(fn_name: pchar, location: pchar): void
fn LockstepRecovery(fn_name: pchar, location: pchar): void {
    PrintStr("LOCKSTEP FAULT in: "); PrintStr(fn_name); PrintStr("\n");
    PrintStr("Location: "); PrintStr(location); PrintStr("\n");

    // Umschalten auf Backup-Computer, Safety-Zustand einleiten, etc.
    ActivateBackupSystem();
}

// Recovery-Handler registrieren (einmalig beim Start)
fn main(): int64 {
    SetLockstepRecoveryHandler(LockstepRecovery as int64);
    // ...
    return 0;
}

DO-178C-Anforderung: Der Recovery-Handler selbst darf nicht mit software_lockstep geschützt sein — er ist der Notfallpfad, der läuft wenn der Schutz versagt hat. Er sollte so minimal wie möglich sein: Status loggen, Backup aktivieren, sicheren Zustand einleiten.

7. DAL-Empfehlung

DAL software_lockstep Begründung
A Zwingend für Kernberechnungen Katastrophaler Ausfall — maximaler Schutz
B Empfohlen Gefährlicher Ausfall — Investition gerechtfertigt
C Optional WCET-Impact meist nicht vertretbar
D/E Nicht sinnvoll Overhead ohne Sicherheitsgewinn

Letzte Aktualisierung: 2026-05-22