Skip to main content

Real-Time Dual Execution Domain

Capabilities

Real-Time Dual Execution Domain

EnerOS adopts a dual execution domain architecture: the General Domain carries Agent orchestration, AI inference, and business logic; the Real-time Domain carries protection logic, switching operations, and closed-loop control, guaranteeing P99 < 1ms deterministic latency. The two domains communicate through kernel-space shared memory, avoiding process switching overhead.

The power system has stringent latency requirements: relay protection must act within 4 cycles (80ms @ 50Hz), PMU phasor calculation requires output above 30 Hz, and closed-loop AVR control cycles are at the 100 ms level. The millisecond-level jitter of a general-purpose operating system (Linux CFS scheduling) cannot meet these scenarios. EnerOS implements “fast-slow separation” at the kernel level through the dual execution domain.

Architecture Overview

┌─────────────────────────────────────────────────┐
│           General Domain                         │
│  - Agent orchestration                           │
│  - LLM inference                                 │
│  - Business process                              │
│  - Time-series analysis                          │
│  - Scheduling: CFS (Completely Fair Scheduler)   │
└──────────────────┬──────────────────────────────┘
                   │ Shared Memory / Lock-free Queue
                   │ Latency < 10μs
┌──────────────────┴──────────────────────────────┐
│           Real-time Domain                       │
│  - Protection logic (4ms)                        │
│  - Closed-loop control (1ms)                     │
│  - Switching operations (100μs)                  │
│  - Phasor calculation (250μs)                    │
│  - Scheduling: SCHED_FIFO / PREEMPT_RT / Xenomai │
└─────────────────────────────────────────────────┘

Domain Responsibility Division

Task TypeGeneral DomainReal-time Domain
Agent orchestration-
LLM inference-
Load flow calculation-
State estimation100ms cycle version
Digital twin What-If-
Protection logic (distance protection)-
Closed-loop control (AVR / AGC)-
Switching operations (GOOSE)-
Phasor calculation (PMU)-
Fault recording trigger-

Real-time Domain Task Definition

Real-time tasks are defined via the RealtimeTask trait, following the periodic trigger model:

use eneros_realtime::{RealtimeTask, RtContext, RtResult};
use std::time::Duration;

struct ProtectionLogic {
    period: Duration,
    breaker_id: u32,
    zone_reach: f64,  // Ω
}

impl RealtimeTask for ProtectionLogic {
    fn period(&self) -> Duration { self.period }

    fn priority(&self) -> i32 { 99 }  // SCHED_FIFO priority

    fn cpu_affinity(&self) -> Option<Vec<u32>> {
        Some(vec![0, 1])  // Pin to CPU 0,1
    }

    fn run(&mut self, ctx: &mut RtContext) -> RtResult<()> {
        // Read PMU data (shared memory, zero-copy)
        let pmu = ctx.pmu_snapshot();

        // Distance protection algorithm
        let z_seen = self.compute_impedance(&pmu);
        if z_seen < self.zone_reach {
            // Trip immediately (lock-free channel, < 100μs)
            ctx.trip_breaker(self.breaker_id);
            ctx.log_event(ProtectionEvent::Trip {
                impedance: z_seen,
                reach: self.zone_reach,
            });
        }

        Ok(())
    }
}

impl ProtectionLogic {
    fn compute_impedance(&self, pmu: &PmuSnapshot) -> f64 {
        let v = pmu.voltage_complex();
        let i = pmu.current_complex();
        (v / i).norm()
    }
}

RealtimeTask Trait Methods

MethodTypeRequiredDescription
period()DurationYesTask period
priority()i32YesSCHED_FIFO priority (1-99)
cpu_affinity()Option<Vec>NoCPU affinity
run(&mut ctx)RtResultYesTask body
init(&mut ctx)RtResultNoInitialization
on_overrun(dur)RtResultNoTimeout callback
shutdown(&mut ctx)RtResultNoCleanup on exit

Task Scheduling

The real-time domain adopts priority-based preemptive scheduling, combined with Linux PREEMPT_RT or Xenomai:

use eneros_realtime::{RtScheduler, SchedPolicy, IsolationConfig};

