STVM OSAL 层设计方案

STVM OSAL 层设计方案

文档版本:v0.1

状态:待评审

目标:将 STVM 运行时与操作系统 API 解耦,支持 Linux / Windows / RTOS(uCOS/FreeRTOS/RT-Thread) 移植
软仓地址:https://gitee.com/galaxy_0/stvm


0. 前置调研:当前 OS 依赖面

0.1 核心运行时(src/)依赖清单

文件 依赖 是否必须进 OSAL 说明
cpu_affinity.cpp(file:///e:/work/dev/stvm/src/platform/cpu_affinity.cpp) sched.h::sched_setaffinity, unistd.h::sysconf, Windows.h::SetThreadAffinityMask 是 唯一的真实 OS API 调用点;但宿主可配置 cpu_affinity = -1 完全跳过
program.cpp(file:///e:/work/dev/stvm/src/program.cpp) std::fopen/fread/fwrite/fseek/ftell/fclose 是 BytecodeProgram 序列化/反序列化;裸机 RTOS 上文件系统可能不存在
VM.cpp L129(file:///e:/work/dev/stvm/src/vm/VM.cpp#L129) std::fopen(trace 输出) 是 可选调试功能,但当前直接调 C 标准库
st_lex.cpp(file:///e:/work/dev/stvm/src/parser/generated/st_lex.cpp) exit(), fread(), fwrite() 否(长期) flex 预生成代码,仅旧 ST 模式用;新 IEC 模式走手写 IecLexer.cpp;长期应删掉旧 ST 管线
iec_parser_driver.cpp(file:///e:/work/dev/stvm/src/parser/iec_parser_driver.cpp) std::ifstream 否 编译期依赖,不进运行时库
其余全部 无 - VM / Scheduler / IecCodegen / IecSymbolTable / VM.cpp / syscall / disassembler 零 OS 依赖

0.2 确认:核心运行时没有以下依赖

  • ❌ std::thread / pthread / 任何多线程 API
  • ❌ std::chrono / clock_gettime / gettimeofday / 任何真实时钟
  • ❌ std::mutex / std::atomic / 任何同步原语
  • ❌ dlfcn.h / LoadLibrary / 动态加载
  • ❌ 信号 / IPC / socket

根本原因 :Scheduler 是单线程顺序扫描 架构,clock_us_ 是虚拟时钟手动推进,不查询 OS。


1. 设计哲学

1.1 三原则

#mermaid-svg-oXfVnraeuAqKWCCM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oXfVnraeuAqKWCCM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oXfVnraeuAqKWCCM .error-icon{fill:#552222;}#mermaid-svg-oXfVnraeuAqKWCCM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oXfVnraeuAqKWCCM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oXfVnraeuAqKWCCM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oXfVnraeuAqKWCCM .marker.cross{stroke:#333333;}#mermaid-svg-oXfVnraeuAqKWCCM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oXfVnraeuAqKWCCM p{margin:0;}#mermaid-svg-oXfVnraeuAqKWCCM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oXfVnraeuAqKWCCM .cluster-label text{fill:#333;}#mermaid-svg-oXfVnraeuAqKWCCM .cluster-label span{color:#333;}#mermaid-svg-oXfVnraeuAqKWCCM .cluster-label span p{background-color:transparent;}#mermaid-svg-oXfVnraeuAqKWCCM .label text,#mermaid-svg-oXfVnraeuAqKWCCM span{fill:#333;color:#333;}#mermaid-svg-oXfVnraeuAqKWCCM .node rect,#mermaid-svg-oXfVnraeuAqKWCCM .node circle,#mermaid-svg-oXfVnraeuAqKWCCM .node ellipse,#mermaid-svg-oXfVnraeuAqKWCCM .node polygon,#mermaid-svg-oXfVnraeuAqKWCCM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oXfVnraeuAqKWCCM .rough-node .label text,#mermaid-svg-oXfVnraeuAqKWCCM .node .label text,#mermaid-svg-oXfVnraeuAqKWCCM .image-shape .label,#mermaid-svg-oXfVnraeuAqKWCCM .icon-shape .label{text-anchor:middle;}#mermaid-svg-oXfVnraeuAqKWCCM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oXfVnraeuAqKWCCM .rough-node .label,#mermaid-svg-oXfVnraeuAqKWCCM .node .label,#mermaid-svg-oXfVnraeuAqKWCCM .image-shape .label,#mermaid-svg-oXfVnraeuAqKWCCM .icon-shape .label{text-align:center;}#mermaid-svg-oXfVnraeuAqKWCCM .node.clickable{cursor:pointer;}#mermaid-svg-oXfVnraeuAqKWCCM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oXfVnraeuAqKWCCM .arrowheadPath{fill:#333333;}#mermaid-svg-oXfVnraeuAqKWCCM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oXfVnraeuAqKWCCM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oXfVnraeuAqKWCCM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oXfVnraeuAqKWCCM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oXfVnraeuAqKWCCM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oXfVnraeuAqKWCCM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oXfVnraeuAqKWCCM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oXfVnraeuAqKWCCM .cluster text{fill:#333;}#mermaid-svg-oXfVnraeuAqKWCCM .cluster span{color:#333;}#mermaid-svg-oXfVnraeuAqKWCCM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oXfVnraeuAqKWCCM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oXfVnraeuAqKWCCM rect.text{fill:none;stroke-width:0;}#mermaid-svg-oXfVnraeuAqKWCCM .icon-shape,#mermaid-svg-oXfVnraeuAqKWCCM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oXfVnraeuAqKWCCM .icon-shape p,#mermaid-svg-oXfVnraeuAqKWCCM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oXfVnraeuAqKWCCM .icon-shape .label rect,#mermaid-svg-oXfVnraeuAqKWCCM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oXfVnraeuAqKWCCM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oXfVnraeuAqKWCCM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oXfVnraeuAqKWCCM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用
链接时选择
后端实现
Linux 后端

sched_setaffinity

fopen/fread/fwrite
Windows 后端

SetThreadAffinityMask

fopen/_wfopen
FreeRTOS/RT-Thread 后端

(无亲和性、可选文件系统)
裸机 后端

(空实现、全部 no-op)
OSAL 抽象层

(唯一依赖入口)
时基 getTimeUs
文件 IO open/read/write/close
CPU 亲和性(可选)
STVM 核心运行时

(零 OS 依赖)
VM 执行器
Scheduler 顺序扫描
IecCodegen
符号表/类型检查
BytecodeProgram 内存模型

原则 含义 影响
极简接口 OSAL 只暴露 STVM 真正用到的 API------3 个函数 不是套 OS 的大而全 HAL,只做 STVM 需要的
可选降级 每个 OSAL API 都有空实现 fallback(返回 0、false、或不做事) 裸机系统上链接空实现就能跑,不依赖 OS
单向依赖 STVM → OSAL → 后端;OSAL 不知道后端的存在 核心 include stvm/osal.hpp,不 include <sched.h> 或 <Windows.h>

1.2 核心架构保持不变

复制代码
┌──────────────────────────────────────────────────────────┐
│  宿主应用(stvm-run / Scheduler 集成 / 你的 FreeRTOS 任务)  │
│  - 初始化 OSAL                                            │
│  - 创建 Scheduler + Task                                  │
│  - 主循环: while(running) { sched.runOnce(); osal.sleep(); } │
└────────────────────┬─────────────────────────────────────┘
                     │ 唯一接口
┌────────────────────▼─────────────────────────────────────┐
│  STVM 核心运行时(无 OS 依赖)                              │
│  Scheduler → VM → opcodes → SvcContext                    │
│  clock_us_ 由宿主推进或 Scheduler 自推进                    │
└────────────────────┬─────────────────────────────────────┘
                     │
┌────────────────────▼─────────────────────────────────────┐
│  OSAL(3 个 API)                                         │
│  osal_get_time_us()    --- 虚拟时基(可选,核心不用)           │
│  osal_file_*()         --- 文件读写(BytecodeProgram 序列化)    │
│  osal_set_affinity()   --- CPU 绑定(可选,默认禁用)          │
└──────────────────────────────────────────────────────────┘

1.3 clock_us_ 处理

#mermaid-svg-JmFPmr2QYOC3Uoqk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JmFPmr2QYOC3Uoqk .error-icon{fill:#552222;}#mermaid-svg-JmFPmr2QYOC3Uoqk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JmFPmr2QYOC3Uoqk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JmFPmr2QYOC3Uoqk .marker.cross{stroke:#333333;}#mermaid-svg-JmFPmr2QYOC3Uoqk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JmFPmr2QYOC3Uoqk p{margin:0;}#mermaid-svg-JmFPmr2QYOC3Uoqk defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-JmFPmr2QYOC3Uoqk g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-JmFPmr2QYOC3Uoqk g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-JmFPmr2QYOC3Uoqk g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-JmFPmr2QYOC3Uoqk g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-JmFPmr2QYOC3Uoqk g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-JmFPmr2QYOC3Uoqk .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-JmFPmr2QYOC3Uoqk .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-JmFPmr2QYOC3Uoqk .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JmFPmr2QYOC3Uoqk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JmFPmr2QYOC3Uoqk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JmFPmr2QYOC3Uoqk .edgeLabel .label text{fill:#333;}#mermaid-svg-JmFPmr2QYOC3Uoqk .label div .edgeLabel{color:#333;}#mermaid-svg-JmFPmr2QYOC3Uoqk .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-JmFPmr2QYOC3Uoqk .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-JmFPmr2QYOC3Uoqk .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-JmFPmr2QYOC3Uoqk .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-JmFPmr2QYOC3Uoqk .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-JmFPmr2QYOC3Uoqk .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JmFPmr2QYOC3Uoqk #statediagram-barbEnd{fill:#333333;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .cluster-label,#mermaid-svg-JmFPmr2QYOC3Uoqk .nodeLabel{color:#131300;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-JmFPmr2QYOC3Uoqk .note-edge{stroke-dasharray:5;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-note text{fill:black;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram-note .nodeLabel{color:black;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagram .edgeLabel{color:red;}#mermaid-svg-JmFPmr2QYOC3Uoqk #dependencyStart,#mermaid-svg-JmFPmr2QYOC3Uoqk #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-JmFPmr2QYOC3Uoqk .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JmFPmr2QYOC3Uoqk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 默认(核心内置)
宿主初始化时设置回调
Scheduler 内部
runOnce() 手动推进

周期任务 next_run_us 比较
osal_get_time_us() → 真实时钟
每个 runOnce() 前读一次

用于 benchmark / 有真实时间要求的宿主
零 OS 依赖

STVM 默认模式

Scheduler 自己管理时间
可选启用

宿主设置 SchedulerConfig.clock_source


2. OSAL 接口定义(v1)

2.1 头文件:include/stvm/osal.hpp

cpp 复制代码
#pragma once

#include <cstdint>
#include <cstddef>

namespace stvm {
namespace osal {

// ============================================================================
// 时基 API ------ 获取系统微秒时间戳
// 返回值语义:单调递增微秒计数(可折回,宿主需自行处理 wrap)
// 默认实现:空时钟(始终返回 0),核心 Scheduler 自己推进虚拟时钟
// 启用方法:链接时选择 Linux/Windows/RTOS 后端
// ============================================================================
uint64_t get_time_us();

// ============================================================================
// 文件 IO ------ 抽象 BytecodeProgram 序列化所需的最小文件操作
// 设计决策:不返回 FILE*,用 opaque handle(uintptr_t)
//   - 裸机 RTOS 上无文件系统时,后端可返回 0 表示失败
//   - Windows/Linux 返回真实 FILE* 但 STVM 不感知
// ============================================================================
using FileHandle = uintptr_t;
constexpr FileHandle INVALID_FILE = 0;

enum class OpenMode {
    ReadBinary,
    WriteBinary,
};

FileHandle file_open(const char* path, OpenMode mode);
size_t    file_read (FileHandle h, void* buf, size_t n);
size_t    file_write(FileHandle h, const void* buf, size_t n);
int       file_seek (FileHandle h, long offset, int whence);  // whence: SEEK_SET/SEEK_CUR/SEEK_END
long      file_tell (FileHandle h);
int       file_close(FileHandle h);

// ============================================================================
// CPU 亲和性 ------ 绑定/解绑当前执行线程(进程/任务)到指定 CPU 核心
// 设计决策:全部可选------返回 false 表示后端不支持或失败,STVM 优雅降级
//   - cfg_.cpu_affinity = -1 → OSAL 根本不被调用
//   - 裸机后端:始终返回 false,不做任何事
// ============================================================================
bool set_thread_affinity(int core_id);     // 绑定到核心 core_id(-1 表示取消绑定)
bool reset_thread_affinity();              // 恢复默认(不限制核心)
int  get_core_count();                     // 返回可用核心数(裸机返回 1)

// ============================================================================
// 调度优先级(实时 OS 专用)------ 设置当前线程/任务的调度策略和优先级
// 普通 Linux / Windows 通常返回 false;PREEMPT_RT / Xenomai / RT-Thread / VxWorks 支持
// policy 枚举:
//   普通 Linux :  SCHED_OTHER (0), SCHED_FIFO (1), SCHED_RR (2)
//   RTOS       :  平台自定义(如 FreeRTOS 用任务优先级数字)
// priority: 0..max_prio(普通 Linux 0=默认;PREEMPT_RT 99=最高;RTOS 数字越大越高)
// 宿主调用顺序建议:先 set_thread_affinity → 再 set_thread_priority
// ============================================================================
enum class SchedPolicy {
    Other,      // 默认分时调度(Linux SCHED_OTHER / 普通线程)
    Fifo,       // 固定优先级实时(Linux SCHED_FIFO)
    Rr,         // 优先级轮转实时(Linux SCHED_RR)
};
bool set_thread_priority(SchedPolicy policy, int priority);
bool reset_thread_priority();    // 恢复默认调度

} // namespace osal
} // namespace stvm

2.2 头文件:include/stvm/osal_fwd.hpp(可选前向声明,减轻 include 负担)

cpp 复制代码
#pragma once
#include <cstdint>
namespace stvm { namespace osal { using FileHandle = uintptr_t; } }

3. 后端实现

3.1 文件布局

复制代码
src/platform/osal/
├── osal.hpp            ← 已在 include/stvm/osal.hpp(对外接口)
├── osal.cpp            ← 默认空实现(fallback,任何 OS 都能链接)
├── osal_linux.cpp      ← Linux / WSL / Android(含 PREEMPT_RT / Xenomai 自动检测)
├── osal_windows.cpp    ← Windows (MSVC / MinGW) 适配
├── osal_freertos.cpp   ← FreeRTOS (V10+) 适配
├── osal_rtthread.cpp   ← RT-Thread (v4.x+) 适配
├── osal_sylixos.cpp    ← SylixOS (国产 RTOS) 适配
└── osal_vxworks.cpp    ← Wind River VxWorks (6.x / 7.x) 适配

每个后端独立一个 .cpp 文件,内部用 #ifdef 保护平台相关 include,编译时只能有一个后端被链接 (CMake 自动处理)。Linux 后端内部自动检测 PREEMPT_RT(/sys/kernel/realtime)和 Xenomai(__XENO_SKIN__),启用更高精度时基和 SCHED_FIFO 调度策略。

3.2 默认空实现(osal.cpp)

cpp 复制代码
#include "stvm/osal.hpp"
#include <cstdio>

namespace stvm::osal {

// 时基:始终返回 0(Scheduler 自己推进虚拟时钟)
uint64_t get_time_us() { return 0; }

// 文件:返回 INVALID_FILE ------ 宿主必须在编译时链接正确后端
FileHandle file_open(const char*, OpenMode) { return INVALID_FILE; }
size_t file_read (FileHandle, void*, size_t) { return 0; }
size_t file_write(FileHandle, const void*, size_t) { return 0; }
int    file_seek (FileHandle, long, int) { return -1; }
long   file_tell (FileHandle) { return -1; }
int    file_close(FileHandle) { return -1; }

// CPU 亲和性:全部返回 false/0(优雅降级)
bool set_thread_affinity(int)    { return false; }
bool reset_thread_affinity()     { return false; }
int  get_core_count()            { return 1; }

// 调度优先级:返回 false(后端不支持)
bool set_thread_priority(SchedPolicy, int) { return false; }
bool reset_thread_priority()               { return false; }

} // namespace stvm::osal

3.3 Linux 后端(osal_linux.cpp)

覆盖 :普通 Linux(Ubuntu/Debian/CentOS/Alpine)、WSL 1/2、Android、PREEMPT_RT 实时补丁 、Xenomai 双内核。

cpp 复制代码
#include "stvm/osal.hpp"

#include <chrono>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <fstream>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
#include <sys/sysinfo.h>
#include <sys/time.h>

namespace stvm::osal {

// ---- 实时内核自动检测 ----
// PREEMPT_RT: /sys/kernel/realtime 存在且内容 = "1"
// Xenomai   : __XENO_SKIN__ 编译宏(Xenomai 内核自动定义)
// 我们缓存一次检测结果,整个后端用同一份
static bool detectRtKernel() {
#ifdef __XENO_SKIN__
    return true;  // Xenomai 编译宏
#else
    std::ifstream rt("/sys/kernel/realtime");
    if (!rt) return false;
    int v = 0; rt >> v;
    return (v == 1);
#endif
}
static const bool kIsRtKernel = detectRtKernel();

// ---- 时基 ----
// 普通 Linux : clock_gettime(CLOCK_MONOTONIC)
// PREEMPT_RT : clock_gettime(CLOCK_MONOTONIC_RAW) --- 不受 NTP 调整影响,硬实时精度
// Xenomai    : clock_gettime(CLOCK_MONOTONIC) 已通过 RTDM 桥接到底层 rt_clock
uint64_t get_time_us() {
    struct timespec ts;
    clockid_t clk = kIsRtKernel ? CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC;
    if (clock_gettime(clk, &ts) != 0) {
        // fallback:万一 CLOCK_MONOTONIC_RAW 在旧内核上不存在
        clock_gettime(CLOCK_MONOTONIC, &ts);
    }
    return static_cast<uint64_t>(ts.tv_sec)  * 1000000ULL
         + static_cast<uint64_t>(ts.tv_nsec) /   1000ULL;
}

// ---- 文件 IO ----
FileHandle file_open(const char* path, OpenMode mode) {
    const char* m = (mode == OpenMode::ReadBinary) ? "rb" : "wb";
    return reinterpret_cast<FileHandle>(std::fopen(path, m));
}
size_t file_read (FileHandle h, void* buf, size_t n) {
    return std::fread(buf, 1, n, reinterpret_cast<FILE*>(h));
}
size_t file_write(FileHandle h, const void* buf, size_t n) {
    return std::fwrite(buf, 1, n, reinterpret_cast<FILE*>(h));
}
int file_seek(FileHandle h, long off, int whence) {
    return std::fseek(reinterpret_cast<FILE*>(h), off, whence);
}
long file_tell(FileHandle h) {
    return std::ftell(reinterpret_cast<FILE*>(h));
}
int file_close(FileHandle h) {
    return std::fclose(reinterpret_cast<FILE*>(h));
}

// ---- CPU 亲和性 ----
bool set_thread_affinity(int core_id) {
    if (core_id < 0) return reset_thread_affinity();
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset);
    pthread_t tid = pthread_self();
    return pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset) == 0;
}
bool reset_thread_affinity() {
    int n = std::max(1, get_core_count());
    cpu_set_t cpuset; CPU_ZERO(&cpuset);
    for (int i = 0; i < n; i++) CPU_SET(i, &cpuset);
    return pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0;
}
int get_core_count() {
    long n = sysconf(_SC_NPROCESSORS_ONLN);
    return (n > 0) ? static_cast<int>(n) : 1;
}

// ---- 调度优先级(PREEMPT_RT / Xenomai 真工作;普通 Linux 返回 false)----
bool set_thread_priority(SchedPolicy policy, int priority) {
    int sched = SCHED_OTHER;
    int prio  = 0;

    switch (policy) {
        case SchedPolicy::Fifo: sched = SCHED_FIFO; prio = priority; break;
        case SchedPolicy::Rr:   sched = SCHED_RR;   prio = priority; break;
        default:                sched = SCHED_OTHER; prio = 0;
    }

    // PREEMPT_RT / Xenomai:SCHED_FIFO 优先级 1..99 合法
    // 普通 Linux:也能设,但不被真正调度(返回 false 让宿主知道这是非实时环境)
    struct sched_param sp;
    sp.sched_priority = (sched == SCHED_OTHER) ? 0 : priority;

    if (pthread_setschedparam(pthread_self(), sched, &sp) != 0) {
        return false;  // 普通 Linux 无 RT 权限时会 EPERM
    }

    // 如果宿主要求 Fifo/Rr 但后端不是 RT 内核,警告(通过 errno 或日志)
    // 这里简单返回 true------普通 Linux 下调 set_thread_priority(SchedPolicy::Fifo, 50)
    // 也能成功(需要 sudo 或 RLIMIT_RTPRIO 限制),但调度效果非实时
    return true;
}

bool reset_thread_priority() {
    struct sched_param sp{0};
    return pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp) == 0;
}

} // namespace stvm::osal

3.4 Windows 后端(osal_windows.cpp)

cpp 复制代码
#include "stvm/osal.hpp"
#include <windows.h>
#include <cstdio>

namespace stvm::osal {

uint64_t get_time_us() {
    LARGE_INTEGER freq, now;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&now);
    return static_cast<uint64_t>(
        static_cast<double>(now.QuadPart) * 1000000.0 / freq.QuadPart);
}

FileHandle file_open(const char* path, OpenMode mode) {
    const char* m = (mode == OpenMode::ReadBinary) ? "rb" : "wb";
    return reinterpret_cast<FileHandle>(std::fopen(path, m));
}
// ... 与 Linux 相同 ...

bool set_thread_affinity(int core_id) {
    DWORD_PTR mask = static_cast<DWORD_PTR>(1) << core_id;
    return SetThreadAffinityMask(GetCurrentThread(), mask) != 0;
}
bool reset_thread_affinity() {
    return SetThreadAffinityMask(GetCurrentThread(), 0) != 0;
}
int get_core_count() {
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    return static_cast<int>(si.dwNumberOfProcessors);
}

} // namespace stvm::osal

3.5 FreeRTOS 后端(osal_freertos.cpp)

平台假设 :FreeRTOS V10+(configUSE_CPU_IDLE 或 configMAX_PRIORITIES 可用),SysTick 或通用定时器作为 tick 源。

文件系统 :可选 FatFs / LittleFS 挂载,未挂载时 file_* 返回 INVALID_FILE。

亲和性 :FreeRTOS 原生无 SMP 亲和性 API(任务可以在任意核心迁移),保持 false。

cpp 复制代码
#include "stvm/osal.hpp"

#include "FreeRTOS.h"
#include "task.h"
#include "portmacro.h"
#include <cstdio>

#if defined(INCLUDE_vTaskSuspend)
#include <timers.h>
#endif

namespace stvm::osal {

// ---- 时基 ----
// FreeRTOS tick 是 uint32_t,通常 1ms/tick;转为微秒
// tick 溢出(~49 天 @1ms)时,上层 Scheduler 虚拟时钟不受影响
uint64_t get_time_us() {
    uint32_t ticks = xTaskGetTickCount();
    // 避免浮点:tick_us = (ticks * 1000) * portTICK_PERIOD_MS
    // 假设 tick = 1ms(configTICK_RATE_HZ = 1000)
    // 若用户 tick 不同,改 STVM_FREERTOS_TICK_US 即可
    constexpr uint32_t TICK_US = STVM_FREERTOS_TICK_US; // 默认 1000
    return static_cast<uint64_t>(ticks) * TICK_US;
}

// ---- 文件 IO(可选 FatFs / LittleFS)----
// FreeRTOS 本身无文件系统;宿主若挂载了 FatFs,在此实现
// 以下用 POSIX 风格 open/read/write/close(SylixOS/VxWorks 也兼容)
#if defined(STVM_FREERTOS_HAS_FATFS)
# include <fcntl.h>
# include <unistd.h>
# include <sys/stat.h>

FileHandle file_open(const char* path, OpenMode mode) {
    int flags = (mode == OpenMode::ReadBinary) ? O_RDONLY
                                                : (O_WRONLY | O_CREAT | O_TRUNC);
    return static_cast<FileHandle>(::open(path, flags, 0644));
}
size_t file_read (FileHandle h, void* buf, size_t n) { return ::read(static_cast<int>(h), buf, n); }
size_t file_write(FileHandle h, const void* buf, size_t n) { return ::write(static_cast<int>(h), buf, n); }
int    file_seek (FileHandle h, long off, int whence) { return ::lseek(static_cast<int>(h), off, whence); }
long   file_tell (FileHandle h) { return ::lseek(static_cast<int>(h), 0, SEEK_CUR); }
int    file_close(FileHandle h) { return ::close(static_cast<int>(h)); }
#else
// 无文件系统------BytecodeProgram 必须在内存中构造
FileHandle file_open(const char*, OpenMode) { return INVALID_FILE; }
size_t file_read (FileHandle, void*, size_t) { return 0; }
size_t file_write(FileHandle, const void*, size_t) { return 0; }
int    file_seek (FileHandle, long, int) { return -1; }
long   file_tell (FileHandle) { return -1; }
int    file_close(FileHandle) { return -1; }
#endif

// ---- CPU 亲和性 ----
// FreeRTOS SMP 支持多核,但没有"将任务绑定到特定核心"的通用 API
// (任务可在任意就绪核心上运行)。返回 false 表示不支持。
// 若宿主有 FreeRTOS 私有补丁(如 XCore-Ready affinity patch),可在此扩展。
bool set_thread_affinity(int)    { return false; }
bool reset_thread_affinity()     { return false; }
int  get_core_count() {
#if defined(configNUMBER_OF_CORES) && configNUMBER_OF_CORES > 1
    return configNUMBER_OF_CORES;
#else
    return 1;  // 单核 FreeRTOS
#endif
}

} // namespace stvm::osal

3.6 RT-Thread 后端(osal_rtthread.cpp)

平台假设 :RT-Thread v4.x+(rt-thread.h),启用 RT_USING_TICK_HOOK 或直接用 rt_tick_get()。

文件系统 :RT-Thread 内置 dfs(device file system)+ FatFs/SFUD 组合,默认启用 。

亲和性 :RT-Thread SMP 有 rt_thread_setaffinity()(v5.x+),可以真实实现。

cpp 复制代码
#include "stvm/osal.hpp"

#include "rt-thread.h"
#include "rtdevice.h"
#include "dfs_posix.h"   // POSIX 兼容层: open/read/write/lseek/close

namespace stvm::osal {

// ---- 时基 ----
// rt_tick_get() 返回 rt_tick_t(uint32_t 或 uint64_t,取决于 RT_TICK_LONG)
// RT_TICK_PER_SECOND 通常 1000
uint64_t get_time_us() {
    rt_tick_t t = rt_tick_get();
    constexpr uint64_t tick_us = 1000000ULL / RT_TICK_PER_SECOND;
    return static_cast<uint64_t>(t) * tick_us;
}

// ---- 文件 IO(RT-Thread POSIX 层)----
// RT-Thread dfs 内置 POSIX 兼容 API:open/read/write/lseek/close
FileHandle file_open(const char* path, OpenMode mode) {
    int flags = (mode == OpenMode::ReadBinary) ? O_RDONLY
                                                : (O_WRONLY | O_CREAT | O_TRUNC);
    return static_cast<FileHandle>(::open(path, flags, 0));
}
size_t file_read (FileHandle h, void* buf, size_t n) {
    return static_cast<size_t>(::read(static_cast<int>(h), buf, n));
}
size_t file_write(FileHandle h, const void* buf, size_t n) {
    return static_cast<size_t>(::write(static_cast<int>(h), buf, n));
}
int file_seek(FileHandle h, long off, int whence) {
    return static_cast<int>(::lseek(static_cast<int>(h), off, whence));
}
long file_tell(FileHandle h) { return ::lseek(static_cast<int>(h), 0, SEEK_CUR); }
int  file_close(FileHandle h) { return ::close(static_cast<int>(h)); }

// ---- CPU 亲和性 ----
// RT-Thread v5.x+ 提供 rt_thread_setaffinity()(SMP 绑定到特定核心)
// v4.x 无此 API,退回 false
#if defined(RT_THREAD_SET_AFFINITY)  // 用户 Kconfig 选项
bool set_thread_affinity(int core_id) {
    if (core_id < 0 || core_id >= RT_CPUS_NR) return false;
    rt_thread_t th = rt_thread_self();
    rt_cpumask_t mask = (rt_cpumask_t)1 << core_id;
    return rt_thread_setaffinity(th, mask, NULL) == RT_EOK;
}
bool reset_thread_affinity() {
    rt_thread_t th = rt_thread_self();
    rt_cpumask_t mask = 0;
    for (int i = 0; i < RT_CPUS_NR; ++i) mask |= (rt_cpumask_t)1 << i;
    return rt_thread_setaffinity(th, mask, NULL) == RT_EOK;
}
#else
bool set_thread_affinity(int) { return false; }
bool reset_thread_affinity()  { return false; }
#endif

int get_core_count() {
#if defined(RT_CPUS_NR) && RT_CPUS_NR > 1
    return RT_CPUS_NR;
#else
    return 1;
#endif
}

} // namespace stvm::osal

3.7 SylixOS 后端(osal_sylixos.cpp)

平台假设 :SylixOS(国产 RTOS,x86/ARM/MIPS),POSIX API 兼容层完整。

文件系统 :SylixOS 原生文件系统,支持 open/read/write/lseek/close。

亲和性 :SylixOS 提供 pthread_setaffinity_np()(Linux 兼容 API),可以真实实现。

cpp 复制代码
#include "stvm/osal.hpp"

#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>

namespace stvm::osal {

// ---- 时基 ----
// SylixOS 有 POSIX clock_gettime(CLOCK_MONOTONIC)
uint64_t get_time_us() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return static_cast<uint64_t>(ts.tv_sec) * 1000000ULL
         + static_cast<uint64_t>(ts.tv_nsec) / 1000ULL;
}

// ---- 文件 IO(完全 POSIX 兼容)----
FileHandle file_open(const char* path, OpenMode mode) {
    int flags = (mode == OpenMode::ReadBinary) ? O_RDONLY
                                                : (O_WRONLY | O_CREAT | O_TRUNC);
    return static_cast<FileHandle>(::open(path, flags, 0644));
}
size_t file_read (FileHandle h, void* buf, size_t n) {
    return static_cast<size_t>(::read(static_cast<int>(h), buf, n));
}
size_t file_write(FileHandle h, const void* buf, size_t n) {
    return static_cast<size_t>(::write(static_cast<int>(h), buf, n));
}
int file_seek(FileHandle h, long off, int whence) {
    return static_cast<int>(::lseek(static_cast<int>(h), off, whence));
}
long file_tell(FileHandle h) { return ::lseek(static_cast<int>(h), 0, SEEK_CUR); }
int  file_close(FileHandle h) { return ::close(static_cast<int>(h)); }

// ---- CPU 亲和性(POSIX 兼容)----
// SylixOS 用 pthread_setaffinity_np,与 Linux 相同 API
bool set_thread_affinity(int core_id) {
    cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset);
    pthread_t tid = pthread_self();
    return pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset) == 0;
}
bool reset_thread_affinity() {
    cpu_set_t cpuset; CPU_ZERO(&cpuset);
    long n = sysconf(_SC_NPROCESSORS_ONLN);
    if (n <= 0) n = 1;
    for (long i = 0; i < n; ++i) CPU_SET(i, &cpuset);
    return pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0;
}
int get_core_count() {
    long n = sysconf(_SC_NPROCESSORS_ONLN);
    return (n > 0) ? static_cast<int>(n) : 1;
}

} // namespace stvm::osal

