跳到主内容

双执行架构

架构设计

双执行架构

EnerOS 采用双执行架构(Dual-Execution Architecture),将通用计算与实时控制分离,同时满足 AI 推理的灵活性与保护逻辑的确定性。该架构借鉴 RT-Linux 的双内核思想,但完全用 Rust 实现于用户态,通过 CPU 亲和性、SCHED_FIFO 调度、共享内存通道与无锁队列实现域间隔离与通信。

设计动机

电力系统同时存在两类工作负载,它们对延迟、抖动与确定性的要求截然不同:

工作负载代表场景延迟要求抖动容忍失败代价
AI 推理 / 业务编排调度计划生成、负荷预测、报价策略10ms - 10s经济损失
保护 / 紧急控制过流跳闸、低频减载、解列< 1ms< 100μs设备损坏、电网失稳

将两者放入同一调度域会导致:AI 推理的 GC/分配/网络 IO 抖动直接污染保护逻辑,违反 NERC PRC-024 的 100ms 抗扰度要求。EnerOS 通过双执行架构从根源上消除这一冲突。

架构总览

┌─────────────────────────────────────────────────────────────────────┐
│                         应用层(Application)                        │
│   DispatchAgent · OperationAgent · TradingAgent · SelfHealingAgent  │
├─────────────────────────────────────────────────────────────────────┤
│                       Agent 运行时(Runtime)                        │
│      eneros-agent · eneros-reasoning · eneros-memory · eneros-tool   │
├──────────────┬──────────────────────┬───────────────────────────────┤
│  通用执行域   │     安全网关          │       实时执行域              │
│ (General)    │  (Safety Gateway)    │      (Real-Time)              │
│              │                      │                               │
│ · Agent 编排 │ · 命令拦截           │ · 保护逻辑(过流/过压/频率)   │
│ · LLM 推理   │ · 约束校验           │ · 开关操作(跳闸/合闸)        │
│ · 业务逻辑   │ · 可行性投影         │ · 紧急控制(切机/切负荷/解列) │
│ · REST API   │ · 优先级排序         │ · 实时数据采集(PMU/RTU)      │
│ · Web 服务   │ · 审计落盘           │ · P99 < 1ms · 抖动 < 100μs    │
│              │                      │                               │
│ SCHED_OTHER  │  双向桥接            │ SCHED_FIFO + CPU isolated     │
│ CFS 调度     │  无锁队列 + 共享内存 │ mlockall + huge pages         │
├──────────────┴──────────────────────┴───────────────────────────────┤
│                    电力原生内核(Power-Native Kernel)               │
│       topology · powerflow · constraint · equipment · timeseries     │
├─────────────────────────────────────────────────────────────────────┤
│                       基础设施(Infrastructure)                     │
│           eneros-os · eneros-multiregion · eneros-tenant             │
└─────────────────────────────────────────────────────────────────────┘

通用执行域(General Execution Domain)

职责

通用域承载所有非实时、可容忍抖动的工作负载:

  • Agent 生命周期管理:创建、暂停、恢复、销毁、迁移
  • AI/LLM 推理:调用本地或远程大模型完成规划、解释、自然语言交互
  • 业务逻辑处理:经济调度算法、报价策略、负荷预测后处理
  • REST/GraphQL API 服务:对外暴露 HTTP/gRPC 接口
  • Dashboard Web 服务:可视化前端与 SSE 推送
  • 报表生成:PDF/Excel/Word 导出与定时邮件

调度策略

通用域使用标准 Linux CFS(Completely Fair Scheduler)调度器,调度策略为 SCHED_OTHER(policy=0),支持多线程并发与异步 IO(tokio runtime)。无 CPU 隔离要求,可运行在任意核心。

延迟特性

指标典型值峰值说明
平均延迟1-10ms50ms含 GC、网络 IO
P99 延迟10-50ms200ms受系统负载影响
抖动±20ms±100msCFS 公平调度
吞吐多线程并行
适用场景Agent / AI / 业务可容忍抖动

关键 Crate

