Files
Thing/ThingHK/ThingHK_GUIDE.md
2026-07-29 10:07:25 +08:00

25 KiB
Raw Permalink Blame History

ThingHK — Thing 硬件监控内核

1. 架构

采用微内核架构,将硬件监控功能剥离为独立进程 ThingHK.exeC# + LibreHardwareMonitor)。Tauri 主进程(Rust)仅作为"消费者",通过本地 HTTP + SSE 获取标准化数据,实现硬件层与业务层彻底解耦。

核心数据流

Rust (Tauri App)  <---HTTP / SSE (127.0.0.1:8730)--->  ThingHK.exe (独立进程)
       ↓ emit("monitor-data")
   前端 MonitorModule.vue

设计取舍

  • 进程隔离而非进程内宿主:LHB 或驱动崩溃不会拖垮 Tauri 主进程;独立进程 + Job Object 既隔离崩溃又复用项目现有架构。
  • C# + LibreHardwareMonitor 选型Rust 侧硬件监控生态薄弱(sysinfo 无 GPU 温度/主板电压,wmi 慢且字段不全,自实现 NVAPI/ADL/SMBus 等于重写 LHB)。LHB 是业界事实标准,覆盖 CPU/GPU/盘/主板/USB,可靠性最高。
  • 复用 ProcessManager:项目已有 process_manager.rs 完整实现了生命周期管理(启动/停止/心跳/崩溃重启/Job Object),ThingHK 不重建这套逻辑,仅作为"数据消费者"。
  • HTTP 而非 Named Pipe:数据量极小(传感器快照每秒 1-2 次、几 KB 量级),Named Pipe 的性能优势用不上;与项目技术栈一致(Cargo.toml 已有 reqwestmihomo 也走 HTTP REST);调试可用浏览器/curl;跨平台抽象免费。

2. 组件与职责

2.1 ThingHK 独立进程(C#

  • 角色:硬件数据中台。
  • 核心组件
    • 数据采集层:以 LibreHardwareMonitorLib 为唯一数据源(LHB 已统一封装 NVAPI/ADL/SMBus/SMART 等来源)。
    • SamplingScheduler:按传感器类型分层分频,避免慢查询拖累快通道。
      • 快通道(CPU/GPU/Memory/Network):PeriodicTimer 默认 1000msUpdateAll() 全量刷新
      • 慢通道(Storage/PSU/Motherboard/Battery/SuperIO/EmbeddedController):独立 PeriodicTimer 默认 5000msUpdateSlowOnly() 仅刷新慢硬件
      • 两通道并行运行,每次快通道 tick 后构建快照并广播到 Channel<SensorSnapshot>bounded=8DropOldest 防慢消费者阻塞)
    • SnapshotCache:线程安全快照缓存,UI 请求时直接返回缓存数据,避免触发底层硬件重扫描。
    • HTTP 服务层ASP.NET Core minimal API,暴露 127.0.0.1:8730 本地 HTTP 服务。

2.2 Tauri 主进程(Rust

  • 角色:管理者(复用 ProcessManager)与消费者。
  • 职责
    • 生命周期管理:复用 ProcessManager,通过 start_with_subscription 拉起 Kernel。崩溃自动重启、Job Object 异常清理、stop_all 退出清理均已内置。
    • 数据接收:作为 HTTP 客户端,轮询 /status 判就绪 → 订阅 /stream SSE → 解析事件 → emit("monitor-data")
    • 写入熔断 + 自动重连:SSE 断开后停止转发,3 秒退避后重试;同时监听 process-status-changed 事件,Kernel 由 ProcessManager 自动重启恢复 Running 后主动重新订阅 SSE。

2.3 前端(Vue 3

  • Pinia storemonitorStore.ts):管理 status/snapshot 状态,订阅 Tauri 事件,提供 connState 状态机(idle/loading/connected/disconnected/error)。
  • MonitorModule.vueMonitorModule.vue):3 个 Tab(概览/详细/设置),关键指标卡 + SVG sparkline 历史曲线(无外部图表库)+ Accordion 分组 + 断线/降级态提示。

3. 通信协议与数据契约

3.1 路由

