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
- 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