3.8 VxWorks 后端(osal_vxworks.cpp)

平台假设 :VxWorks 6.x(Tornado)或 VxWorks 7(Wind River)。

文件系统 :VxWorks 6 有 fopen/fread/fwrite(stdio 层);VxWorks 7 同时有 POSIX 和 stdio。

亲和性 :VxWorks 6 无亲和性;VxWorks 7 提供 taskCpuSetAffinity() 真实实现。

cpp 复制代码
#include "stvm/osal.hpp"

// VxWorks 6: vxWorks.h, taskLib.h, tickLib.h, fioLib.h
// VxWorks 7: vxWorks.h, taskV2.h, clockLib.h, fioLib.h
#if defined(VX_VERSION) && VX_VERSION >= 0x70000000   // VxWorks 7
# include <vxWorks.h>
# include <taskLib.h>
# include <tickLib.h>
# include <fioLib.h>
# include <stdio.h>
#else                                                  // VxWorks 6
# include <vxWorks.h>
# include <taskLib.h>
# include <tickLib.h>
# include <fioLib.h>
# include <stdio.h>
#endif

namespace stvm::osal {

// ---- 时基 ----
// VxWorks tickLib 提供 tickGet();tick 频率由 sysClkRateGet() 查询
uint64_t get_time_us() {
    ULONG ticks = tickGet();
    int   ticks_per_sec = sysClkRateGet();  // 通常 1000
    if (ticks_per_sec <= 0) ticks_per_sec = 1000;
    // 先除后乘避免溢出:(ticks / rate) * 1e6 + (ticks % rate) * 1e6 / rate
    uint64_t sec = static_cast<uint64_t>(ticks) / ticks_per_sec;
    uint64_t rem = static_cast<uint64_t>(ticks) % ticks_per_sec;
    return sec * 1000000ULL + rem * 1000000ULL / ticks_per_sec;
}

// ---- 文件 IO(stdio 层,VxWorks 6/7 通用)----
FileHandle file_open(const char* path, OpenMode mode) {
    const char* m = (mode == OpenMode::ReadBinary) ? "rb" : "wb";
    return reinterpret_cast<FileHandle>(fopen(path, m));
}
size_t file_read (FileHandle h, void* buf, size_t n) {
    return fread(buf, 1, n, reinterpret_cast<FILE*>(h));
}
size_t file_write(FileHandle h, const void* buf, size_t n) {
    return fwrite(buf, 1, n, reinterpret_cast<FILE*>(h));
}
int file_seek(FileHandle h, long off, int whence) {
    // VxWorks whence: SEEK_SET/SEEK_CUR/SEEK_END ------ 与 POSIX 一致
    return fseek(reinterpret_cast<FILE*>(h), off, whence);
}
long file_tell(FileHandle h) { return ftell(reinterpret_cast<FILE*>(h)); }
int  file_close(FileHandle h) { return fclose(reinterpret_cast<FILE*>(h)); }

// ---- CPU 亲和性 ----
// VxWorks 6: taskCpuSetAffinity() 不存在;返回 false
// VxWorks 7: 提供 taskCpuSetAffinity(taskId, cpuset)
#if defined(VX_VERSION) && VX_VERSION >= 0x70000000
bool set_thread_affinity(int core_id) {
    cpuset_t mask = (cpuset_t)1 << core_id;
    TASK_ID tid = taskIdSelf();
    return taskCpuSetAffinity(tid, mask, NULL) == OK;
}
bool reset_thread_affinity() {
    cpuset_t mask = 0;
    for (int i = 0; i < _VX_MAX_CPU_COUNT; ++i) mask |= (cpuset_t)1 << i;
    return taskCpuSetAffinity(taskIdSelf(), mask, NULL) == OK;
}
#else
bool set_thread_affinity(int) { return false; }
bool reset_thread_affinity()  { return false; }
#endif

int get_core_count() {
#if defined(VX_VERSION) && VX_VERSION >= 0x70000000
    // VxWorks 7: _VX_MAX_CPU_COUNT 或 taskCpuGetCount()
    return taskCpuGetCount();
#else
    return 1;
#endif
}

} // namespace stvm::osal