路由 方法 说明
/ GET 健康检查
/status GET 冷启动就绪探测,前端轮询判断是否可订阅
/snapshot GET 一次性拉取最新缓存快照
/stream GET (SSE) 订阅推送,Kernel 按采样间隔主动 Push SensorSnapshot
/config POST 运行时调整采样间隔({"FastIntervalMs":500,"SlowIntervalMs":3000}
/shutdown POST 优雅关闭(提权模式下由 Tauri 调用,普通权限无法 kill 管理员进程)

3.2 数据契约(显式版本化)

为避免 Kernel 升级后前端解析炸裂,数据 schema 显式版本化。前端按 schemaVersion 解析;Kernel 升级 schema 时递增版本号并提供兼容期。当前 schemaVersion=1

{
  "schemaVersion": 1,
  "timestamp": 1730000000000,
  "coldStartMs": 4906,
  "isAdmin": true,
  "ready": true,
  "groups": [
    {
      "id": "cpu",
      "name": "CPU",
      "sensors": [
        { "id": "...", "name": "CPU Package", "type": "temperature", "hardwareName": "Intel Core i5-10400", "value": 52.3, "unit": "°C" }
      ]
    }
  ]
}

JSON 字段命名:双方均 camelCase。C# 端 JsonSourceGenerationOptions 设置 PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCaseRust 端 #[serde(rename_all = "camelCase")]

3.3 Tauri 命令与事件

7 个命令

命令 作用
monitor_kernel_info 查询 Kernel 二进制路径/是否存在/端口
monitor_status 查询运行状态(running/pid/ready/sensorCount/restartCount/elevated
monitor_start 启动 Kernel + 自动订阅 SSE(命令和 setup 共用 start_with_subscription
monitor_start_elevated 以管理员权限启动 Kernel(弹 UAC),停止走 /shutdown 接口
monitor_stop 停止 Kernel(提权模式走 /shutdown,普通模式走 ProcessManager
monitor_get_status 透传 Kernel /status
monitor_get_snapshot 透传 Kernel /snapshot

5 个事件monitor-data(快照)、monitor-readymonitor-loadingmonitor-disconnectedmonitor-error

4. 传感器覆盖度

在管理员权限下,--basic 模式(仅开 CPU/GPU/RAM/Storage)读取两轮后的结果:

硬件分组 传感器数 覆盖项
CPU 47 每核每线程负载、每核温度、TjMax、时钟、Package/Cores/Memory/Platform 功率、每核电压、Bus Speed
Memory 42 虚拟/物理内存使用量、可用、负载;两条 DDR4 SPD 时序 + 容量
GpuIntel 17 GPU 功率、D3D 共享内存总量/已用、D3D 3D/VideoDecode/Copy/VideoProcessing 各引擎负载
Storage 11 温度、Power on count/hours、已用/可用/总空间、Read/Write/Total Activity、Read/Write Rate
合计 117 100% 有值

full 模式额外多出 Network60 个传感器)和 Motherboard(本机 0 个,需更深驱动/权限)。

非提权降级LHB 访问 SMBus、部分 EC 传感器、某些 GPU 传感器需要管理员权限。默认非提权运行,覆盖大部分 CPU/GPU 温度(通过 OHM RPC/WMI 仍可读),牺牲部分主板/电压传感器。提权策略(任务计划程序免 UAC)作为可选优化。

5. 实现要点与经验教训

5.1 C# KernelNative AOT

关键配置ThingHK.csproj):

  • <TargetFramework>net8.0-windows</TargetFramework>LHB 在 Windows 下最稳)
  • <PublishAot>true</PublishAot>
  • <TrimmerRootDescriptor>rd.xml</TrimmerRootDescriptor>LHB 反射根描述,preserve="all" 兜底)
  • JSON 使用 [JsonSourceGeneration] 消除 IL3050/IL2026 警告

AOT 体积18.3 MB(含 ASP.NET Core runtime),无需 .NET Runtime,符合"最小安装"偏好。

AOT 踩坑

  1. 匿名类型无法序列化Results.Ok(new { ... }) 在 AOT 下抛 JsonTypeInfo metadata ... was not provided。所有响应改用强类型 + Results.Json(obj, ThingHKJsonContext.Default.T),新增 HealthResponse/ErrorResponse 类型注册到 [JsonSerializable]
  2. async lambda 返回类型app.MapPost("/config", async (ctx) => { return Results.Json(...); }) 在 AOT 下返回空响应体。原因:async lambda 返回 IResult 被框架当成 Task<IResult> 的未等待任务。修复:显式声明 async Task<IResult> (HttpContext ctx) =>
  3. HardwareType 枚举变更LHB 0.9.5 的 HardwareTypeController,改为 SuperIO + EmbeddedController
  4. Computer 非 IDisposableLHB 0.9.5 的 Computer 类未实现 IDisposableHardwareManager.Dispose() 改用显式 _computer.Close()

