跳到主内容

v0.2.0 版本说明

EnerOS v0.2.0

发布日期:2024年2月12日 版本代号:Foundation(基石) Git Tag:v0.2.0 支持状态:内部预览(Internal Preview) Crate 总数:1 测试用例数:186

版本概述

EnerOS v0.2.0「Foundation」是项目从「工程骨架」迈向「操作系统内核」的关键一步。v0.1.0 仅提供了 eneros-core crate 的雏形,而 v0.2.0 在此基础上正式确立了内核抽象层(Kernel ABI)、系统调用接口(Syscall Interface)、统一错误码体系(Error Code System)与日志追踪系统(Tracing Integration)。这四件套构成了 EnerOS 作为「操作系统」而非「普通库」的本质区别——它们定义了用户态与内核态的边界,所有上层能力(拓扑、潮流、Agent、网关)都必须通过 syscall 访问内核资源。

内核抽象层的设计借鉴了 seL4、Hubris 等 Rust 微内核的经验,但针对电力系统场景做了深度裁剪:第一,syscall 必须满足确定性时延要求,避免 GC 或动态分配导致的尾延迟;第二,syscall 必须感知电气语义,例如 ReadBusWriteSwitch 等接口直接对应电力领域操作;第三,syscall 必须支持能力(capability)模型,每个 syscall 调用都需要携带能力令牌,实现最小权限原则。这种设计使得 EnerOS 能够在内核层面拒绝越权操作——例如一个只读 Agent 无法调用 WriteSwitch

v0.2.0 的另一项重要工作是引入 tracing 生态作为统一的日志与可观测性基础设施。电力系统是关键基础设施,运行过程的可追溯性是合规审计的硬性要求(NERC CIP、IEC 62443)。tracing 相比传统 log crate 的优势在于结构化日志(structured logging)与分布式追踪(distributed tracing),能够将一次电网操作的完整调用链记录下来,便于事后复盘与故障定位。本版本还建立了 EnerOSResult<T> 统一返回类型,所有 fallible 函数均返回该类型,错误码与 HTTP 状态码、gRPC 状态码对齐,方便后续 REST/gRPC 层接入。

关键数据

指标数值说明
Syscall 数量24基础系统调用
错误码数量48分 6 大类
代码行数4,210较 v0.1.0 增长 127%
测试用例186覆盖率 78%
内核调用延迟 P99380 ns同步 syscall
tracing 事件吞吐85 万条/秒单线程

新特性

1. 内核抽象层(Kernel ABI)

定义 EnerOS 内核与用户态之间的二进制接口(ABI)。Kernel ABI 采用 capability-based security 模型,每个内核资源(母线、开关、Agent)都关联一个能力令牌,用户态必须持有对应能力才能访问。

// crates/eneros-core/src/abi/mod.rs
//! EnerOS 内核抽象层
//!
//! 定义用户态与内核态之间的接口契约。

use std::fmt;

/// 内核能力令牌
///
/// 每个能力令牌对应一种内核资源访问权限。
/// 能力令牌不可伪造,只能由内核颁发。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Capability(pub u64);

impl Capability {
    /// 创建一个根能力(仅内核启动时使用)
    pub(crate) fn root() -> Self {
        Capability(0xFFFF_FFFF_FFFF_FFFF)
    }

    /// 检查能力是否包含某权限
    pub fn grants(&self, perm: Permission) -> bool {
        // 简化的能力检查实现
        (self.0 & perm.mask()) != 0
    }
}

/// 权限枚举
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Permission {
    Read = 0x01,
    Write = 0x02,
    Execute = 0x04,
    Admin = 0x08,
}

impl Permission {
    pub fn mask(&self) -> u64 {
        1u64 << (*self as u32)
    }
}

/// 内核对象句柄
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Handle(pub u32);

impl fmt::Display for Handle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "handle:{}", self.0)
    }
}

能力委款示例:

