Skip to main content

Real-Time Determinism

Core Concepts

Real-Time Determinism

Real-Time Determinism is the performance guarantee of EnerOS: through the Dual-Execution Architecture, it simultaneously satisfies the flexibility of AI inference and the determinism of power protection logic. The general execution domain handles non-real-time tasks (Agent orchestration, AI inference, business logic), while the real-time execution domain handles hard real-time tasks (relay protection, switch operations, emergency control). The real-time domain guarantees microsecond-level response with zero jitter.

Design Motivation

Real-Time Requirements of Power Systems

Different tasks in power systems have vastly different latency requirements:

Task TypeLatency RequirementJitter ToleranceExample
Relay protection< 4ms< 100μsFault detection and tripping
Emergency control< 10ms< 1msLow-frequency load shedding, stability control
Stability devices< 100ms< 10msOscillation islanding
Dispatch decisions< 1sSecond-levelEconomic dispatch
State estimation< 10sSecond-levelLoad flow calculation
Load forecastingMinute-levelMinute-levelDay-ahead forecasting

Traditional single execution domain architectures cannot simultaneously satisfy microsecond-level protection and second-level AI inference: protection tasks would be blocked by AI inference, and AI inference would be interrupted by protection tasks.

Dual-Execution Architecture Solution

EnerOS divides the execution domain into two isolated layers:

┌──────────────────────────────────────────────────────────────┐
│                    General Execution Domain                   │
│                                                              │
│  · Agent orchestration · AI inference · Business logic · API │
│  · Scheduling policy: CFS (Completely Fair Scheduler)        │
│  · Latency characteristics: Probabilistic (1-50ms)           │
│  · CPU isolation: Core 0-2                                   │
└──────────────────────────┬───────────────────────────────────┘


                  ┌─────────────────┐
                  │  Safety Gateway │
                  │  · Command interception     │
                  │  · Constraint validation     │
                  │  · Feasibility projection    │
                  │  · Priority sorting          │
                  └────────┬────────┘


┌──────────────────────────────────────────────────────────────┐
│                    Real-Time Execution Domain                 │
│                                                              │
│  · Protection logic · Switch operations · Emergency control  │
│  · Watchdog                                                  │
│  · Scheduling policy: FIFO + static priority                 │
│  · Latency characteristics: Deterministic (P99 < 1ms)        │
│  · CPU isolation: Core 3 (isolated core)                     │
│  · Kernel: PREEMPT_RT or bare metal                          │
└──────────────────────────────────────────────────────────────┘

Dual-Execution Architecture Details

General Execution Domain

The general execution domain runs the standard Linux scheduler, handling latency-insensitive tasks:

AttributeValue
SchedulerCFS (Completely Fair Scheduler)
Preemption modeVoluntary preemption
Latency range1-50ms
JitterHigh (affected by system load)
CPU affinityCore 0-2 (configurable)
Memory limitNo special restrictions
Applicable scenariosAgent orchestration, AI inference, business logic, API services
use eneros_os::domain::{GeneralDomain, DomainConfig};

let general = GeneralDomain::new(DomainConfig {
    cpu_affinity: vec![0, 1, 2],          // Bind Core 0-2
    scheduler: Scheduler::CFS,
    memory_limit_mb: 4096,                 // 4GB memory limit
    io_priority: IoPriority::BestEffort,
});

// Run Agent in general domain
general.spawn(async move {
    agent.run().await
}).await?;

Real-Time Execution Domain

The real-time execution domain runs a real-time scheduler, handling hard real-time tasks:

AttributeValue
SchedulerSCHED_FIFO + static priority
Preemption modeFull preemption (PREEMPT_RT)
Latency range< 1ms (P99)
JitterExtremely low (< 100μs)
CPU affinityCore 3 (isolated core, exclusive)
Memory limitPre-allocated + mlock
Applicable scenariosRelay protection, switch operations, emergency control
use eneros_os::domain::{RealTimeDomain, RealTimeConfig};

let rt = RealTimeDomain::new(RealTimeConfig {
    cpu_affinity: vec![3],                // Exclusive Core 3
    scheduler: Scheduler::Fifo,
    priority_range: (50, 99),              // Real-time priority range
    memory_prealloc_mb: 256,               // Pre-allocate 256MB
    mlock: true,                           // Lock memory, disable paging
    isolated: true,                        // CPU isolation
});