5.2 Rust 集成

关键架构决策

  1. 自动启动策略:参照 proxy 模块的 auto_start_on_launch 模式,在 setup 中用 tauri::async_runtime::spawn 异步拉起 Kernel。硬件监控为被动读取、无副作用(不修改系统状态),故默认启用,无需用户配置开关。
  2. start_with_subscription 抽取:将"启动进程 + 注册自动重连 + 启动 SSE 订阅"封装为单一方法,供 monitor_start 命令和 setup 自动启动复用,避免两条路径行为漂移。
  3. MonitorKernel 改为 Clone + Arc<Mutex> 共享状态tauri::State::inner() 返回 &T 而非 &Arc<T>,无法直接 clone 出 Arc<MonitorKernel>。将 sub_handlelistener_ids 改为 Arc<Mutex<...>>MonitorKernel 派生 Clone,clone 出的实例与原实例共享订阅控制状态。
  4. sse_clientclient 分离client 带 30s 超时,用于 /status/snapshot 等短请求;sse_client 无超时,专用于 /stream 长连接。早期版本对 SSE 请求误用 timeout(Duration::from_secs(0)),导致 reqwest 立即超时失败。
  5. 写入熔断 + 自动重连subscribe_once 收到 SSE 断开后停止 emit,3 秒退避后重试;Kernel 不响应则退出循环。同时监听 process-status-changed 事件,Kernel 由 ProcessManager 自动重启恢复 Running 后主动重新订阅 SSE。

Rust 踩坑

  1. Tauri 2 async 命令必须返回 Result<T, E>AsyncCommandMustReturnResult 未实现 for MonitorStatus。修复:monitor_status 返回类型从 MonitorStatus 改为 Result<MonitorStatus, String>
  2. Kernel /status 反序列化失败C# 默认输出 PascalCaseReadyIsAdmin),Rust 期望 camelCase。修复:Contracts.csJsonSourceGenerationOptions 添加 PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase
  3. 崩溃重连无并发订阅问题:旧 SSE 循环在 is_kernel_alive 返回 false(端口未释放)时正确退出,register_auto_reconnect 启动新订阅,netstat 确认仅 1 个 ESTABLISHED 连接。

5.3 前端

关键设计决策

  1. 状态机 connState:基于 status + eventCount + lastEventTime 推断 5 种状态(idle/loading/connected/disconnected/error),5 秒未收到 monitor-data 事件判定为 disconnected。
  2. findSensorValue 多条件查找:支持 groupId + name + hardwareName + type 四维匹配,name 用精确匹配 + includes 子串兜底。用于解决 Memory 分组有两条同名 "Memory" 传感器(Virtual/Total)的歧义。
  3. SVG sparkline 无外部依赖:关键指标历史用 30 点环形 bufferSVG path 自绘,不引入 Chart.js(符合"最小安装、少依赖"偏好)。
  4. schemaVersion 守卫store 订阅 monitor-data 时检查 schemaVersion !== 1 并 warn,未来 Kernel 升级 schema 时前端不会静默解析错误数据。
  5. 模块卸载不停止 Kerneldispose 仅取消事件订阅,不调用 monitor_stop。Kernel 生命周期由 ProcessManager 全局管理,与模块 UI 生命周期解耦。
  6. Ready 边沿触发拉取快照watch store.status?.ready 从 false→true 时主动 fetchSnapshot(),避免 Kernel 冷启动就绪后 UI 等待下一个 SSE tick 才有数据。

传感器名匹配(通过 curl /snapshot 验证实际传感器名):

指标 查找逻辑 说明
CPU 封装温度 name: 'CPU Package', type: 'temperature'
CPU 总负载 name: 'CPU Total', type: 'load'
内存负载 name: 'Memory', hardwareName: 'Total Memory', type: 'load' Memory 分组有两条同名传感器(Virtual/Total),需用 hardwareName 消歧
Intel GPU 3D 负载 name: 'D3D 3D' ?? name: 'GPU Core' ?? type: 'load' LHB 实际输出名是 "D3D 3D",无 "GPU" 前缀
存储温度 type: 'temperature' 用 type 查找