// 内核颁发能力给用户态 Agent
let agent_cap = kernel.grant_capability(
    resource: ResourceId::Bus(1),
    permissions: Permission::Read | Permission::Write,
    ttl: Duration::from_secs(3600),
)?;

// Agent 使用能力访问资源
let bus_state = kernel.syscall(Syscall::ReadBus {
    handle: bus_handle,
    cap: agent_cap,
})?;

2. 系统调用接口(Syscall Interface)

定义 24 个基础系统调用,覆盖资源管理、拓扑访问、Agent 控制与时间操作四大类。Syscall 采用编号 + 参数结构体的形式,便于未来扩展为真正的 trap 指令。

// crates/eneros-core/src/syscall/mod.rs
//! EnerOS 系统调用接口

/// 系统调用编号
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum SyscallId {
    // 资源管理(0x00-0x0F)
    CreateResource = 0x00,
    DestroyResource = 0x01,
    OpenResource = 0x02,
    CloseResource = 0x03,
    GrantCapability = 0x04,
    RevokeCapability = 0x05,

    // 拓扑访问(0x10-0x1F)
    ReadBus = 0x10,
    WriteBus = 0x11,
    ReadBranch = 0x12,
    ReadSwitch = 0x13,
    WriteSwitch = 0x14,
    GetTopology = 0x15,

    // Agent 控制(0x20-0x2F)
    SpawnAgent = 0x20,
    KillAgent = 0x21,
    SendMessage = 0x22,
    RecvMessage = 0x23,
    AgentSleep = 0x24,
    AgentWake = 0x25,

    // 时间操作(0x30-0x3F)
    GetTime = 0x30,
    Sleep = 0x31,
    SetTimer = 0x32,
    CancelTimer = 0x33,

    // 诊断(0x40-0x4F)
    Log = 0x40,
    Metric = 0x41,
    Trace = 0x42,
}

/// 系统调用参数与返回值
#[derive(Debug)]
pub enum Syscall {
    ReadBus { handle: Handle, cap: Capability },
    WriteBus { handle: Handle, cap: Capability, value: BusState },
    WriteSwitch { handle: Handle, cap: Capability, state: SwitchState },
    SpawnAgent { manifest: AgentManifest },
    SendMessage { target: Handle, msg: Message },
    GetTime,
    // ...
}

/// 系统调用返回值
#[derive(Debug)]
pub enum SyscallResult {
    Bus(BusState),
    Switch(SwitchState),
    AgentHandle(Handle),
    Time(SystemTime),
    Ack,
    Err(ErrorCode),
}

syscall 调用示例:

use eneros_core::syscall::{Syscall, SyscallId, SyscallResult};

// 读取母线 1 的状态
let result = kernel.syscall(Syscall::ReadBus {
    handle: bus_handle,
    cap: read_cap,
})?;

match result {
    SyscallResult::Bus(state) => {
        tracing::info!(bus_id = 1, voltage = state.voltage, "读取母线状态");
    }
    SyscallResult::Err(code) => {
        tracing::error!(code = ?code, "读取母线失败");
        return Err(code.into());
    }
    _ => unreachable!(),
}

3. 错误码体系

建立统一的错误码体系,分为 6 大类共 48 个错误码。错误码采用 E + 类别 + 编号的格式,与 HTTP 状态码、gRPC 状态码对齐。

// crates/eneros-core/src/error.rs
use thiserror::Error;

/// EnerOS 统一错误类型
#[derive(Debug, Clone, Error)]
pub enum ErrorCode {
    // 通用错误(E1xxx)
    #[error("E1001: 内部错误 - {0}")]
    Internal(String),

    #[error("E1002: 无效参数 - {0}")]
    InvalidArgument(String),

    #[error("E1003: 资源未找到 - {0}")]
    NotFound(String),

    #[error("E1004: 资源已存在 - {0}")]
    AlreadyExists(String),