Crate角色
eneros-agentAgent 生命周期与编排
eneros-reasoningLLM 推理引擎
eneros-apiREST/GraphQL/SSE 服务
eneros-dashboardWeb 前端服务
eneros-report报表生成

实时执行域(Real-Time Execution Domain)

职责

实时域承载所有硬实时、要求确定性的工作负载:

  • 保护逻辑:过流、过压、欠压、频率异常、距离保护
  • 开关操作:跳闸、合闸、重合闸
  • 紧急控制:切机、切负荷、解列、AGC
  • 实时数据采集:PMU 同步相量、RTU 遥测、SOE 事件
  • 实时控制闭环:电压调节、无功补偿、有功分配

调度策略

实时域采用 SCHED_FIFO(policy=1)实时调度策略,配合以下机制保证确定性:

机制配置作用
CPU 亲和性isolated_cores=[2,3]隔离核心,避免被通用进程抢占
调度优先级priority=80(1-99)高于所有非实时线程
内存锁定mlockall(MCL_CURRENT | MCL_FUTURE)禁止换页,消除缺页中断
大页内存use_huge_pages=true减少 TLB miss
内核隔离isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3内核线程不调度到 RT 核心

延迟特性

指标典型值峰值说明
平均延迟< 500μs< 1ms含 IPC + 设备 IO
P99 延迟< 1ms< 2ms满足 NERC PRC-024
抖动< 100μs< 200μs确定性执行
吞吐单核串行
适用场景保护 / 控制硬实时

关键 Crate

Crate角色
eneros-os (rt)实时运行时配置(SCHED_FIFO、CPU 亲和性)
eneros-os (rt/shm)共享内存通道(mmap + eventfd)
eneros-gateway (rt_executor)实时命令执行器
eneros-scada实时数据采集(IEC 104、CDT、PMU)
eneros-emtp电磁暂态仿真(保护整定)

域间隔离机制

CPU 隔离

通用域与实时域运行在不同的 CPU 核心上,通过 sched_setaffinity 强制绑定:

use eneros_os::rt::{RtConfig, RtRuntime};

// 实时域配置:绑定到 CPU 2、3,SCHED_FIFO 优先级 80
let rt_config = RtConfig {
    cpus: vec![2, 3],
    priority: 80,
    lock_memory: true,
    use_huge_pages: true,
};

let rt_runtime = RtRuntime::new(rt_config);
rt_runtime.configure_current_thread()?;

内存隔离

  • mlockall:实时域锁定全部内存页,禁止换页
  • 大页内存:使用 2MB huge pages 减少 TLB miss
  • 内存池:实时域使用 eneros-perf 的 arena allocator,零运行时分配

故障隔离

  • 实时域进程崩溃不会影响通用域(独立进程)
  • 通用域 GC/分配不会阻塞实时域(独立核心)
  • 安全网关的看门狗(watchdog)监控实时域心跳,超时触发 fail-safe

通信机制

1. 无锁 SPSC 队列(同进程)

eneros-os::rt::ipc::RtCommandQueue 提供同进程内的无锁单生产者单消费者队列:

use eneros_os::rt::ipc::RtCommandQueue;
use eneros_core::Command;

// 容量 1024 的无锁命令队列
let queue: RtCommandQueue<Command, 1024> = RtCommandQueue::new();

// 通用域线程(生产者):try_push
let cmd = Command::new_switch_toggle(42, true, "operator-1");
match queue.try_push(cmd) {
    Ok(()) => println!("命令已入队"),
    Err(cmd) => println!("队列已满,命令被拒绝: {:?}", cmd),
}

// 实时域线程(消费者):try_pop
match queue.try_pop() {
    Some(cmd) => {
        println!("实时域收到命令: {:?}", cmd);
        // 确定性执行保护逻辑
    }
    None => {
        // 队列为空,立即返回(非阻塞)
    }
}

特性

  • 单次 try_push / try_pop 延迟 < 100ns
  • 无锁、无 CAS、无系统调用(仅 Release/Acquire 原子操作)
  • 可用容量 = CAPACITY - 1(保留 1 槽区分空满)
  • 线程安全要求:单一生产者 + 单一消费者

