Files
homeagent-sdk/tools/plugindev/templates/proc_shm_unix.go.tmpl
JianFeeeee 9f844123fe plugindev: entry 语义收敛 + 删 C ABI 工具链 + Windows 共享内存适配(Part 6.1)
## entry 不再是通道开关 —— 外部插件零改动的关键

17 个存量插件的 plg.json 都写着 "entry": "plugin.so"。若把 entry 当通道
开关,迁移就得改 17 个文件,而「外部插件零改动」是本次迁移的硬约束。

改法:Go 插件一律产出 plugin.bin,不看 entry 值。isProcEntry 删除,
resolveBuild 去掉 proc 参数。entry 现在只剩区分 Lua(main.lua)一个用途。

实测:weather 的 plg.json 一行不改(仍写 plugin.so),plugindev build
直接产出三平台 plugin.bin。

## Windows 不再是能力退化的第三套实现(§9.2 的正解)

C ABI 时代 Windows 是独立的第三套 ABI:dynamic_dll_windows.go 的 stage
只下发 3 个字段(raw_message/user_id/phase)且完全没有写回,sanitizer
这类改写型插件在 Windows 上静默失效,且无任何运行时警告。

现在 Windows 与 Unix 共用同一份 RPC 逻辑与同一份共享段布局。平台差异
收敛到三个挂载函数:
- Unix(linux/darwin/freebsd):内核经 ExtraFiles 传继承 fd(3=StageContext
  段,4=事件环段,5=eventfd/pipe)
- Windows:没有 fd 继承语义(os/exec 的 ExtraFiles 在 Windows 不支持),
  改用命名内核对象——父进程 CreateFileMapping/CreateEvent 建带名字的对象,
  子进程 OpenFileMappingW/OpenEventW 按同名打开。名字经环境变量传入而非
  硬编码:多个 homed 实例并存时不能撞名。

Windows 绑定用 syscall.NewLazyDLL 而非 golang.org/x/sys/windows:
OpenFileMappingW/OpenEventW 未被标准库 syscall 导出,而引入 x/sys 会给
**每个插件的 go.mod** 加一个新依赖,违反「插件仅依赖公开 SDK」。
LazyDLL 属标准库,零新增依赖。

新增 evtWaiter 接口抽象等待语义:eventfd 是计数器(多事件合并成一次
唤醒),Windows Event 是二元信号。不影响正确性——消费者被唤醒后按
readSeq 追 writeSeq 批量 drain,一次唤醒能处理累积的全部事件。

模板拆成三个文件:
  proc_main.go.tmpl          平台无关(RPC + 共享段布局 + stage + 事件环消费)
  proc_shm_unix.go.tmpl      继承 fd 挂载
  proc_shm_windows.go.tmpl   命名对象挂载

## 删除 C ABI 工具链

templates.go 1296 → 516 行:
- tmplBridge(Windows DLL bridge)      -265 行
- tmplLinuxBridge(Linux c-shared)     -457 行
- tmplPluginInitC(C 入口)              -57 行
另删 generateBridge / detectWindowsCC(MinGW 探测)/ tmplCABIHeader /
InitData.CABIVersion+CABIHeader。

交叉编译不再需要目标平台 C 工具链——这是 -buildmode=c-shared 退场的
连带收益(§3.1)。

## 测试

15 项全过,新增 4 项守护迁移不变量:
- AllPlatformsProduceBin:6 个 GOOS/GOARCH 组合统一产出 plugin.bin
- LuaIsSeparatePath:Lua 仍走解释器路径
- UnsupportedOSErrors:不支持平台明确报错,不静默产出错误产物
- NoCABIResiduals:代码中不得再出现 c-shared / CGO_ENABLED=1 /
  detectWindowsCC / tmplLinuxBridge / tmplPluginInitC(注释除外)
- IgnoresEntryForGoPlugins:isProcEntry 必须已删除

验证:go build/vet/test 全通过;三平台交叉编译产出 plugin.bin;
git diff sdk/ 为空(接口冻结)。

Ref: docs/zh/架构迁移评估.md §3.1/§9.2、docs/zh/plugin-migration-plan.md Part 6
2026-09-02 18:39:02 +08:00

63 lines
1.8 KiB
Cheetah
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build linux || darwin || freebsd
package main
import (
"fmt"
"os"
"syscall"
)
// Unix 侧共享段挂载:内核经 ExtraFiles 传入继承的 fd。
//
// fd 布局(与内核 internal/plugin/proc/plugin.go 的 ExtraFiles 顺序一致):
//
// fd 3 = StageContext 段memfd / 已 unlink 的临时文件)
// fd 4 = 事件环段
// fd 5 = 事件通知Linux eventfd / macOS pipe 读端)
//
// 继承的 fd 无需文件名,也不残留——这是选 memfd 而非 /dev/shm 的原因。
const (
fdStageShm = 3
fdEvtRingShm = 4
fdEvtNotifier = 5
)
// attachStageShm 挂载 StageContext 共享段。
//
// 各进程 mmap 到不同虚拟地址,段内一律用相对偏移而非指针,故仍能正确解引用
// (实验 2 已验证父子 mmap 基址不同时偏移解引用正确)。
func attachStageShm(size int) ([]byte, error) {
return syscall.Mmap(fdStageShm, 0, size,
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
}
// attachEvtRingShm 挂载事件环段。
func attachEvtRingShm(size int) ([]byte, error) {
return syscall.Mmap(fdEvtRingShm, 0, size,
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
}
// openEvtNotifier 打开事件通知读端。
func openEvtNotifier() (evtWaiter, error) {
f := os.NewFile(fdEvtNotifier, "evtnotify")
if f == nil {
return nil, fmt.Errorf("fd %d 不是有效的通知句柄", fdEvtNotifier)
}
return &unixEvtWaiter{f: f}, nil
}
// unixEvtWaiter 用 eventfd/pipe 的阻塞 Read 等待通知。
//
// os.NewFile 把 fd 注册进 runtime netpollerRead 阻塞时只 park goroutine
// 不占 OS 线程(实验 1200 个等待者仅增 1 个 OS 线程)。
// 反面对照是经 cgo 调 sem_wait——那会阻塞整个 M。
type unixEvtWaiter struct {
f *os.File
}
func (w *unixEvtWaiter) Wait(buf []byte) error {
_, err := w.f.Read(buf)
return err
}