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 mitsoftware_lockstepgeschü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