2. 共享内存通道(跨进程)

eneros-os::rt::shm::SharedMemoryChannel<T> 提供跨进程的类型化共享内存通道:

use eneros_os::rt::shm::SharedMemoryChannel;
use eneros_core::Command;

// 创建共享内存通道(容量 256,元素为 Command)
let channel = SharedMemoryChannel::<Command>::create(
    "/eneros-rt-cmd",  // 共享内存名称
    256,                // 容量
)?;

// 生产者进程(通用域):push
channel.push(&Command::new_switch_toggle(42, true, "operator-1"))?;

// 消费者进程(实时域):blocking_pop(带 eventfd 通知)
let cmd = channel.blocking_pop(Duration::from_millis(10))?;
println!("实时域收到命令: {:?}", cmd);

特性

  • 基于 mmap + eventfd + 原子索引
  • 单次 push + pop 延迟 < 5μs
  • 跨进程零拷贝(T: Copy 位拷贝)
  • 魔数校验(SHM_MAGIC = 0x454E4552)防止误用

3. 优先级命令队列(安全网关)

eneros-gateway::priority_queue::SharedPriorityCommandQueue 提供带优先级的跨域命令队列:

use eneros_gateway::priority_queue::SharedPriorityCommandQueue;
use eneros_core::{Command, CommandPriority};

let queue = std::sync::Arc::new(SharedPriorityCommandQueue::new(1024));

// 紧急命令(保护跳闸):优先级 Critical
let trip_cmd = Command::new_switch_toggle(42, false, "protection")
    .with_priority(CommandPriority::Critical);
queue.enqueue(trip_cmd);

// 普通命令(调度计划):优先级 Normal
let dispatch_cmd = Command::new_generator_setpoint(1, 50.0, "dispatcher")
    .with_priority(CommandPriority::Normal);
queue.enqueue(dispatch_cmd);

// 实时执行器按优先级出队(Critical 先于 Normal)

安全网关(Safety Gateway)

安全网关是双执行域之间的唯一桥梁,确保所有从通用域到实时域的命令都经过安全校验。

工作流程

通用域 Agent 生成命令


┌───────────────────┐
│  SafetyGateway    │
│  1. 命令拦截       │
│  2. 约束校验       │ ← eneros-constraint
│  3. 优先级排序     │
│  4. 可行性投影     │ ← 违反约束时投影为最近可行解
│  5. 审计落盘       │ ← eneros-audit (WORM)
└─────┬─────────────┘

      ├── 通过 ──► 优先级队列 ──► 实时域执行

      └── 违反 ──► 投影 / 拒绝 ──► 通知 Agent

决策矩阵

约束校验结果命令处理Agent 通知
通过转发到实时域执行ExecuteResult::Executed
轻微违反(可投影)投影为最近可行命令后执行ExecuteResult::Projected(cmd)
严重违反(不可投影)拒绝执行ExecuteResult::Rejected(reason)

完整代码示例