前端踩坑

  1. lucide-vue-next 模块未找到:项目实际使用 @lucide/vue(见 package.json),而非 lucide-vue-next
  2. connState === 'idle' 类型比较错误vue-tsc 报 TS2367。v-else-if 已处理 idle 分支,v-else 块内 connState 类型被收窄为非 idle=== 'idle' 永假。修复:移除该比较。

5.4 稳定性与打包

冷启动:约 5 秒(computer.Open() 驱动加载占绝大部分,Update() 本身约 200ms)。前端 Loading 超时阈值设为 10 秒,轮询间隔 500ms。

崩溃恢复验证kill ThingHK 进程后,ProcessManager 3s 巡检检测崩溃 → 800ms 端口等待 → spawn 新 Kernel → register_auto_reconnect 监听 process-status-changed(running) 重新订阅 SSE。旧 SSE 循环正确退出,无并发订阅问题。重启后 ready=TruesensorCount=117(与崩溃前一致)。

退出清理验证

  • 正常退出quit_app / Ctrl+C):stop_all() 同步 kill + 3s 超时 wait,无残留。
  • 异常崩溃kill thing.exe):Windows Job ObjectJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE)兜底自动清理,无残留。

打包验证

  • tauri build 生成 MSI31 MB+ NSIS22.3 MB)安装包。
  • ThingHK.exe18.3 MB)通过 tauri.conf.json "resources": ["binaries/*"] 打包进安装包。
  • BaseDirectory::Resource 在 release 模式下正确解析到 <exe_dir>/binaries/ThingHK.exeTauri 2 在打包后指向 exe 所在目录,而非 resources/ 子目录)。
  • Release exe 直接运行:Kernel 自动启动成功,ready=TruesensorCount=117。

打包踩坑tauri build 在 TRAE 环境失败 error: invalid value '1' for '--ci'。TRAE 环境设置了 CI=1,导致 tauri CLI 误解析。修复:构建前清除 $env:CI = $null

6. 文件索引

C# Kernel

文件 职责
ThingHK.csproj .NET 8 AOT 配置,Microsoft.NET.Sdk.Web + ASP.NET Core minimal API
Program.cs serve/scan 双子命令(System.CommandLine),默认无参数等价 serve --port 8730 --basic
Contracts.cs 数据契约 + ThingHKJsonContextJSON 源生成,camelCase
HardwareManager.cs HardwareManager(封装 LHB Computer,快慢通道分类)+ SamplingSchedulerPeriodicTimer 分频)+ SnapshotCache(线程安全缓存)+ SnapshotVisitor(递归收集)
HttpEndpoints.cs 6 个路由(含 /shutdown 优雅关闭)+ 全局异常中间件 + KernelHost(统一生命周期)
rd.xml LHB 反射根描述符,preserve="all" 兜底 AOT trimming

7. FPS 监控方案(后续计划,暂不实施)

为扩展硬件监控模块对游戏 FPS 的支持,调研了四种实现方案。当前作为后续规划储备,暂不实施。

注:本节为后续规划,与第 8 节提权机制章节编号独立,不影响现有架构。

7.1 方案对比