    // 权限错误(E2xxx)
    #[error("E2001: 权限不足 - 需要 {required}")]
    PermissionDenied { required: Permission },

    #[error("E2002: 能力令牌无效")]
    InvalidCapability,

    #[error("E2003: 能力令牌已过期")]
    CapabilityExpired,

    // 拓扑错误(E3xxx)
    #[error("E3001: 母线不存在 - id={0}")]
    BusNotFound(u32),

    #[error("E3002: 拓扑不连通")]
    TopologyDisconnected,

    #[error("E3003: 电气岛为空")]
    EmptyIsland,

    // 潮流错误(E4xxx)
    #[error("E4001: 潮流不收敛 - 迭代 {iter} 次")]
    PowerFlowNotConverged { iter: usize },

    #[error("E4002: 雅可比矩阵奇异")]
    SingularJacobian,

    // Agent 错误(E5xxx)
    #[error("E5001: Agent 不存在 - {0}")]
    AgentNotFound(String),

    #[error("E5002: Agent 状态非法 - 当前 {current}")]
    InvalidAgentState { current: AgentState },

    // 时序错误(E6xxx)
    #[error("E6001: 时间戳越界 - {0}")]
    TimestampOutOfRange(i64),

    #[error("E6002: 时序数据不连续")]
    TimeSeriesDiscontinuous,
}

/// EnerOS 统一返回类型
pub type EnerOSResult<T> = Result<T, ErrorCode>;

错误码分类汇总:

类别前缀范围数量对应 HTTP
通用错误E1xxxE1001-E109912500/400/404/409
权限错误E2xxxE2001-E20998403/401
拓扑错误E3xxxE3001-E309910404/422
潮流错误E4xxxE4001-E40996422/500
Agent 错误E5xxxE5001-E50998404/409
时序错误E6xxxE6001-E60994422

4. 日志系统(tracing 集成)

集成 tracingtracing-subscriber,提供结构化日志与分布式追踪能力。EnerOS 定义了标准的日志字段规范,所有日志事件必须包含 trace_idspan_idagent_id(如适用)。

// crates/eneros-core/src/log.rs
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

/// 初始化 EnerOS 日志系统
pub fn init_log() {
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    let fmt_layer = fmt::layer()
        .with_target(true)
        .with_thread_ids(true)
        .with_file(true)
        .with_line_number(true)
        .json();

    tracing_subscriber::registry()
        .with(env_filter)
        .with(fmt_layer)
        .init();
}

/// 标准日志字段
pub mod fields {
    pub const TRACE_ID: &str = "trace_id";
    pub const SPAN_ID: &str = "span_id";
    pub const AGENT_ID: &str = "agent_id";
    pub const BUS_ID: &str = "bus_id";
    pub const VOLTAGE: &str = "voltage";
    pub const DURATION_US: &str = "duration_us";
}

使用示例:

use tracing::{info_span, info, instrument};

#[instrument(skip(kernel), fields(bus_id = %bus_id))]
pub fn read_bus_voltage(kernel: &Kernel, bus_id: BusId) -> EnerOSResult<f64> {
    let span = info_span!("syscall_read_bus", bus_id = %bus_id);
    let _enter = span.enter();

    let result = kernel.syscall(Syscall::ReadBus {
        handle: kernel.open(bus_id)?,
        cap: kernel.current_capability(),
    })?;

    match result {
        SyscallResult::Bus(state) => {
            tracing::info!(
                bus_id = %bus_id,
                voltage = state.voltage,
                "读取母线电压成功"
            );
            Ok(state.voltage)
        }
        SyscallResult::Err(code) => Err(code),
        _ => unreachable!(),
    }
}

