mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
补齐内核侧的 Windows 创建端,与 6.1 的插件侧打开端配对。三平台 (linux/darwin/windows)现在都能构建 internal/plugin/proc。 ## Windows 走命名内核对象(无 fd 继承语义) os/exec 的 ExtraFiles 在 Windows 实现里不被支持,故: - shmalloc_windows.go:CreateFileMappingW(INVALID_HANDLE_VALUE + 命名 → 系统页文件支撑的匿名段,不落盘)+ MapViewOfFile - evtfd_windows.go:CreateEventW 命名 Event 对象 + SetEvent 通知 - shmpass_windows.go:把段名/对象名经环境变量注入子进程 (HOMEAGENT_SHM_STAGE / HOMEAGENT_SHM_EVTRING / HOMEAGENT_EVT_EVENT) 名字带 PID + 递增序号:多个 homed 实例并存时不能撞名。 Event 与 eventfd 的语义差异:Event 是二元信号,多次 SetEvent 只对应一次 唤醒,不累积。不影响正确性——消费者被唤醒后按 readSeq 追 writeSeq 批量 drain,丢的是"唤醒次数"不是"事件";事件环本身就允许溢出丢弃并让消费者 知道丢了(dropped 计数),通知面从来不是可靠投递语义。 ## 传递机制抽象为 shmpass_*.go Plugin.Start 不再直接构造 ExtraFiles 列表,改为问 Host 要: Env: p.host.procEnvForShm() // Windows 返回段名,Unix 返回 nil ExtraFiles: p.host.procExtraFilesForShm() // Unix 返回 fd 列表,Windows 返回 nil 平台差异被收敛到这一对函数,Plugin/coreHandler/stage 全部平台无关。 ## macOS pipe 生命周期修正 原实现只返回读端 fd,写端 *os.File 无人持有 → 可能被 GC 回收 → 读端收到 EOF 而非阻塞 → 消费循环变忙转。改为 pipePair 表同时持有两端, evtfdClose 一并关闭。 ## E2E 测试跟进模板拆分 模板从单文件拆成三个(主体 + unix/windows 挂载),测试需要一并落盘, 否则编译报 attachStageShm undefined。procRuntimeTemplates 表必须与 SDK 仓 proc_runtime.go 的 procRuntimeFiles 一致。 验证:三平台 go build ./internal/plugin/... 通过(gojieba 的 cgo 依赖 导致 internal/memory 在非 linux 失败,与本次无关); go test -race ./internal/plugin/... 全绿,含 2 项真实模板 E2E。 Ref: docs/zh/架构迁移评估.md §9.2、docs/zh/plugin-migration-plan.md Part 6
138 lines
4.3 KiB
Go
138 lines
4.3 KiB
Go
//go:build windows
|
||
|
||
package proc
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"sync"
|
||
"sync/atomic"
|
||
"unsafe"
|
||
|
||
"golang.org/x/sys/windows"
|
||
)
|
||
|
||
// Windows 侧共享段:命名 FileMapping + 命名 Event。
|
||
//
|
||
// 与 Unix 的机制差异(不是能力差异):
|
||
// Windows 没有 fd 继承语义——os/exec 的 ExtraFiles 在 Windows 实现里不被支持。
|
||
// 等价机制是命名内核对象:父进程 CreateFileMappingW 建带名字的段,
|
||
// 子进程 OpenFileMappingW 按同名打开,拿到同一份物理页。
|
||
//
|
||
// **这是 §9.2 的正解**。C ABI 时代 Windows 是第三套独立 ABI 实现
|
||
// (dynamic_dll_windows.go),stage 只下发 3 字段且完全没有写回,
|
||
// sanitizer 这类改写型插件静默失效。三套 ABI 收敛为单一 RPC 后,
|
||
// Windows 与 Unix 共用同一份 stage 逻辑与同一份共享段布局,
|
||
// 平台差异只剩本文件的创建端 + 插件侧模板的打开端。
|
||
//
|
||
// 名字带 PID 与递增序号:多个 homed 实例并存时不能撞名,
|
||
// 同一实例内 StageContext 段与事件环段也必须分开。
|
||
var shmNameSeq atomic.Uint64
|
||
|
||
const (
|
||
shmNamePrefix = "Local\\HomeAgentShm"
|
||
evtRingNamePfx = "Local\\HomeAgentEvtRing"
|
||
evtEventNamePfx = "Local\\HomeAgentEvtSignal"
|
||
envStageShmName = "HOMEAGENT_SHM_STAGE"
|
||
envEvtRingName = "HOMEAGENT_SHM_EVTRING"
|
||
envEvtEventName = "HOMEAGENT_EVT_EVENT"
|
||
)
|
||
|
||
// namedShm 持有一块命名共享段。
|
||
//
|
||
// 不用 *os.File 承载:Windows 的 FileMapping 句柄不是文件句柄,
|
||
// 包进 os.File 后 Close 语义不对(会尝试当文件关)。故用独立类型,
|
||
// 由 shmHandles 表按 mmap 地址反查——freeShm 只拿到 (*os.File, []byte)。
|
||
type namedShm struct {
|
||
name string
|
||
mapping windows.Handle
|
||
addr uintptr
|
||
size int
|
||
}
|
||
|
||
// shmHandles 记录已分配的段,供 freeShm 按数据指针反查句柄。
|
||
//
|
||
// 为何需要这张表:allocShm 的跨平台签名返回 (*os.File, []byte),
|
||
// Windows 没有对应的 fd,只能把句柄存在旁路。key 用切片首地址。
|
||
var (
|
||
shmHandles = map[uintptr]*namedShm{}
|
||
shmHandlesMu sync.Mutex
|
||
)
|
||
|
||
// allocShm 创建命名共享段并映射。
|
||
//
|
||
// 返回的 *os.File 为 nil:Windows 不经 fd 传递段,插件按名字打开。
|
||
// 名字通过 procEnvForShm 注入子进程环境变量。
|
||
func allocShm(size int) (*os.File, []byte, error) {
|
||
name := fmt.Sprintf("%s_%d_%d", shmNamePrefix, os.Getpid(), shmNameSeq.Add(1))
|
||
shm, data, err := createNamedMapping(name, size)
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
shmHandlesMu.Lock()
|
||
shmHandles[uintptr(unsafe.Pointer(&data[0]))] = shm
|
||
shmHandlesMu.Unlock()
|
||
return nil, data, nil
|
||
}
|
||
|
||
// createNamedMapping 建命名段并映射为 []byte。
|
||
func createNamedMapping(name string, size int) (*namedShm, []byte, error) {
|
||
namePtr, err := windows.UTF16PtrFromString(name)
|
||
if err != nil {
|
||
return nil, nil, fmt.Errorf("proc: 共享段名字非法 %q: %w", name, err)
|
||
}
|
||
|
||
// INVALID_HANDLE_VALUE + 命名 → 由系统页文件支撑的匿名段(不落盘)
|
||
mapping, err := windows.CreateFileMapping(
|
||
windows.InvalidHandle, nil, windows.PAGE_READWRITE,
|
||
uint32(uint64(size)>>32), uint32(size), namePtr)
|
||
if err != nil {
|
||
return nil, nil, fmt.Errorf("proc: 创建命名共享段 %q: %w", name, err)
|
||
}
|
||
|
||
addr, err := windows.MapViewOfFile(mapping, windows.FILE_MAP_WRITE, 0, 0, uintptr(size))
|
||
if err != nil {
|
||
windows.CloseHandle(mapping)
|
||
return nil, nil, fmt.Errorf("proc: 映射共享段 %q: %w", name, err)
|
||
}
|
||
|
||
return &namedShm{name: name, mapping: mapping, addr: addr, size: size},
|
||
unsafe.Slice((*byte)(unsafe.Pointer(addr)), size), nil
|
||
}
|
||
|
||
// freeShm 解除映射并关闭段句柄。
|
||
func freeShm(f *os.File, data []byte) error {
|
||
if len(data) == 0 {
|
||
return nil
|
||
}
|
||
key := uintptr(unsafe.Pointer(&data[0]))
|
||
shmHandlesMu.Lock()
|
||
shm, ok := shmHandles[key]
|
||
delete(shmHandles, key)
|
||
shmHandlesMu.Unlock()
|
||
if !ok {
|
||
return nil
|
||
}
|
||
var firstErr error
|
||
if err := windows.UnmapViewOfFile(shm.addr); err != nil {
|
||
firstErr = err
|
||
}
|
||
if err := windows.CloseHandle(shm.mapping); err != nil && firstErr == nil {
|
||
firstErr = err
|
||
}
|
||
return firstErr
|
||
}
|
||
|
||
// shmNameOf 返回某块已分配段的名字(供注入子进程环境变量)。
|
||
func shmNameOf(data []byte) string {
|
||
if len(data) == 0 {
|
||
return ""
|
||
}
|
||
shmHandlesMu.Lock()
|
||
defer shmHandlesMu.Unlock()
|
||
if shm, ok := shmHandles[uintptr(unsafe.Pointer(&data[0]))]; ok {
|
||
return shm.name
|
||
}
|
||
return ""
|
||
}
|