3.9 后端横向对比

后端 时基 文件 IO CPU 亲和性 调度优先级 核心数 备注
Linux CLOCK_MONOTONIC ✅ fopen/fread ✅ pthread_setaffinity_np ✅ pthread_setschedparam sysconf PREEMPT_RT 自动启用 CLOCK_MONOTONIC_RAW
PREEMPT_RT / Xenomai CLOCK_MONOTONIC_RAW ✅ ✅ ✅ SCHED_FIFO/SCHED_RR 真实生效 sysconf Linux 后端内部自动检测,无需换后端文件
Windows QueryPerformanceCounter ✅ fopen/fread ✅ SetThreadAffinityMask ❌ GetSystemInfo -
FreeRTOS xTaskGetTickCount() FatFs 可选 ❌ ❌ configNUMBER_OF_CORES -
RT-Thread rt_tick_get() ✅ dfs POSIX v5.x 可选 rt_thread_set_priority RT_CPUS_NR -
SylixOS CLOCK_MONOTONIC ✅ POSIX ✅ pthread_setaffinity_np ✅ pthread_setschedparam sysconf POSIX 全套,与 Linux 同 API
VxWorks 6 tickGet() ✅ fopen/fread ❌ taskPrioritySet 1 -
VxWorks 7 tickGet() ✅ ✅ taskCpuSetAffinity taskPrioritySet ✅ taskCpuGetCount -
默认空实现 0 INVALID_FILE false false 1 任何 OS 都能链接,核心仍能跑