// Run protection logic in real-time domain
rt.spawn_realtime(move || {
    protection_loop();
}).await?;

Inter-Domain Communication

The two execution domains communicate through lock-free queues to avoid lock contention affecting real-time performance:

use eneros_os::domain::{Channel, ChannelConfig};

// Create inter-domain channel (lock-free SPSC queue)
let (tx, rx) = Channel::new(ChannelConfig {
    capacity: 1024,
    lock_free: true,                       // Lock-free
    cache_line_size: 64,                   // Cache line alignment
});

// General domain sends command
general.spawn(async move {
    tx.send(Command::OpenBreaker(breaker_id)).await?;
}).await?;

// Real-time domain receives and executes
rt.spawn_realtime(move || {
    loop {
        match rx.recv_nonblocking() {
            Some(cmd) => execute_command(cmd),
            None => cpu_relax(),
        }
    }
}).await?;

Real-Time Domain Scheduling Strategy

Priority Levels

The real-time domain uses SCHED_FIFO scheduling with the following priority levels:

PriorityTask TypeResponse RequirementExample
99Hardware interrupt< 10μsFault detection
95-98Protection logic< 1msTrip command
90-94Emergency control< 10msLow-frequency load shedding
80-89Stability control< 100msOscillation islanding
70-79Real-time monitoring< 100msState collection
60-69Inter-domain communication< 1msCommand dispatch
50-59Background tasks< 100msLog recording

Task Registration

use eneros_os::realtime::{RealTimeTask, Priority, Schedule};

// Register protection logic task (highest priority)
rt.register_task(RealTimeTask::new(
    "protection_main",
    Priority::new(96),
    Schedule::Periodic { period_us: 1000 }, // 1ms period
    move || {
        protection_loop();
    },
)).await?;

// Register emergency control task
rt.register_task(RealTimeTask::new(
    "emergency_control",
    Priority::new(92),
    Schedule::Sporadic { deadline_us: 5000 }, // 5ms deadline
    move || {
        emergency_handler();
    },
)).await?;

// Start real-time domain
rt.start().await?;

Priority Inversion Handling

Priority Inversion Problem

When a low-priority task holds a resource needed by a high-priority task, the high-priority task gets blocked:

Time →
High priority task H:     ████████waiting for resource████████execute
Medium priority task M:               ████████████████
Low priority task L:     ██holds resource██

Solution: Priority Inheritance Protocol

EnerOS implements the Priority Inheritance Protocol in the real-time domain:

use eneros_os::realtime::{PriorityInheritance, RtMutex};

// Mutex with priority inheritance enabled
let mutex = RtMutex::new_with_inheritance(resource);

// Low priority task acquires lock, temporarily promoted to waiter's priority
rt.spawn_realtime(move || {
    let _guard = mutex.lock();  // Automatically promotes priority
    use_resource(&mut resource);
    // Restores original priority when lock is released
}).await?;

// High priority task waits for lock
rt.spawn_realtime(move || {
    let _guard = mutex.lock();  // Blocks, but low priority task has been promoted
    use_resource(&mut resource);
}).await?;

Configuration Options

use eneros_os::realtime::RtConfig;

let config = RtConfig::new()
    .priority_inheritance(true)            // Enable priority inheritance
    .priority_ceiling(false)               // Priority ceiling (more aggressive)
    .max_blocked_us(100)                   // Maximum blocking time 100μs
    .watchdog_timeout_ms(10)               // Watchdog timeout 10ms
    .cpu_isolation(true)                   // CPU isolation
    .disable_irq_balance(true);            // Disable IRQ load balancing

Interrupt Response

Interrupt Handling Layers

┌─────────────────────────────────────┐
│  Hardware Interrupt (IRQ)           │  ← Hardware triggered, < 1μs
│  · Fault detection                  │
│  · Timer                            │
│  · Network card receive             │
├─────────────────────────────────────┤
│  Top Half                           │  ← Atomic execution, < 10μs
│  · Emergency operations (e.g., trip)│
│  · Wake up real-time task           │
├─────────────────────────────────────┤
│  Bottom Half                        │  ← Real-time task, < 100μs
│  · Detailed analysis                │
│  · Log recording                    │
│  · Notify general domain            │
└─────────────────────────────────────┘