use eneros_gateway::{SafetyGateway, command::*, executor::DeviceCommandExecutor};
use eneros_device::DeviceManager;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. 准备设备管理器(真实设备执行)
    let device_manager = Arc::new(DeviceManager::new());

    // 2. 创建命令执行器
    let executor = Arc::new(DeviceCommandExecutor::new(device_manager.clone()));

    // 3. 创建安全网关(历史容量 1000)
    let gateway = SafetyGateway::with_executor(1000, executor);

    // 4. 注册安全校验(约束引擎、权限、限幅)
    gateway.register_safety_check(Box::new(VoltageLimitCheck::new(0.95, 1.05)));
    gateway.register_safety_check(Box::new(CurrentLimitCheck::new(1000.0)));
    gateway.register_safety_check(Box::new(PermissionCheck::new("dispatcher")));

    // 5. 构造控制命令:将开关 42 闭合
    let cmd = Command::new(
        CommandType::SwitchToggle,
        42,
        CommandPriority::Normal,
        "operator-1",
    )
    .with_parameter("closed", 1.0);

    // 6. 通过安全网关执行
    match gateway.execute_command(cmd).await {
        Ok(()) => {
            println!("命令已成功执行");
            let history = gateway.command_history();
            println!("历史记录数: {}", history.len());
        }
        Err(e) => {
            println!("命令执行失败: {}", e);
        }
    }

    // 7. 批量执行(同设备串行,不同设备并行)
    let batch = vec![
        Command::new(CommandType::SwitchToggle, 1, CommandPriority::Normal, "op")
            .with_device_id("rtu-1"),
        Command::new(CommandType::SwitchToggle, 2, CommandPriority::Normal, "op")
            .with_device_id("rtu-2"),
        Command::new(CommandType::SwitchToggle, 3, CommandPriority::Normal, "op")
            .with_device_id("rtu-1"),
    ];
    let results = gateway.execute_command_batch(batch).await;
    for (i, result) in results.iter().enumerate() {
        println!("命令 {} 执行结果: success={}", i, result.success);
    }

    Ok(())
}

/// 电压越限校验
struct VoltageLimitCheck {
    v_min: f64,
    v_max: f64,
}

impl VoltageLimitCheck {
    fn new(v_min: f64, v_max: f64) -> Self {
        Self { v_min, v_max }
    }
}

impl SafetyCheck for VoltageLimitCheck {
    fn validate(&self, command: &Command) -> eneros_core::Result<()> {
        if let Some(v) = command.parameters.get("voltage") {
            if *v < self.v_min || *v > self.v_max {
                return Err(eneros_core::EnerOSError::Constraint(format!(
                    "电压 {} 超出允许范围 [{}, {}]",
                    v, self.v_min, self.v_max
                )));
            }
        }
        Ok(())
    }

    fn name(&self) -> &str {
        "VoltageLimitCheck"
    }

    fn description(&self) -> &str {
        "校验电压是否在允许范围内"
    }
}

struct CurrentLimitCheck {
    i_max: f64,
}

impl CurrentLimitCheck {
    fn new(i_max: f64) -> Self {
        Self { i_max }
    }
}

impl SafetyCheck for CurrentLimitCheck {
    fn validate(&self, command: &Command) -> eneros_core::Result<()> {
        if let Some(i) = command.parameters.get("current") {
            if *i > self.i_max {
                return Err(eneros_core::EnerOSError::Constraint(format!(
                    "电流 {} 超过最大允许值 {}",
                    i, self.i_max
                )));
            }
        }
        Ok(())
    }

    fn name(&self) -> &str {
        "CurrentLimitCheck"
    }

    fn description(&self) -> &str {
        "校验电流是否超过最大允许值"
    }
}

struct PermissionCheck {
    required_role: String,
}

impl PermissionCheck {
    fn new(role: &str) -> Self {
        Self {
            required_role: role.to_string(),
        }
    }
}

impl SafetyCheck for PermissionCheck {
    fn validate(&self, command: &Command) -> eneros_core::Result<()> {
        if command.operator != self.required_role && command.operator != "admin" {
            return Err(eneros_core::EnerOSError::Gateway(format!(
                "操作员 {} 无权限执行此命令(需要角色: {})",
                command.operator, self.required_role
            )));
        }
        Ok(())
    }

    fn name(&self) -> &str {
        "PermissionCheck"
    }

    fn description(&self) -> &str {
        "校验操作员权限"
    }
}

Lock-Free 优化路径

为消除 parking_lot::RwLock 锁争用,EnerOS v0.42.0 引入了基于原子状态机 + SkipList 的无锁安全网关路径,将热点路径 P99 从 4-8ms 推进到 < 1ms:

Command


LockFreeGatewayState::try_acquire_device(device_id, Idle → Validating)
    │  CAS AtomicU8(per-device 原子状态机)

ParallelSafetyChecker::check_parallel(cmd)
    │  rayon::scope 并行执行所有 SafetyCheck

AtomicCommandHistory::insert(cmd)
    │  crossbeam_skiplist::SkipMap 无锁插入(按 LSN 排序)