关键设计点 :PREEMPT_RT 和 Xenomai 不需要单独的后端文件 ------Linux 后端在运行时读 /sys/kernel/realtime 或检测 __XENO_SKIN__ 编译宏自动切换时基和调度策略。宿主只需链接 osal_linux.cpp,不管是普通 Ubuntu 还是 PREEMPT_RT 内核都能跑。


4. CMake 开关

4.1 选项

cmake 复制代码
# ---- OSAL 后端选择 ----
# AUTO       : 自动检测 CMAKE_SYSTEM_NAME(推荐)
# LINUX      : Linux / WSL / Android
# WINDOWS    : Windows(MSVC / MinGW)
# FREERTOS   : FreeRTOS V10+
# RTTHREAD   : RT-Thread v4.x+
# SYLIXOS    : SylixOS 国产 RTOS
# VXWORKS    : VxWorks 6.x / 7.x
# BARE       : 裸机 / 空实现 fallback(任何 OS 都能链接)
option(STVM_OSAL_BACKEND "OSAL backend (AUTO/LINUX/WINDOWS/FREERTOS/RTTHREAD/SYLIXOS/VXWORKS/BARE)" AUTO)

4.2 自动检测逻辑

cmake 复制代码
# ---- OSAL 后端 ----
if(STVM_OSAL_BACKEND STREQUAL "AUTO")
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android")
        set(_OSAL_BACKEND "LINUX")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
        set(_OSAL_BACKEND "WINDOWS")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "SylixOS")
        set(_OSAL_BACKEND "SYLIXOS")
    elseif(CMAKE_SYSTEM_NAME MATCHES "VxWorks")
        set(_OSAL_BACKEND "VXWORKS")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Generic")
        # 嵌入式交叉编译------靠 STVM_OSAL_BACKEND 强制指定
        set(_OSAL_BACKEND "BARE")
    else()
        set(_OSAL_BACKEND "BARE")
    endif()