Interrupt Registration

use eneros_os::realtime::{IrqHandler, IrqConfig};

// Register fault detection interrupt
rt.register_irq(IrqHandler::new(
    IrqConfig::new(irq_number)
        .priority(99)                      // Highest priority
        .threaded(false),                  // Non-threaded (top half execution)
    move |irq| {
        // Top half: emergency trip
        trip_breaker(fault_breaker_id);
        // Wake up real-time task to handle details
        rt.wake_task("fault_handler");
        IrqResult::Handled
    },
)).await?;

// Register fault handling real-time task
rt.register_task(RealTimeTask::new(
    "fault_handler",
    Priority::new(97),
    Schedule::Sporadic { deadline_us: 1000 },
    move || {
        let fault = read_fault_details();
        log_fault(&fault);
        notify_general_domain(fault);
    },
)).await?;

Watchdog Mechanism

Soft Watchdog

EnerOS has a built-in soft watchdog in the real-time domain to monitor task execution:

use eneros_os::realtime::{Watchdog, WatchdogConfig};

let watchdog = Watchdog::new(WatchdogConfig {
    timeout_ms: 10,                        // 10ms timeout
    action_on_timeout: WatchdogAction::Reboot, // Reboot on timeout
    stats_interval_ms: 1000,
});

// Task periodically feeds the watchdog
rt.register_task(RealTimeTask::new(
    "protection_main",
    Priority::new(96),
    Schedule::Periodic { period_us: 1000 },
    move || {
        watchdog.kick();                   // Feed the watchdog
        protection_loop();
    },
)).await?;

watchdog.start().await?;

Hardware Watchdog

Supports hardware watchdog to ensure recovery even when the kernel is completely frozen:

use eneros_os::realtime::HardwareWatchdog;

let hw_watchdog = HardwareWatchdog::open("/dev/watchdog")?
    .timeout_ms(100)                       // 100ms timeout
    .auto_kick(true);                      // Real-time domain auto-feeds

hw_watchdog.start()?;

Watchdog Timeout Handling

Timeout ScenarioDetection MethodAction
Task did not feed watchdogSoft watchdogRestart task
Real-time domain frozenSoft watchdogRestart real-time domain
Kernel frozenHardware watchdogHardware reboot
Scheduling delay exceededScheduling traceAlert + degradation

Safety Gateway

The safety gateway is the bridge between the two execution domains, responsible for safe command delivery:

use eneros_os::gateway::{SafetyGateway, GatewayConfig, Priority};

let gateway = SafetyGateway::new(GatewayConfig {
    constraint_engine: constraints.clone(),
    channel_capacity: 1024,
    max_command_rate: 10000,                // Max commands per second
    enable_projection: true,                // Enable feasibility projection
});

// Agent dispatches commands through the gateway
let result = gateway.execute(
    Command::SetGeneration { bus: 1, mw: 80.0 },
    Priority::Normal,
).await?;

match result {
    ExecuteResult::Executed => {
        log::info!("Command executed");
    }
    ExecuteResult::Projected(projected) => {
        log::warn!("Command was projected: {:?}", projected);
    }
    ExecuteResult::Rejected(reason) => {
        log::error!("Command rejected: {}", reason);
    }
    ExecuteResult::Queued => {
        log::info!("Command queued, waiting for real-time domain processing");
    }
}

Emergency Command Priority

// Emergency commands (e.g., protection trip) are prioritized
gateway.execute(
    Command::TripBreaker(fault_breaker_id),
    Priority::Emergency,                    // Emergency priority
).await?;
// Emergency commands reach the real-time domain within < 100μs

Real-Time Performance Metrics

Latency Metrics

OperationGeneral DomainReal-Time Domain
Average latency1-10ms< 0.5ms
P99 latency10-50ms< 1ms
P99.9 latency50-200ms< 2ms
Max jitter50ms< 100μs
Interrupt response10-100μs< 10μs
Inter-domain communication-< 50μs

Throughput Metrics

OperationGeneral DomainReal-Time Domain
Command processing10k/sec100k/sec
Event broadcast10k/sec50k/sec
Time-series write1 million points/sec500k points/sec
Constraint validation100k/sec1 million/sec