let mut sched = RtScheduler::new()
    .policy(SchedPolicy::Fifo)
    .cpu_affinity(0..2)  // Isolate CPU 0,1
    .isolate_from_general(true)
    .isolation(IsolationConfig {
        isolcpus: true,           // Kernel boot parameter isolcpus
        nohz_full: true,          // NO_HZ_FULL
        rcu_nocbs: true,          // RCU offloading
        huge_pages: true,         // Huge pages
        disable_irq: true,        // Disable IRQ
    });

sched.spawn(ProtectionLogic::new(Duration::from_micros(4000), brk_id, 5.0));
sched.spawn(ClosedLoopControl::new(Duration::from_millis(1)));
sched.spawn(BreakerOperation::new(Duration::from_micros(100)));
sched.spawn(PhasorCalculator::new(Duration::from_micros(250)));
sched.start()?;

SchedPolicy Variants

PolicyDescriptionApplicable Scenario
FifoSCHED_FIFO first-in-first-outMost real-time tasks
RoundRobinSCHED_RR time-sliced rotationMultiple tasks at the same priority
DeadlineSCHED_DEADLINE EDFPeriodic tasks
XenomaiXenomai CobaltUltimate real-time performance

Cross-Domain Communication

The two domains communicate through lock-free ring queues, avoiding lock contention and cache jitter:

use eneros_realtime::cross::{Channel, Message, ChannelConfig};

let (tx, rx) = Channel::unbounded::<ControlCmd>(
    ChannelConfig {
        capacity: 1024,
        spin_wait_us: 5,
        cache_align: true,
    },
);

// General domain → Real-time domain
tx.send(ControlCmd::SetVoltage(1.02))?;

// Real-time domain receives (non-blocking)
if let Some(cmd) = rx.try_recv() {
    match cmd {
        ControlCmd::SetVoltage(v) => self.target_voltage = v,
        ControlCmd::TripBreaker(id) => ctx.trip_breaker(id),
    }
}

// Real-time domain → General domain (event reporting)
let (evt_tx, evt_rx) = Channel::bounded::<RtEvent>(256);
evt_tx.send(RtEvent::BreakerTripped { id: 5, reason: "overcurrent" });

Channel Types

TypeCapacityBlocking BehaviorApplicable Scenario
unboundedUnlimitedSend does not blockGeneral → Real-time
bounded(n)nBlocks when fullReal-time → General
spsc1Single-producer single-consumerHigh-speed data stream
mpmcnMulti-producer multi-consumerGeneral scenario

Cross-Domain Message Latency

PathLatency (P99)
General → Real-time (lock-free queue)< 5μs
Real-time → General (event queue)< 10μs
Shared memory read< 1μs
Shared memory write< 1μs

Determinism Guarantees

MeasureDescriptionEffect
CPU isolationisolcpus=0,1 isolates real-time coresAvoid being preempted by other processes
Memory pre-allocationAllocated at startup, no GC at runtimeAvoid allocation jitter
Lock-free data structuresSPSC/MPMC lock-free queuesAvoid lock contention
Huge pages2MB / 1GB hugepagesReduce TLB misses
IRQ affinityNetwork card interrupts bound to non-real-time coresAvoid IRQ interruptions
WatchdogAuto-reset on task timeoutFault self-healing
NO_HZ_FULLReduce timer ticksReduce context switches
RCU nocbsRCU callback offloadingReduce jitter

Kernel Boot Parameter Example

# /etc/default/grub
GRUB_CMDLINE_LINUX="isolcpus=0,1 nohz_full=0,1 rcu_nocbs=0,1 \
    hugepagesz=2M hugepages=512 iommu=pt intel_idle.max_cstate=0 \
    processor.max_cstate=0 idle=poll"

Task Types and Periods

TaskPeriodPriorityJitterCPU
Switching operation (GOOSE)100μs99< 5μs0
Phasor calculation (PMU)250μs98< 10μs0
Closed-loop control (AVR)1ms95< 50μs1
Closed-loop control (AGC)4ms94< 100μs1
Protection logic (distance)4ms90< 100μs0
Protection logic (overcurrent)4ms90< 100μs0
State estimation (fast)100ms50< 1ms1
Communication (IEC 61850)1ms80< 50μs1

RtContext API

APIs provided by the real-time task context:

use eneros_realtime::RtContext;

impl RtContext {
    // Data access (shared memory, zero-copy)
    pub fn pmu_snapshot(&self) -> PmuSnapshot;
    pub fn rtu_snapshot(&self) -> RtuSnapshot;
    pub fn network_state(&self) -> NetworkState;