else()
    set(_OSAL_BACKEND "${STVM_OSAL_BACKEND}")
endif()

# ---- 添加对应源文件(只链接一个后端)----
set(_OSAL_SRC
    src/platform/osal/osal.cpp          # 默认空实现,始终在(会被后端符号覆盖)
)
# 或者:默认空实现不进 list,后端进 list(避免符号冲突)
set(_OSAL_SRC "")

if(_OSAL_BACKEND STREQUAL "LINUX")
    list(APPEND STVM_SOURCES src/platform/osal/osal_linux.cpp)
    find_package(Threads REQUIRED)
    target_link_libraries(stvm PUBLIC Threads::Threads)
elseif(_OSAL_BACKEND STREQUAL "WINDOWS")
    list(APPEND STVM_SOURCES src/platform/osal/osal_windows.cpp)
elseif(_OSAL_BACKEND STREQUAL "FREERTOS")
    list(APPEND STVM_SOURCES src/platform/osal/osal_freertos.cpp)
    # 需要宿主在 FREERTOS_DIR 指定 FreeRTOS 源码位置
    set(FREERTOS_DIR "" CACHE PATH "FreeRTOS source dir")
    if(FREERTOS_DIR)
        target_include_directories(stvm PUBLIC ${FREERTOS_DIR}/include
                                   ${FREERTOS_DIR}/portable/${FREERTOS_PORT_ARCH})
    endif()
    # 可选 FatFs
    option(STVM_FREERTOS_HAS_FATFS "Enable FatFs file backend" OFF)
    if(STVM_FREERTOS_HAS_FATFS)
        target_compile_definitions(stvm PUBLIC STVM_FREERTOS_HAS_FATFS=1)
        set(STVM_FREERTOS_TICK_US 1000 CACHE INT "FreeRTOS tick period in us")
        target_compile_definitions(stvm PUBLIC STVM_FREERTOS_TICK_US=${STVM_FREERTOS_TICK_US})
    endif()
