refactor(shm): 变长块分配器 + 工具调用 funccall 调用帧(§13.2/§13.3)

按架构约束推进两步:

1) 分配器收回内核后,定长槽失去了唯一存在理由
   (定长槽只是为了绕开"跨进程无法安全变长分配"),
   于是 arena 换成**变长块分配器**:first-fit + 邻块合并,
   块头 16B(size/state/owner/prevSize)。内核可以按需标定每块大小,
   payload 不再受固定槽容量限制(旧上限 16KB)。

   - Alloc/Put/Read/Free/ReclaimOwner 全在内核进程内,一把 sync.Mutex
   - Free 走块链校验 offset 是已分配块的数据起点 + owner 匹配,
     伪造引用不能改动分配器状态
   - Read 允许块内偏移(调用帧的结果区就在帧块中间),但不许跨块边界
   - arena 4MB,底层 memfd 惰性分配:未触碰的页不占物理内存

2) 工具调用 payload **始终**走共享内存,取消按大小切内联的分支。
   按 funccall 模型,内核(caller)标定调用帧交给插件(callee):

       [0, ArgsLen)              参数 JSON
       [ArgsLen, Frame.Length)   结果区(内核预留的预算)

   结果超出预算时插件才 `arena.alloc` 扩容块,引用上打
   sharedRefFlagExpand 让内核单独归还。Cleaner 复用同一帧模型。
   ToolInvokeParams.Args / ToolInvokeResult.Result 仅剩给直连 RPC 的
   测试(process/bench 不建 Host);生产路径永远走帧。

测试:
- TestArena_*(分配/归属/回收/并发唯一/耗尽/超限/伪造 offset/
  相邻合并/对齐/布局校验)
- TestPlugin_ToolInvokeArgsResultViaArena:大**小** payload 都经帧往返,
  结果一致且 arena 归零("小 payload 同样走调用帧"是本轮行为变更)
- TestPlugin_ArenaAllocFreeAcrossProcess、TestE2E_* 保持通过
- 顺手修 process_test.go 一处预存 doc-comment 分段
This commit is contained in:
JianFeeeee
2026-09-10 18:54:36 +08:00
parent f6f92a8a6e
commit b096e8ba2c
10 changed files with 921 additions and 516 deletions

View File

@ -30,8 +30,11 @@ type Host struct {
seg *Segment // StageContext segment位于 unified ctxData
shmSize int // 统一区域总大小
// arena 是跨进程共享槽池§13.2 重设计)。分配/释放走共享位图 CAS
// 任意进程/goroutine 并发调用都安全,不需要任何锁
// arena 是跨进程共享内存分配器§13.2 重设计),由内核独占管理:
// 插件通过 RPC 申请/归还,不做任何分配决策
//
// 分配与回收都在内核进程内进行,一把 mu 即可保证安全,
// 不存在跨进程分配器那种“共享游标被两个进程各自更新”的竞态。
arena *arenaRegion
// nextOwner 给每个插件进程分配一个不透明 owner ID供 ReclaimOwner 使用。
@ -77,15 +80,15 @@ func NewHost() (*Host, error) {
return nil, err
}
// 初始化 Exchange Arena 槽池
arena, err := initArena(data, ur.arenaOff, ur.arenaCap, arenaDefaultSlotCount, arenaDefaultSlotSize)
// 初始化 Exchange Arena(内核独占的变长块分配器)
arena, err := initArena(data, ur.arenaOff, ur.arenaCap)
if err != nil {
freeShm(memfd, data)
return nil, fmt.Errorf("槽池初始化: %w", err)
return nil, fmt.Errorf("共享内存分配器初始化: %w", err)
}
if used, total := arena.Stats(); used != 0 || total != arenaDefaultSlotCount {
if used, total := arena.Stats(); used != 0 || total != ur.arenaCap {
freeShm(memfd, data)
return nil, fmt.Errorf("槽池初始化异常used=%d total=%d", used, total)
return nil, fmt.Errorf("分配器初始化异常used=%d total=%d", used, total)
}
// 创建 StageContext segment位于 SuperBlock 之后)