改进

  • 构建速度:启用 sccache 缓存,CI 全量构建从 1m12s 降至 48s
  • 测试并行化:使用 cargo nextest,测试执行时间从 18s 降至 7s
  • 文档生成:集成 cargo-docmdbook,自动生成 API 文档与用户手册
  • 依赖审计:引入 cargo-deny,禁止 GPL/AGPL 依赖混入
  • 代码覆盖率:集成 tarpaulin,CI 中强制覆盖率不低于 75%

Bug 修复

  • 修复 Version::parse 在遇到 0.1.0-alpha.1 等预发布标签时解析失败的问题(#23)
  • 修复 Capability::grants 在多个权限组合时返回错误结果的问题(#27)
  • 修复 tracing 在 Windows 下输出 ANSI 颜色失效的问题(#31)
  • 修复 ErrorCodeDisplay 实现未包含错误码编号的问题(#34)
  • 修复 CI 中 cargo clippy 未启用 --all-targets 的问题(#38)

破坏性变更

  • eneros_core::version::Version:新增 pre: Option<String> 字段,原 Version::new 签名不变,但直接结构体构造需更新
  • eneros_core::errorEnerOSError 重命名为 ErrorCode,并实现 std::error::Error trait
  • 日志宏:移除对 log crate 的依赖,统一使用 tracing

迁移示例:

// v0.1.0(旧)
use eneros_core::error::EnerOSError;
fn foo() -> Result<(), EnerOSError> { ... }

// v0.2.0(新)
use eneros_core::error::{ErrorCode, EnerOSResult};
fn foo() -> EnerOSResult<()> { ... }

性能提升

v0.2.0 在 syscall 路径上做了多项优化,相比 v0.1.0 的「直接函数调用」基线,引入 ABI 边界后的额外开销控制在可接受范围:

操作v0.1.0v0.2.0变化
版本号解析142 ns138 ns-2.8%
Syscall(ReadBus)N/A380 ns新增
能力检查N/A18 ns新增
错误码构造24 ns16 ns-33%
tracing::info!N/A1.2 μs新增
CI 构建1m12s48s-33%
测试执行18s7s-61%

syscall 性能基准:

// benches/syscall_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use eneros_core::syscall::{Syscall, SyscallId};

fn bench_syscall_dispatch(c: &mut Criterion) {
    let kernel = Kernel::mock();
    let cap = Capability::root();
    let handle = Handle(1);

    c.bench_function("syscall_read_bus", |b| {
        b.iter(|| {
            kernel.syscall(black_box(Syscall::ReadBus {
                handle,
                cap,
            }))
        })
    });
}

criterion_group!(benches, bench_syscall_dispatch);
criterion_main!(benches);

贡献者

v0.2.0 共 5 位贡献者:

贡献者角色提交数
@eneros-foundation架构师42
@grid-rustaceanRust 工程师31
@kernel-hacker内核工程师18
@powerdomain-reviewer电力领域专家9
@observability-eng可观测性工程师7

升级指南

从 v0.1.0 升级

# 1. 拉取最新代码
git fetch origin
git checkout v0.2.0

# 2. 更新依赖
cargo update

# 3. 构建项目
cargo build --release

# 4. 运行测试(注意破坏性变更)
cargo test --all

代码迁移清单

变更项影响迁移方式
EnerOSErrorErrorCode所有错误处理代码全局替换类型名
Version 新增 pre 字段直接结构体构造改用 Version::new 或补全字段
logtracing所有日志调用log::info!tracing::info!

配置日志输出

v0.2.0 起通过环境变量配置日志级别:

# 设置日志级别
export RUST_LOG="eneros_core=debug,eneros_toplogy=info,warn"

# 输出 JSON 格式日志
export ENEROS_LOG_FORMAT=json

# 启用 OpenTelemetry 导出
export ENEROS_OTEL_ENDPOINT=http://otel-collector:4317

下一步

v0.3.0「Topology」将引入电网拓扑数据模型,Bus、Branch、Generator 等核心节点类型将正式落地。建议提前阅读 IEC 61970 CIM 标准,以便理解后续版本的数据模型设计。