elseif(_OSAL_BACKEND STREQUAL "RTTHREAD")
    list(APPEND STVM_SOURCES src/platform/osal/osal_rtthread.cpp)
    set(RTTHREAD_DIR "" CACHE PATH "RT-Thread root dir (contain rt-thread.h)")
    if(RTTHREAD_DIR)
        target_include_directories(stvm PUBLIC ${RTTHREAD_DIR}/include)
    endif()
elseif(_OSAL_BACKEND STREQUAL "SYLIXOS")
    list(APPEND STVM_SOURCES src/platform/osal/osal_sylixos.cpp)
    find_package(Threads REQUIRED)
    target_link_libraries(stvm PUBLIC Threads::Threads)
elseif(_OSAL_BACKEND STREQUAL "VXWORKS")
    list(APPEND STVM_SOURCES src/platform/osal/osal_vxworks.cpp)
else()
    # BARE: 默认空实现
    list(APPEND STVM_SOURCES src/platform/osal/osal.cpp)
endif()

message(STATUS "OSAL backend : ${_OSAL_BACKEND}")

4.3 可移植到的平台

平台 后端 OSAL 覆盖 编译命令
Linux / WSL / Android LINUX 时基 ✅ 文件 ✅ 亲和性 ✅ -DCMAKE_BUILD_TYPE=Release
Windows MSVC / MinGW WINDOWS 时基 ✅ 文件 ✅ 亲和性 ✅ -G "Visual Studio 18 2026"
FreeRTOS V10+ FREERTOS 时基 ✅ 文件 可选 FatFs 亲和性 ❌ -DSTVM_OSAL_BACKEND=FREERTOS -DFREERTOS_DIR=...
RT-Thread v4.x+ RTTHREAD 时基 ✅ 文件 ✅ dfs POSIX 亲和性 可选 v5.x -DSTVM_OSAL_BACKEND=RTTHREAD -DRTTHREAD_DIR=...
SylixOS SYLIXOS 时基 ✅ 文件 ✅ 亲和性 ✅ POSIX 全套 CMAKE_SYSTEM_NAME=SylixOS 自动
VxWorks 6/7 VXWORKS 时基 ✅ 文件 ✅ 亲和性 V7 ✅ -DSTVM_OSAL_BACKEND=VXWORKS
uCOS-III 后续 可仿照 FreeRTOS 后端 用户实现时加 osal_ucos.cpp
裸机 Cortex-M BARE 全空实现,核心仍能跑 -DSTVM_OSAL_BACKEND=BARE

5. 核心代码改造点(最小化清单)

5.1 需要改的文件(仅 5 个)

文件 改动
src/program.cpp std::fopen/fread/fwrite/... → osal::file_open/read/write/...
src/vm/VM.cpp L129 trace 输出 std::fopen → osal::file_open
src/platform/cpu_affinity.cpp 整个文件删掉(或改为 inline wrapper 调 osal::set_thread_affinity)
src/scheduler/Scheduler.cpp cpu_affinity::bindCurrentThread → osal::set_thread_affinity
include/stvm/vm/context.hpp (可选)新增 clock_source 字段,允许宿主注入真实时钟

5.2 CMakeLists.txt

  • 新增 src/platform/osal/ 目录下 3-4 个源文件
  • 新增 STVM_OSAL_BACKEND 选项
  • 去掉或保留 STVM_USE_CPP_MISC / STVM_USE_LIBCO(已不影响 OSAL)

5.3 不需要改的

  • VM.cpp 主体:无 OS 调用
  • Scheduler.cpp 核心调度循环 :无 OS 调用(clock_us_ 虚拟时钟)
  • IecCodegen / IecSymbolTable / IecTypeChecker:编译期,不进运行时库
  • 所有 opcode handlers:无 OS 调用
  • SyscallTable:系统调用本身就是宿主注册回调的模式,天然隔离

6. 移植到不同 OS 的步骤

6.1 FreeRTOS(STM32F4/F7/H7 等 Cortex-M/M33)