维度 ① DWM API ② ETW Present ③ PresentMon ④ RTSS
依赖 无(系统内置) 无(系统内置) PresentMon Service(需安装 MSI ~30MB RTSS(需安装)
体积增量 0 KB 0 KB ~30MB+ 外部进程
代码量 ~60 行 ~400 行 ~1000+ 行(跨栈) ~200 行(读共享内存)
精度
1% Low (需自行实现统计算法) 内置
进程级监控
全屏独占兼容
需管理员权限
桌面静止时 显示刷新率(60/144,误导) FPS=0(准确) FPS=0(准确) FPS=0
AOT 风险 低(P/Invoke 中(P/Invoke C API
与"最小依赖"偏好 完美契合 契合 冲突 冲突

7.2 方案 ①:DWM API(最简单,降级方案)

原理:调用 DwmGetCompositionTimingInfo 获取 DWM 合成帧计数,两次采样差值 / 时间差 = FPS。

优点

  • 零依赖、零体积增量,dwmapi.dll 是 Windows 系统组件
  • 实现极简(~60 行),AOT 原生友好
  • 无需提权

缺点

  • 全屏独占(FS Exclusive)模式下 DWM 不参与合成,FPS 读数为 0
  • 桌面静止时仍以显示器刷新率合成,FPS 显示 60/144(误导)
  • 仅全局 FPS,无法按进程过滤

定位:降级方案。在用户未提权或仅需粗略 FPS 时使用。

核心 P/Invoke

[DllImport("dwmapi.dll", PreserveSig = false)]
static extern void DwmGetCompositionTimingInfo(IntPtr hwnd, ref DWM_TIMING_INFO pTimingInfo);

7.3 方案 ②:自己监听 ETW 的 Present 事件(推荐主力方案)

原理:订阅 Microsoft-Windows-DXGIGUID CA11C036-0102-4A2D-A6AD-F03CEDF728C7)的 Present 事件(Event ID 0),滑动窗口统计单位时间 Present 调用次数 = FPS。

实现路径:原生 P/Invoke advapi32.dll 的 ETW APIStartTraceWEnableTraceEx2OpenTraceProcessTrace),零依赖。

优点

  • 零依赖、零体积增量
  • 进程级监控(按 PID 过滤前台游戏)
  • 全屏独占兼容(DXGI Present 事件仍触发)
  • AOT 原生友好

缺点

  • 需管理员权限(与 ThingHK 提权体系兼容)
  • 实现复杂度中高(~400 行):需手工定义 EVENT_TRACE_PROPERTIES/EVENT_RECORD 等结构体
  • DXGI Present 事件 payload 无公开 schema,需参考 PresentMon 源码硬编码偏移
  • ProcessTrace 是阻塞调用,需后台线程管理生命周期
  • 会话未正确清理会残留 ETW 会话影响系统性能

FPS 计算:1 秒滑动窗口,500ms 清理过期时间戳,FPS = 队列长度 / 窗口时长

实现步骤

  1. 新增 EtwFpsMonitor.csP/Invoke + 会话管理 + FPS 计算)
  2. KernelHost 持有实例,启动时检测管理员权限,未提权则 Fps=null
  3. Contracts.csSensorSnapshot 增加 Fps 字段,递增 SchemaVersion
  4. SamplingScheduler.FastLoopAsync 每 tick 调用 SampleFps()
  5. 前端 OSD/详情页增加"FPS"特殊项

参考PresentMon/IntelPresentMon/PresentData/ETW/Microsoft_Windows_DXGI.cpp

7.4 方案 ③:PresentMon Service + API(功能最全,但依赖重)

原理:安装 PresentMon Service(官方 MSI),ThingHK 通过 P/Invoke PresentMonAPI2.dll 连接 Service 获取 140+ 指标。

部署约束(关键)

  • Service 是独立进程,不能"只丢几个 DLL"
  • 官方明确警告:客户端自带 DLL 不保证与服务二进制兼容,必须用官方 MSI 安装
  • MSI 含 CEF 运行时,安装包 ~30MB,安装后常驻进程 ~30-50MB 内存
  • Service 需管理员权限

API 用法

pmOpenSession(&hSession);
pmStartTrackingProcess(hSession, processId);
pmRegisterDynamicQuery(hSession, &hQuery, elements, 2, 1000, 0);
pmPollDynamicQuery(hQuery, processId, blob, &numSwapChains);

优点:功能最全(FPS / 1% Low / 0.1% Low / 帧时间 / GPU 延迟 / 功耗),官方维护。

缺点

  • 与"minimal installation size, few dependencies"偏好冲突
  • 实现复杂度高(~1000+ 行跨栈:C# P/Invoke + Rust 下载安装管理 + 前端)
  • AOT 下 P/Invoke C API 需验证
  • "游戏模式按需下载"实际是下载 MSI 静默安装,体验重

定位:不推荐。若用户已自行安装 PresentMon Service,可检测后切换为高精度模式(不在应用内下载安装)。

7.5 方案 ④:RTSS(需安装第三方软件)

原理RTSSRivaTuner Statistics Server)提供共享内存暴露 FPS/帧时间,ThingHK 读取共享内存。

优点:成熟稳定,精度高,支持 1% Low。

缺点

  • 需用户安装 RTSS(商业软件,与 MSi Afterburner 捆绑)
  • 共享内存 schema 无官方文档,需逆向
  • 与"few dependencies"偏好冲突

定位:不推荐。仅作为已安装 RTSS 用户的可选对接。

7.6 推荐路线

