Files
homeagent-sdk/tools/plugindev/proc_runtime.go
JianFeeeee 09b64dcb53 plugindev: 支持子进程插件构建(entry=plugin.bin,零 cgo)
Part 3 工具链改造。插件业务代码零改动,只需把 plg.json 的 entry
从 plugin.so 换成 plugin.bin。

新增 templates/proc_main.go.tmpl(1113 行)——子进程运行时:
- 51 个 core method 的插件侧 RPC 实现(procIO/procMemory/procSettings/
  procSocial/procLLM/procKnowledge/procDocMemory/procTextMemory/procPluginMgr)
- 共享段访问(fd 3 = 内核经 ExtraFiles 传入的 memfd)+ 16 字段
  StageContext 编解码,布局常量与 internal/plugin/proc/shm.go 逐一对齐
- handleStageInvoke:拿锁 → 读段 → handler → **只写脏字段** → 放锁。
  只读插件脏字段集为空 → 零写入 → 不可能覆盖他人改写
  (对照 C ABI 副本模型实测 35.8~36.8% lost update)
- 主循环每请求独立 goroutine:handler 内会反向调用内核并等应答,
  在读循环里同步处理会死锁
- plugin.start 后显式上报 AutoRestart:公开 SDK 的 SetAutoRestart 是纯
  setter 无 hook,隔着进程边界内核读不到(内核侧 corehandler.go:145 已就绪)

模板选择真实 .go 源文件 + //go:embed 而非 raw string:900+ 行代码塞在
字符串里写错只能等生成插件时才炸,作为源文件可被 parser/gofmt/vet 检查。

cmd_build.go:resolveBuild(target, proc) 分派;proc 走 go build -trimpath
+ CGO_ENABLED=0,交叉编译不再需要目标平台 C 工具链。bundle 模式各平台
产物同名故 zip 内加平台后缀(plugin.bin.linux.amd64)。

proc_runtime.go 生成时清理残留 z_bridge_gen.go/z_entry.c——同目录两套
main 会编译冲突,这让 .so → .bin 切换无需人工清理。

proc_runtime_test.go 16 项静态检查,防内核/插件两侧漂移:
method 名清单、7 个内核调用、共享段常量与字段枚举顺序、stage 加锁顺序、
快照必须存序列化字符串(切片共享底层数组的坑在 11.3 已踩过)、
arena 不足须报错、日志走 stderr、版本不匹配须拒绝、零 cgo。

验证:真实 plugindev 构建 example/weather,plugin.go 逐字节未改,
产出静态链接 ELF;git diff sdk/ 为空(接口冻结)。

Ref: docs/zh/架构迁移评估.md §3、docs/zh/plugin-migration-plan.md Part 3
2026-09-02 12:13:45 +08:00

55 lines
2.0 KiB
Go
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.

package main
import (
"embed"
"fmt"
"os"
)
// 子进程插件运行时(外部插件多进程化,Part 3)。
//
// 与旧 C ABI bridge 的差异:
// - 模板改为**真实 .go 源文件**(templates/proc_main.go.tmpl)而非 raw string:
// 900+ 行代码塞在字符串里,写错只能等生成插件时才炸;作为源文件可被
// gofmt / go vet / parser 直接检查。
// - 构建从 `-buildmode=c-shared` + CGO_ENABLED=1 改为普通 `go build` + CGO_ENABLED=0,
// 交叉编译不再需要目标平台的 C 工具链(§3.1 连带消失项)。
//
// 设计依据:docs/zh/架构迁移评估.md §3、docs/zh/plugin-migration-plan.md Part 3
//go:embed templates/proc_main.go.tmpl
var procTemplates embed.FS
// procEntryFile 是子进程插件的入口二进制名(与内核 internal/plugin/dynamic.go 的 binEntry 一致)。
const procEntryFile = "plugin.bin"
// procGenFile 是生成的运行时文件名。
// 前缀 z_ 使其在目录列表中排在业务代码之后,且与旧 bridge 的 z_bridge_gen.go 风格一致。
const procGenFile = "z_proc_gen.go"
// generateProcRuntime 把子进程运行时写入插件目录,返回清理函数。
//
// 与 generateBridge 的差异:只写一个 .go 文件,不需要 C 入口(z_entry.c)。
func generateProcRuntime() (func(), error) {
data, err := procTemplates.ReadFile("templates/proc_main.go.tmpl")
if err != nil {
return nil, fmt.Errorf("读取内嵌模板: %w", err)
}
// 清理可能残留的 C ABI 产物:同目录同时存在两套 main 会编译冲突。
// 这也让 .so → .bin 的切换无需人工清理。
for _, stale := range []string{"z_bridge_gen.go", "z_entry.c"} {
os.Remove(stale)
}
if err := os.WriteFile(procGenFile, data, 0644); err != nil {
return nil, fmt.Errorf("写入 %s: %w", procGenFile, err)
}
return func() { os.Remove(procGenFile) }, nil
}
// isProcEntry 判断 plg.json 的 entry 是否声明了子进程模式。
func isProcEntry(entry string) bool {
return entry == procEntryFile
}