#mermaid-svg-bubNCpeb37gcnVT9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bubNCpeb37gcnVT9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bubNCpeb37gcnVT9 .error-icon{fill:#552222;}#mermaid-svg-bubNCpeb37gcnVT9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bubNCpeb37gcnVT9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bubNCpeb37gcnVT9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bubNCpeb37gcnVT9 .marker.cross{stroke:#333333;}#mermaid-svg-bubNCpeb37gcnVT9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bubNCpeb37gcnVT9 p{margin:0;}#mermaid-svg-bubNCpeb37gcnVT9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bubNCpeb37gcnVT9 .cluster-label text{fill:#333;}#mermaid-svg-bubNCpeb37gcnVT9 .cluster-label span{color:#333;}#mermaid-svg-bubNCpeb37gcnVT9 .cluster-label span p{background-color:transparent;}#mermaid-svg-bubNCpeb37gcnVT9 .label text,#mermaid-svg-bubNCpeb37gcnVT9 span{fill:#333;color:#333;}#mermaid-svg-bubNCpeb37gcnVT9 .node rect,#mermaid-svg-bubNCpeb37gcnVT9 .node circle,#mermaid-svg-bubNCpeb37gcnVT9 .node ellipse,#mermaid-svg-bubNCpeb37gcnVT9 .node polygon,#mermaid-svg-bubNCpeb37gcnVT9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bubNCpeb37gcnVT9 .rough-node .label text,#mermaid-svg-bubNCpeb37gcnVT9 .node .label text,#mermaid-svg-bubNCpeb37gcnVT9 .image-shape .label,#mermaid-svg-bubNCpeb37gcnVT9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bubNCpeb37gcnVT9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bubNCpeb37gcnVT9 .rough-node .label,#mermaid-svg-bubNCpeb37gcnVT9 .node .label,#mermaid-svg-bubNCpeb37gcnVT9 .image-shape .label,#mermaid-svg-bubNCpeb37gcnVT9 .icon-shape .label{text-align:center;}#mermaid-svg-bubNCpeb37gcnVT9 .node.clickable{cursor:pointer;}#mermaid-svg-bubNCpeb37gcnVT9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bubNCpeb37gcnVT9 .arrowheadPath{fill:#333333;}#mermaid-svg-bubNCpeb37gcnVT9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bubNCpeb37gcnVT9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bubNCpeb37gcnVT9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bubNCpeb37gcnVT9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bubNCpeb37gcnVT9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bubNCpeb37gcnVT9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bubNCpeb37gcnVT9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bubNCpeb37gcnVT9 .cluster text{fill:#333;}#mermaid-svg-bubNCpeb37gcnVT9 .cluster span{color:#333;}#mermaid-svg-bubNCpeb37gcnVT9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bubNCpeb37gcnVT9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bubNCpeb37gcnVT9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bubNCpeb37gcnVT9 .icon-shape,#mermaid-svg-bubNCpeb37gcnVT9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bubNCpeb37gcnVT9 .icon-shape p,#mermaid-svg-bubNCpeb37gcnVT9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bubNCpeb37gcnVT9 .icon-shape .label rect,#mermaid-svg-bubNCpeb37gcnVT9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bubNCpeb37gcnVT9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bubNCpeb37gcnVT9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bubNCpeb37gcnVT9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有 FatFs
无 FatFs

  1. CMake 选项

-DSTVM_OSAL_BACKEND=FREERTOS

-DFREERTOS_DIR=/path/to/FreeRTOS
2. 后端已内置

osal_freertos.cpp

时基 ✅ tick 转换
文件系统?
3a. -DSTVM_FREERTOS_HAS_FATFS=1

posix open/read/write
3b. file_open 返回 INVALID_FILE

BytecodeProgram 在内存构造
4.宿主创建任务

void vSTVM_Task
5. 主循环

while(1) sched.runOnce();

vTaskDelay pdMS_TO_TICKS 1

宿主侧代码(STM32CubeMX 工程):

cpp 复制代码
#include "FreeRTOS.h"
#include "task.h"
#include "stvm/stvm.hpp"