Scheduling Latency Test

use eneros_os::realtime::{LatencyTest, LatencyResult};

// Run scheduling latency test
let result: LatencyResult = rt.test_latency(Duration::seconds(60)).await?;

println!("Average latency: {} μs", result.avg_us);
println!("P99 latency: {} μs", result.p99_us);
println!("P99.9 latency: {} μs", result.p999_us);
println!("Max latency: {} μs", result.max_us);
println!("Violation count: {}", result.violations);

Complete Example: Protection Logic Real-Time Execution

use eneros_os::{domain::{RealTimeDomain, RealTimeConfig}, realtime::*};
use eneros_constraint::ConstraintEngine;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create real-time domain
    let rt = RealTimeDomain::new(RealTimeConfig {
        cpu_affinity: vec![3],
        scheduler: Scheduler::Fifo,
        priority_range: (50, 99),
        memory_prealloc_mb: 256,
        mlock: true,
        isolated: true,
    });

    // 2. Enable priority inheritance
    rt.set_config(RtConfig::new()
        .priority_inheritance(true)
        .max_blocked_us(100)
        .watchdog_timeout_ms(10)
        .cpu_isolation(true));

    // 3. Register fault detection interrupt
    rt.register_irq(IrqHandler::new(
        IrqConfig::new(IRQ_FAULT_DETECT)
            .priority(99)
            .threaded(false),
        move |_irq| {
            trip_breaker(FAULT_BREAKER_ID);
            rt.wake_task("fault_handler");
            IrqResult::Handled
        },
    ))?;

    // 4. Register protection logic periodic task
    rt.register_task(RealTimeTask::new(
        "protection_main",
        Priority::new(96),
        Schedule::Periodic { period_us: 1000 },
        move || {
            watchdog.kick();
            protection_loop();
        },
    ))?;

    // 5. Register fault handling task
    rt.register_task(RealTimeTask::new(
        "fault_handler",
        Priority::new(97),
        Schedule::Sporadic { deadline_us: 1000 },
        move || {
            let fault = read_fault_details();
            log_fault(&fault);
            notify_general_domain(fault);
        },
    ))?;

    // 6. Start watchdog
    let watchdog = Watchdog::new(WatchdogConfig {
        timeout_ms: 10,
        action_on_timeout: WatchdogAction::Reboot,
        stats_interval_ms: 1000,
    });
    watchdog.start()?;

    // 7. Start real-time domain
    rt.start()?;

    Ok(())
}

Comparison with RT-Linux

DimensionRT-LinuxEnerOS Real-Time Domain
ImplementationKernel patchKernel component
SchedulerSCHED_FIFO/RRSCHED_FIFO + static priority
Preemption modeFull preemptionFull preemption
P99 latency< 1ms< 1ms
Priority inheritanceSupportedBuilt-in
WatchdogExternal toolBuilt-in
Domain isolationcgroupsCPU affinity + isolated core
Power domain knowledgeNoneBuilt-in protection logic framework
Constraint validationNoneBuilt-in constraint engine
Deployment complexityHigh (requires kernel compilation)Low (out of the box)

Comparison with Other Real-Time Solutions

SolutionLatencyEase of UsePower AdaptationApplicable Scenarios
RT-Linux< 1msMediumNoneGeneral real-time
Xenomai< 500μsLowNoneHard real-time
PREEMPT_RT< 1msMediumNoneGeneral real-time
VxWorks< 100μsLowWeakEmbedded
EnerOS real-time domain< 1msHighStrongPower-native

Limitations and Trade-offs

Trade-offDescriptionMitigation Strategy
CPU usageReal-time domain exclusively occupies one coreMulti-core systems recommended ≥ 4 cores
Memory pre-allocationReal-time domain requires pre-allocated memoryConfigure reasonable upper limit
Deployment limitationsRequires PREEMPT_RT or isolated coreGeneral domain can be deployed independently
Debugging difficultyReal-time domain is hard to interrupt for debuggingProvides scheduling traces and statistics
Cross-domain communication overheadInter-domain data copyLock-free queue + shared memory
Real-time domain cannot blockCannot call blocking APIsProvides async IO interface

Next Steps