    // Control output
    pub fn trip_breaker(&self, id: u32);
    pub fn close_breaker(&self, id: u32);
    pub fn set_excitation(&self, gen_id: u32, v: f64);
    pub fn set_tap(&self, xfmr_id: u32, tap: i16);

    // Communication
    pub fn send_event(&self, evt: RtEvent);
    pub fn try_recv_cmd(&self) -> Option<ControlCmd>;

    // Logging (lock-free)
    pub fn log_event(&self, evt: impl Event);
    pub fn waveform_record(&self, data: &WaveformBuffer);

    // Time
    pub fn now(&self) -> Timestamp;
    pub fn elapsed(&self) -> Duration;
}

Fault Switching

A General domain failure does not affect the Real-time domain: when the Real-time domain detects a General domain heartbeat timeout, it automatically switches to “Safe Mode” and runs according to a preset strategy.

use eneros_realtime::SafetyMode;

sched.on_general_domain_timeout(|| {
    // Switch to safe mode
    SafetyMode::island_operation()
        .hold_frequency(50.0)
        .hold_voltage(1.0)
        .disable_remote_control()
        .enable_local_protection()
        .activate();
});

// Heartbeat configuration
sched.heartbeat_config(HeartbeatConfig {
    interval: Duration::from_millis(100),
    timeout: Duration::from_millis(500),
    on_missed: HeartbeatAction::Warn,
    on_timeout: HeartbeatAction::EnterSafetyMode,
});

SafetyMode Behavior

BehaviorMeaning
hold_frequencyMaintain frequency at the set value
hold_voltageMaintain voltage at the set value
disable_remote_controlReject remote control commands
enable_local_protectionEnable local protection
activateActivate safe mode

Performance Metrics

MetricValueTest Method
Task scheduling jitter (P99)< 50μs10,000 periodic tasks
Task scheduling jitter (P99.9)< 100μs10,000 periodic tasks
Cross-domain message latency< 10μsLock-free queue
GPIO response (switching operation)< 100μsOscilloscope measurement
End-to-end protection action (P99)< 1msFault injection
End-to-end protection action (worst case)< 4msFault injection
Max continuous jitter-free operation> 30 days7×24 test
CPU utilization (real-time core)< 60%Full load
Memory usage (real-time domain)< 100MBPre-allocated

Test environment: Intel Xeon 4 cores / 8GB / Ubuntu 22.04 + PREEMPT_RT.

Configuration Parameters

Real-time domain-related configuration in eneros.toml:

[realtime]
# Scheduling policy: fifo / round_robin / deadline / xenomai
policy = "fifo"
# Real-time CPU cores (consistent with isolcpus)
cpus = [0, 1]
# Huge pages (MB)
huge_pages_mb = 1024
# Whether to enable the watchdog
watchdog = true
# Watchdog timeout (ms)
watchdog_timeout_ms = 100
# Heartbeat interval (ms)
heartbeat_interval_ms = 100
# Heartbeat timeout (ms)
heartbeat_timeout_ms = 500
# Failover to safe mode
safety_mode_on_timeout = true
# Maximum number of real-time tasks
max_tasks = 256
ParameterTypeDefaultDescription
policyenumfifoScheduling policy
cpusVec[0,1]Real-time CPU cores
huge_pages_mbu321024Huge pages size
watchdogbooltrueEnable watchdog
watchdog_timeout_msu32100Watchdog timeout
heartbeat_interval_msu32100Heartbeat interval
heartbeat_timeout_msu32500Heartbeat timeout
safety_mode_on_timeoutbooltrueSwitch to safe mode on timeout
max_tasksu32256Maximum number of real-time tasks

Relationship with Other Capabilities

Related CapabilityInteraction
Safety GuardReal-time domain commands also pass through the gateway (simplified version)
Digital Twin EngineSyncs mirror with the real-time domain
Equipment Model LibraryReal-time domain uses simplified equipment models
Multi-Agent CollaborationProtection-class Agents run in the real-time domain
Time-Series Native OperationsPMU data is written in streaming fashion
Physical Constraint DecisionReal-time domain constraint validation completes in microseconds
Grid Topology First-Class CitizenTopology changes sync to the real-time domain in real time