void vSTVM_Task(void* pvParam) {
    // 1. 内存中构造 BytecodeProgram(或从 Flash 读入)
    std::vector<uint8_t> bc_data = loadBytecodeFromFlash();
    stvm::BytecodeProgram prog;
    std::string err;
    if (!prog.deserialize(bc_data.data(), bc_data.size(), err)) {
        // 错误处理...
        vTaskDelete(NULL);
        return;
    }

    // 2. 创建 Scheduler(cpu_affinity 保持 -1,FreeRTOS 不支持)
    stvm::Scheduler sched;
    sched.config().default_quota = 100000;
    sched.addTask("Main", prog, 1000 /* 1ms 周期 */, 100000);

    // 3. 主循环
    while (1) {
        sched.runOnce();
        vTaskDelay(pdMS_TO_TICKS(1));  // 让出 CPU
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_FREERTOS_Init();
    xTaskCreate(vSTVM_Task, "STVM", 4096, NULL, 1, NULL);
    vTaskStartScheduler();
    for (;;);
}

6.2 RT-Thread (Cortex-M / RISC-V)

bash 复制代码
# Kconfig 选项
RT_USING_STVM=y            # 宿主 RT-Thread 集成
STVM_OSAL_BACKEND="RTTHREAD"

RT-Thread 的 dfs POSIX 层开箱即用,宿主可以直接 open("/spiflash/main.bytecode", "rb") 加载 bytecode。SMP 架构下 v5.x 还能启用 rt_thread_setaffinity 绑定核心。

rt-thread 入口线程:

cpp 复制代码
#include <rtthread.h>
#include "stvm/stvm.hpp"

static void stvm_entry(void* param) {
    // RT-Thread dfs 自动挂载 FatFs / SPI Flash
    stvm::BytecodeProgram prog;
    std::string err;
    if (!prog.loadFromFile("/main.bytecode", err)) {
        rt_kprintf("STVM load failed: %s\n", err.c_str());
        return;
    }

    stvm::Scheduler sched;
    sched.addTask("Main", prog, 10000 /* 10ms */, 1000000);

    while (1) {
        sched.runOnce();
        rt_thread_mdelay(1);
    }
}

int stvm_init(void) {
    rt_thread_t tid = rt_thread_create("stvm", stvm_entry, NULL,
                                       4096, 10, 1);
    if (tid) rt_thread_startup(tid);
    return 0;
}
INIT_APP_EXPORT(stvm_init);

6.3 SylixOS(国产,x86/ARM/m68k)

SylixOS 是 POSIX 完全兼容的 RTOS,STVM 的 LINUX 后端几乎直接就能用 (CMake 自动检测 CMAKE_SYSTEM_NAME=SylixOS → SYLIXOS 后端)。

bash 复制代码
# 交叉编译
cmake -DCMAKE_SYSTEM_NAME=SylixOS \
      -DCMAKE_C_COMPILER=arm-sylixos-gcc \
      -DCMAKE_CXX_COMPILER=arm-sylixos-g++ \
      -DCMAKE_BUILD_TYPE=Release ..
make -j4

SylixOS 的 pthread_setaffinity_np / clock_gettime(CLOCK_MONOTONIC) / open/read/write/lseek/close 全支持,OSAL 三个函数组都是真实实现。

6.4 VxWorks 7(PowerPC / x86 / ARM)

bash 复制代码
# Wind River Workbench 编译环境
# set up VxWorks toolchain first
cmake -DCMAKE_SYSTEM_NAME=VxWorks \
      -DCMAKE_C_COMPILER=cceppc \
      -DCMAKE_CXX_COMPILER=cceppc \
      -DSTVM_OSAL_BACKEND=VXWORKS \
      -DCMAKE_BUILD_TYPE=Release ..

VxWorks 7 提供 taskCpuSetAffinity(),可将 vSTVM_Task 绑定到特定核心,适合硬实时 PLC 场景。

6.5 Linux / WSL(最常用,开发调试)

bash 复制代码
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
./build_linux/tests/bench_perf        # bench_perf 20 次循环编译
./build_linux/tests/test_vm           # 74/74
./build_linux/stvmc --iec main.iec -o main.bytecode

6.6 Windows(MSVC / MinGW)

bash 复制代码
cmake -G "Visual Studio 18 2026" -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --config Release

6.7 PREEMPT_RT / Xenomai 实时 Linux(硬实时 PLC 目标)

内核准备
bash 复制代码
# 确认 PREEMPT_RT 内核
uname -r
# → 5.10.0-rt-amd64(带 rt 后缀)

cat /sys/kernel/realtime
# → 1 (1=RT 内核,0=普通)

# 编译参数要求 CONFIG_PREEMPT_RT_FULL=y
zcat /proc/config.gz | grep PREEMPT_RT
# → CONFIG_PREEMPT_RT_FULL=y
构建(与普通 Linux 完全相同)
bash 复制代码
# Linux 后端内部自动检测 /sys/kernel/realtime = 1 → 切换
# CLOCK_MONOTONIC → CLOCK_MONOTONIC_RAW,并启用 SCHED_FIFO 支持
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
宿主侧:线程亲和性 + 实时调度策略
cpp 复制代码
#include <sched.h>
#include <pthread.h>
#include <stvm/stvm.hpp>

int main() {
    // 1. 配置 VM
    stvm::BytecodeProgram prog;
    stvm::Scheduler sched;
    sched.addTask("PLC_Main", prog, 1000 /* 1ms 周期 */, 50000);

    // 2. 绑定到 CPU 核心 1(避免被其他进程干扰)
    stvm::osal::set_thread_affinity(1);

    // 3. SCHED_FIFO 优先级 50(PREEMPT_RT 下真实生效)
    // 优先级范围 1..99,99 = 最高(会抢占一切)
    // PLC 建议 40-60(给设备驱动/定时器中断留余量)
    stvm::osal::set_thread_priority(
        stvm::osal::SchedPolicy::Fifo, 50);

    // 4. 主循环(PREEMPT_RT 下不会被普通进程抢占)
    while (sched.runOnce()) {
        // 无需 osal.sleep() ------ Scheduler 内部虚拟时钟推进
        // PREEMPT_RT 的硬实时延迟:1ms 周期抖动 < 10μs
    }
    return 0;
}
Xenomai 双内核部署
cpp 复制代码
// Xenomai 不需要额外改代码------osal_linux.cpp 检测 __XENO_SKIN__ 自动切换
// Xenomai 内核自带 __XENO_SKIN__ 编译宏

// 编译时工具链用 xenomai 的 wrapper
xeno-arm-linux-gnueabihf-g++ -o plc plc.cpp -lstvm -lxenoalchemy

// 运行前先进入 Xenomai 模式
sudo /usr/xenomai/scripts/xenomai start
./plc   // Xenomai 实时模式下延迟 < 5μs
实时性能预期
场景 内核 周期抖动 最大延迟
普通 Ubuntu 5.15-generic ±100-300μs 不确定(可到 ms 级)
PREEMPT_RT 5.10-rt-amd64 ±5-15μs < 50μs
Xenomai ipipe 双内核 ±1-5μs < 20μs

关键结论:OSAL 层完全透明------宿主无需改任何代码就能在不同 Linux 内核上获得不同的实时性表现。调度策略优先级从 0(普通 SCHED_OTHER)自动升到 40-60(SCHED_FIFO)只需要在宿主多写两行 osal 调用。


7. 交付计划

Phase 1(本次):基础接口 + Linux / Windows 后端

任务 产出
写 osal.hpp 头文件 3 类 API(时基 / 文件 / 亲和性),共 10 个函数
写 osal.cpp 默认空实现 任何 OS 可链接
写 osal_linux.cpp 完整实现(pthread_setaffinity_np + chrono + POSIX)
写 osal_windows.cpp 完整实现(SetThreadAffinityMask + QPC + fopen)
改 program.cpp / VM.cpp / Scheduler.cpp std::fopen → osal::file_open;cpu_affinity → osal::
改 CMakeLists.txt STVM_OSAL_BACKEND 自动检测 + 后端选择
全量回归测试 test_vm 74/74 + test_bytecode 33/33 + bench_perf 0 failures

Phase 2(后续):RTOS 后端

FreeRTOS / RT-Thread / SylixOS / VxWorks 后端代码已在文档 §3.5-3.8 提供 ,用户确认后即可创建对应的 .cpp 文件并集成。


附录:API 完整签名

cpp 复制代码
namespace stvm::osal {

// 时基 ------ 微秒单调递增时间戳
// Linux 普通内核:  CLOCK_MONOTONIC
// PREEMPT_RT:      CLOCK_MONOTONIC_RAW(不受 NTP 影响)
// Xenomai:         自动通过 __XENO_SKIN__ 宏检测
// 裸机:            返回 0(Scheduler 用虚拟时钟)
uint64_t get_time_us();

// 文件 IO ------ BytecodeProgram 序列化最小集合
using FileHandle = uintptr_t;
constexpr FileHandle INVALID_FILE = 0;
enum class OpenMode { ReadBinary, WriteBinary };

FileHandle file_open (const char* path, OpenMode mode);
size_t     file_read (FileHandle h, void* buf, size_t n);
size_t     file_write(FileHandle h, const void* buf, size_t n);
int        file_seek (FileHandle h, long offset, int whence);
long       file_tell (FileHandle h);
int        file_close(FileHandle h);

// CPU 亲和性 ------ 绑定线程到核心(可选,返回 false 表示不支持)
bool set_thread_affinity(int core_id);   // -1 = 取消绑定
bool reset_thread_affinity();
int  get_core_count();

// 调度优先级 ------ 设置实时调度策略(PREEMPT_RT / Xenomai / RT-Thread / VxWorks)
enum class SchedPolicy { Other, Fifo, Rr };
bool set_thread_priority(SchedPolicy policy, int priority);  // priority: 1..99(Fifo/Rr)
bool reset_thread_priority();  // 恢复 SCHED_OTHER 0

} // namespace stvm::osal
相关推荐
数字新视界1 小时前
国产信创动环监控系统的未来趋势分析
物联网·3d·数字孪生·三维可视化·数据可视化
会周易的程序员2 小时前
STVM OSAL 层架构设计文档
c++·物联网·嵌入式·虚拟机·iot·软plc·iec61131
调试优选官3 小时前
2026年物联网应用开发公司上榜品牌解析:D-coding技术纵深、项目规范与长期服务观察
物联网·开发经验·d-coding
白搞电子3 小时前
ESP32-S31 WASM 应用开发实录:重力弹球从电脑预览到 ESP-Mosaico 实机测试
单片机·嵌入式硬件·物联网·电脑·wasm
Mr_韩4 小时前
子网路由实战:无需公网 IP,异地直接访问家庭内网全部设备
运维·网络·物联网·nas
Bruce_Liuxiaowei4 小时前
内网“影子资产“:串口服务器安全盲区剖析
服务器·网络·安全·iot
Quanqiucard4 小时前
矿山安全管控升级,布控球机物联网卡成视频稳定回传关键
物联网·安全
microrain4 小时前
同一个值,四个名字,四套口径:SagooIoT 属性上报链路的 Canonical 收敛
物联网·golang·开源·sagooiot
乐橙开放平台5 小时前
通道国标 ID 贴反会串店:海康大华宇视摄像头怎么用 GB28181 统一接入?
网络·笔记·物联网·自动化·音视频·智能家居