双执行架构
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-10ms | 50ms | 含 GC、网络 IO |
| P99 延迟 | 10-50ms | 200ms | 受系统负载影响 |
| 抖动 | ±20ms | ±100ms | CFS 公平调度 |
| 吞吐 | 高 | 高 | 多线程并行 |
| 适用场景 | Agent / AI / 业务 | — | 可容忍抖动 |
关键 Crate
| Crate | 角色 |
|---|---|
eneros-agent | Agent 生命周期与编排 |
eneros-reasoning | LLM 推理引擎 |
eneros-api | REST/GraphQL/SSE 服务 |
eneros-dashboard | Web 前端服务 |
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)
设备状态机
| 状态 | 值 | 含义 |
|---|---|---|
Idle | 0 | 空闲,可接受新命令 |
Validating | 1 | 正在执行安全检查 |
Executing | 2 | 命令正在下发到设备 |
Cooldown | 3 | 命令执行后的冷却期 |
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 ~ 19 | RT 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 隔离)下的实测数据:
| 操作 | 通用域延迟 | 实时域延迟 | 说明 |
|---|---|---|---|
| 命令入队(无锁队列) | 80ns | 80ns | RtCommandQueue::try_push |
| 命令出队(无锁队列) | 70ns | 70ns | RtCommandQueue::try_pop |
| 命令入队(共享内存) | 3.2μs | 3.2μs | 跨进程 SharedMemoryChannel::push |
| 命令出队(共享内存) | 4.1μs | 4.1μs | 跨进程 blocking_pop |
| 安全校验(单条) | 15μs | 15μs | 3 个 SafetyCheck |
| 安全校验(并行) | 8μs | 8μs | rayon 并行 |
| 网关端到端 | 4ms (P99) | 0.8ms (P99) | 校验 + 队列 + 执行 |
| 保护逻辑响应 | — | < 100μs | 从感知到跳闸 |
| 开关操作响应 | — | < 500μs | 从命令到设备 |
适用场景
必须使用实时域
- 继电保护逻辑(过流、距离、差动)
- 紧急控制(低频减载、切机、解列)
- AGC 闭环控制
- PMU 同步相量采集
- 开关跳闸 / 合闸操作
可使用通用域
- 调度计划生成
- 负荷预测
- 电力交易报价
- 配网规划
- 报表生成
- Web 可视化
混合场景(双域协同)
- 故障自愈:通用域定位故障 + 实时域执行隔离与恢复
- 智能调度:通用域生成计划 + 实时域下发指令
- 数字孪生:通用域仿真推演 + 实时域镜像现场状态
相关文档
- 分层架构 - 五层架构详解
- 实时双执行域 - 能力详解
- 安全守卫 - 安全网关能力
- Crate 依赖关系 - Crate 依赖图