LockFreeGatewayState::release_device(device_id, Idle)

设备状态机

状态含义
Idle0空闲,可接受新命令
Validating1正在执行安全检查
Executing2命令正在下发到设备
Cooldown3命令执行后的冷却期
use eneros_gateway::lockfree::{LockFreeGatewayState, DeviceState};

let state = LockFreeGatewayState::new();

// CAS: Idle → Validating(成功表示获取到设备)
state.try_acquire_device("rtu-1", DeviceState::Idle, DeviceState::Validating)?;

// 执行安全校验...

// 释放:Validating → Idle
state.release_device("rtu-1", DeviceState::Idle);

// 查询设备状态
match state.device_state("rtu-1") {
    DeviceState::Idle => println!("设备空闲"),
    DeviceState::Validating => println!("设备校验中"),
    DeviceState::Executing => println!("设备执行中"),
    DeviceState::Cooldown => println!("设备冷却中"),
}

调度策略对比

维度通用执行域实时执行域
调度策略SCHED_OTHER (CFS)SCHED_FIFO
优先级范围nice -20 ~ 19RT 1-99
CPU 隔离否(任意核心)是(isolated cores)
内存锁定mlockall
大页内存可选强制
抢占可被 RT 抢占仅被更高优先级 RT 抢占
异步运行时tokio (multi-thread)tokio (current-thread) 或裸轮询
内存分配标准分配器arena / pool(零运行时分配)
IO 模型async IO(epoll)轮询 / mmap(零系统调用)
典型延迟1-50ms< 1ms

实时域保证机制

1. CPU 隔离

通过内核启动参数 isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3 将 CPU 2、3 完全隔离,内核线程不会调度到这些核心。

2. SCHED_FIFO 调度

实时域线程使用 SCHED_FIFO 调度策略,优先级 80(高于所有非实时线程),不会被通用域抢占。

3. 内存锁定

mlockall(MCL_CURRENT | MCL_FUTURE) 锁定当前与未来所有内存页,禁止换页,消除缺页中断(缺页中断可达 10ms+)。

4. 大页内存

使用 2MB huge pages 减少 TLB miss,配合 madvise(MADV_HUGEPAGE) 启用透明大页。

5. 看门狗

use eneros_os::rt::watchdog::Watchdog;

// 实时域看门狗:1ms 超时
let mut watchdog = Watchdog::new(Duration::from_millis(1));
watchdog.start();

loop {
    // 实时任务循环
    execute_protection_logic();
    watchdog.kick(); // 喂狗
}

性能指标

在标准测试环境(4 核 / 8GB / Ubuntu 22.04,CPU 2-3 隔离)下的实测数据:

操作通用域延迟实时域延迟说明
命令入队(无锁队列)80ns80nsRtCommandQueue::try_push
命令出队(无锁队列)70ns70nsRtCommandQueue::try_pop
命令入队(共享内存)3.2μs3.2μs跨进程 SharedMemoryChannel::push
命令出队(共享内存)4.1μs4.1μs跨进程 blocking_pop
安全校验(单条)15μs15μs3 个 SafetyCheck
安全校验(并行)8μs8μsrayon 并行
网关端到端4ms (P99)0.8ms (P99)校验 + 队列 + 执行
保护逻辑响应< 100μs从感知到跳闸
开关操作响应< 500μs从命令到设备

适用场景

必须使用实时域

  • 继电保护逻辑(过流、距离、差动)
  • 紧急控制(低频减载、切机、解列)
  • AGC 闭环控制
  • PMU 同步相量采集
  • 开关跳闸 / 合闸操作

可使用通用域

  • 调度计划生成
  • 负荷预测
  • 电力交易报价
  • 配网规划
  • 报表生成
  • Web 可视化

混合场景(双域协同)

  • 故障自愈:通用域定位故障 + 实时域执行隔离与恢复
  • 智能调度:通用域生成计划 + 实时域下发指令
  • 数字孪生:通用域仿真推演 + 实时域镜像现场状态

相关文档