分两步走

  1. 先实现方案 ①(DWM API:验证 FPS 展示链路(数据契约 → SSE → 前端 OSD)打通,0 风险、0 依赖。UI 标注"桌面合成 FPS"。

  2. 升级为方案 ②(ETW Present:替换 Sample() 实现,前端无改动,数据契约不变。获得精确的进程级 FPS。

统一数据契约(两种方案共用):

public float? Fps { get; set; }        // 实时 FPS(无游戏时为 0 或 null)
public uint? FpsProcessId { get; set; } // 目标进程(可选,0=全局)

权限策略

  • 未提权:降级为 DWM API 或返回 null
  • 已提权:启用 ETW 方案

Rust 集成

文件 职责
monitor_kernel.rs Tauri 侧 Kernel 客户端:复制二进制、构造 StartProcessParams、轮询就绪、SSE 订阅、写入熔断 + 自动重连、ShellExecuteW 提权启动
lib.rs 7 个 monitor 命令注册 + setup 自动启动
process_manager.rs 通用进程管理(复用,含崩溃重启 + Job Object

前端

文件 职责
monitorStore.ts Pinia store:状态管理 + 事件订阅 + connState 状态机 + findSensorValue
MonitorModule.vue 3 Tab UI(概览/详细/设置)+ 关键指标卡 + SVG sparkline + Accordion 分组
index.ts 模块配置和 lifecycle 钩子

二进制

文件 说明
src-tauri/binaries/ThingHK.exe Kernel AOT 产物(18.3 MB),随安装包分发,运行时复制到 {app_data_dir}/monitor/cores/ThingHK.exe

用法

# 默认 serve(无参数等价)
ThingHK.exe serve --port 8730 --basic --fast-ms 1000 --slow-ms 5000

# 传感器覆盖矩阵测试
ThingHK.exe scan --json --basic

8. 提权机制

8.1 背景

LibreHardwareMonitor 访问 CPU MSR(温度/时钟)、存储 SMART、SMBus、EC 传感器需要管理员权限。普通权限下可读传感器受限:

权限 分组 传感器数 可读项
普通 cpu/memory/gpuintel 62 CPU 负载/功率、内存、GPU 负载(温度/时钟全 null)
管理员 cpu/memory/gpuintel/storage 117 完整温度/时钟/电压/存储 SMART

7.2 实现方案

采用 ShellExecuteW "runas" 弹 UAC 提权启动 Kernel,架构上与普通模式分离:

  • 启动monitor_kernel.rs start_elevated → 停止当前 Kernel → ShellExecuteW "runas" 启动管理员 Kernel(隐藏控制台窗口)→ 等待 ready → 重新订阅 SSE
  • 停止:调用 Kernel 的 POST /shutdown 接口(普通权限无法 TerminateProcess 管理员进程)
  • 状态查询:通过 HTTP /status 判断运行状态(pid 不可用,返回 None)

7.3 提权模式限制

能力 普通模式 提权模式
进程管理 ProcessManagerkill/restart/Job Object 不受管控(跨权限级别句柄不可用)
崩溃重启 3s 巡检 + 自动重启 不支持(需手动重新提权)
停止方式 ProcessManager.stopkill POST /shutdown(优雅退出)
PID 查询 可用 不可用(返回 None
UAC 弹窗 每次启动需确认

7.4 C# Kernel /shutdown 接口

HttpEndpoints.cs 增加 POST /shutdown 路由,触发 CancellationTokenSource.Cancelapp.RunAsync 优雅退出。响应先返回 200,延迟 100ms 后取消,确保响应体完整发送。

9. 后续可选优化

  1. ProcessManager Mutex 锁阻塞问题check_and_cleanup 中 800ms sleep(端口等待)在持有 processes Mutex 锁的情况下执行,会阻塞所有进程状态查询。建议将重启逻辑移出锁作用域(已知问题,与 proxy 模块共享)。
  2. 免 UAC 提权:当前每次提权需弹 UAC,可通过 Windows 任务计划程序注册"以最高权限运行"的任务实现免 UAC 启动(需一次性管理员权限注册任务)。
  3. 多机型回归AMD Ryzen / NVIDIA GPU / 笔记本场景的实际传感器覆盖度验证(需实际硬件)。
  4. Kernel 版本管理prepare_kernel 只在文件不存在时复制,不会覆盖更新。建议增加版本比对自动更新。