核心架构¶
pVisor 是 Operation 的处理核心:接收操作请求,根据策略决定如何处理和放置,调用实际执行机制,再通过 Event 描述发生了什么。
这条主线分成两个职责:core 提供定义,pvisor 提供实现。 外部调用方提交执行请求,通过运行句柄控制一次执行,通过 Event 观察它的过程和结果。
定义与实现¶
| 所有者 | 职责 |
|---|---|
pvisor-core |
Operation、策略决定、Placement、Outcome、Event,以及跨组件交互契约;共享纯校验与策略求值 |
pvisor |
请求解析、能力准入、实际策略改写、放置选择、调度、执行和 Attempt 生命周期 |
pvisor-journal |
Event 提交、回执、去重、因果引用校验和恢复 |
pvisor-overlay-core |
文件授权接入、写时复制、preimage、review/apply/recovery/drop |
pvisor-overlayfs |
宿主 FUSE 挂载与文件操作接入 |
pvisor-overlaynet |
网络解析、代理转发和 VM 网络接入;落实 core 定义的策略 |
pvisor-guest |
VM 内 PID 1 与命令启动契约 |
pvisor-gateway |
可选的模型协议路由、转换和调用观察 |
pvisor-daemon |
独立的单节点 sandbox 准入、持久归属、原生 VM supervisor 生命周期与端点代理 |
pvisor-cli |
CLI、TUI、cache/replay 前端与 Host Job 应用适配;消费 runtime |
pvisor-vm |
VM、设备、冻结、snapshot 与 RAM 映射;统一 api、私有平台实现 |
pvisor-replay |
Agent 轨迹回放机制;消费 Core/Journal 合同,前端位于 CLI |
Daemon 使用 VM-only NativeRuntime:独立 supervisor 嵌入 PVisor::run,跨 daemon 重启保留 RunHandle。可执行入口通过原生 CLI 设置构造 NativeRuntime;stage/apply、checkpoint/fork 和 Gateway API 未实现,也不自动获取 node 共享。见职责收敛。主机选择和工作流属于外部编排。
core 不拥有执行循环,也不启动进程或打开控制 socket。pvisor 实现 AgentCtl 客户端/服务端和审批 socket;驱动实现各自的文件、网络与隔离边界。默认核心不依赖 Gateway、TUI 或 replay,捕获通过 gateway feature 启用。
API 边界与迁移状态¶
crate 的逻辑职责和 Rust 可见性需要分别检查。当前只有 pvisor-vm、pvisor-overlayfs 和 pvisor-journal 已采用唯一 api 入口;其他 crate 保留既有接口,不能因为架构图已经分层就假定整个 workspace 完成迁移。
| crate | 当前公开边界 | 实现与资源归属 |
|---|---|---|
pvisor-vm |
pvisor_vm::api;VmBuilder、VmmHandle 通过 trait 使用 |
backend、VMM、设备、RAM 和平台分派私有 |
pvisor-overlayfs |
pvisor_overlayfs::api;配置、mount、session 与 metrics 契约 |
fs、mount、observation 私有;不拥有 apply 或 Run 生命周期 |
pvisor-journal |
pvisor_journal::api;JournalStore、TraceProducer、DurableFiles |
journal、trace、persistence 私有;Event/Receipt 由 Core 定义 |
pvisor-core |
领域模块与根级 re-export,尚未迁移 | 共享身份、协议、纯校验与策略定义;不拥有执行资源 |
pvisor-overlay-core、pvisor-overlaynet |
现有公开模块与根级接口,尚未迁移 | 文件语义、双入口文件服务、网络代理与出口数据面 |
pvisor、Gateway、Replay、Daemon 等 |
保留各自既有入口,未统一成单一 api 模型 |
继续按实际生命周期与领域边界演进 |
已迁移 crate 的 api 声明公开数据、字段和 trait 方法;不透明 owner 可以从私有实现重新导出,但状态保持私有。方法体、校验、平台分派和资源管理放在实现中,公开 trait 没有默认方法体;调用方通过所属 crate 的 api 导入合同。API 在支持的平台和 feature 间保持同一声明形状,实际能力用能力查询和明确的不支持错误表达。
迁移按 crate 进行:同时调整该 crate 的调用方、README、公开文档和边界检查,保留外部 API 合同覆盖。尚未迁移的 crate 不应临时套一层全量 re-export 或重复 DTO 来制造一致外观,也不能公开内部模块绕过编译边界。硬件/私有状态检查留在 crate 内;外部调用方只依据所有权、生命周期、失败后状态和同步合同。
这套 Rust 边界不自动提供稳定线上协议或旧记录兼容性。CLI 的 JobCommand/ticket 仍要求精确构建匹配,磁盘格式也有独立 reader。版本对应关系见记录与版本矩阵,VM 边界的背景见ADR 0005。状态依据 lib.rs、VM 的 runtime_modules.rs、各 api.rs 与目录 README 静态核对;不代表本次重新运行了契约测试或平台验证。
Host 与 Guest AgentCtl¶
Host AgentCtl 是内置 Job CLI 操作的权限路径。Guest AgentCtl 是每个 Attempt
面向工作负载 Hello/Sync、客户端状态、directive 与 checkpoint 静默点的
协作路径。两者使用独立 schema、凭据与端点:guest 协作 token 不能授权
宿主 Job、VM、暂存文件或 daemon supervisor 控制。Bundle 的 agentctl
快照仍描述 Guest 协作,不是 Host 授权回执。
Listener 与请求所有权¶
CLI 解析类型化 JobCommand 并连接按需启动的持久 listener。
JobCommand 嵌入 CLI DTO,是要求精确 schema/构建匹配的内部契约,
不是稳定公共 API。Core 的共享 Host envelope 与 supervisor 契约仍是
纯定义和校验,不包含 CLI DTO 或传输。
cli/host_service.rs 用私有锁串行化启动,在规范化
/tmp/pvisor-host-<有效 UID> 下发布 generation/capability manifest 和
以 generation 命名的 Unix socket。根目录属于同 UID、不是符号链接,
权限恰为 0700;socket 与私有 manifest 文件为 0600。
已有条目无效时 fail closed,不用 chmod 修复。listener 独立于
daemon sandbox/pool 所有权及独立缓存。Node 资源协议是运行时设施,没有 daemon acquire/release 适配器。
通过内核同 UID peer 验证和构建兼容协商后,前端用 SCM_RIGHTS 传递
stdin/stdout/stderr,提交类型化命令、cwd、环境、终端上下文及固定目标。
listener 返回关联 ticket,携带 stdio 和私有 worker 通道。前端将检查过的
可执行文件启动为子进程,保留原终端 session,建立独立进程组。
listener 在授予准入前校验注册和 worker readiness;worker 不重新解释
shell argv。cwd、环境、输出与退出状态属于请求,不属于持久 listener。
嵌入式 PVisor 调用仍直接进入 runtime,不要求经过该 CLI 前端。
持久 Job selector 无需端点参数。选中的记录固定到 Job、Attempt 及存在时 的执行 generation,并在产生副作用前复核;过期选择不会悄悄指向新目标。 Host live 控制和 stage 发现链接也使用共享权限根目录下的私有端点。 executor 将权限根目录排除在 guest 暴露范围之外;CLI 拒绝暴露它的 guest 文件系统源。同 UID 是宿主信任边界,不是对该用户所有进程的隔离。
线上契约与升级¶
Core 定义 AgentCtlHostRequest<C>(version、request_id、可选 target、
command)与 AgentCtlHostResponse<R>(version、request_id、result)。
当前版本为 1;target 包含 job_id、可选 attempt_id 与可选
generation。端点所有者验证权限与目标范围。Live Attempt 端点要求精确
Job/Attempt,拒绝独立 generation。Envelope 拒绝未知字段;身份非空,
至多 256 字节,不含控制字符。Job 服务及其内部 worker 使用
runtime/host_transport.rs 的换行分隔 JSON;async/sync 使用相同 framing
规则,JSON 上限为 1 MiB,不含换行分隔符。reader 只消费到该分隔符,
保留下一个 frame 或 FD marker。SCM_RIGHTS marker 字节是独立传输记录,
不是 JSON;描述符处理由 cli/host_fds.rs 负责。类型化错误码为 invalid_request、unauthorized、version_mismatch、
conflict、unsupported、internal 和 unavailable。
Core 的 host_protocol 还定义 live VM 的 HostVmCommand(Pause、
Resume、Offload、Status)及 HostVmResult(status、value)。
pvisor 导出 host_vm_exchange,用于类型化 Host 请求/响应交换。
--vm-load 选择 Resume,映射为
同一 live Attempt 的 RunResume,不是 Load 线上操作。
内部 Job 握手在接纳描述符或命令之前检查 Host 版本 1、Job ticket schema、Cargo 包版本及可执行文件内容的 BLAKE3 摘要。 仅包版本相同不代表兼容;原地重新构建也可能不兼容。worker 启动前检查 程序路径、所有权、权限、device/inode 与文件内容。
Linux 读取 /proc/self/exe。macOS 的 cli/host_image.rs 在对同一个
已打开文件求摘要之前,将 dyld 已加载主映像的 UUID 与磁盘 Mach-O 中
匹配 CPU slice 的 LC_UUID 比较。UUID 元数据缺失、格式错误、有歧义
或不匹配时 fail closed;准入要求源 Mach-O UUID 匹配。该检查验证首次路径替换前后的
可执行文件身份。UUID 匹配不等于已加载内存的
逐字节认证,也不等于内核固定的 exec 权限。macOS 平台路径尚未编译或
测试;parser 检查不能验证 dyld 访问、平台链接或真实程序替换行为。
Daemon 原生 supervisor 使用同一 version-1 换行 Host envelope,
配合私有 owner/token 凭据和 Job/Attempt/generation 目标。它不使用 Guest
Hello/Sync,也不使用 CLI worker ticket 机制。该线上格式与旧 supervisor
不兼容。升级前使用旧二进制排空 sandbox;同样先排空活动 CLI 请求并停止
旧 Job listener,再替换程序。新客户端在提交描述符或命令之前拒绝不兼容
的 live listener。不提供 legacy fallback,也不透明接管旧 supervisor。
取消与验证限制¶
前端在启动和准入之前锁存 SIGINT/SIGTERM/SIGHUP,发送关联取消,并负责
恢复终端及回收 worker。listener 持有请求清理所有权直到 worker 完成,
可升级清理强度。Linux 实现 subreaper 收养、/proc 后代跟踪及基于 pidfd
的信号发送,并将 listener 排除在请求清理之外。macOS 跟踪出生身份已确认
的后代及已知工作负载进程组,可跨普通进程组变化。清理先冻结 root 与发现
的 forker,反复扫描直到跟踪集合稳定,再逐个发送经出生身份复核的信号。
清理范围包含 worker 进程组之外跟踪到的后代。不保证拥有发现前已 reparent、因而漏掉的孤儿;
libproc 身份检查后按数字 PID 发信号,不是原子 pidfd 操作,也不提供
Linux 等价的 containment。macOS 清理路径尚未编译或测试。
持久 listener 提供请求接入,不持久化请求队列。request_id 关联响应、错误、ticket 与
取消;通用去重和 exactly-once 不在其合同范围内。部分持久 Job 操作保留自己
范围内的回执,但不覆盖所有 Host 命令。断连、超时和取消可能发生在副作用
之后;前端报告不确定性,不自动重试。决定再次提交前,先核对 Job 状态和
产物。
Host AgentCtl 路径尚无真实 VM TUI 端到端验证。现有传输、进程或 mock 检查的 验证范围不含 guest 正确性、生产级持久性或完整平台行为;macOS 身份与清理 路径的限制见上方说明。
一条生产执行路径¶
准备驱动会建立真实的文件与网络资源,执行器派发才让工作负载开始运行。把这两个步骤分开,可以在必要启动事实提交失败时阻止派发,同时由 Session 收回已经准备的资源。结束时同样先收敛资源和观察,再公布终态;调用方取消等待不等于这些动作已经完成。
RunSpec 是调用方的执行配置输入;Operation 是结构化的操作描述。目前唯一生产操作是 run.execute,包含程序、参数和工作目录。执行器实际消费有效 RunSpec 及准备好的驱动附件。PVisor::resolve_operation 使用同一准入路径,供启动前审查,不能代替实际执行及安装证据。
准入保留请求与有效操作的快照。实际策略变化记录为 Rewritten,选定的 VM/Overlay 放置记录为 Placed。拦截发生在实际驱动边界:文件操作进入 OverlayFS/OverlayCore,网络流量进入 OverlayNet。它们共享策略定义,但当前并没有将每个文件或网络操作都提升为独立的公共 Operation。
例如,网络驱动把请求的 Ambient 能力收窄为 Deny:Requested 保留原始权限,Rewritten 保存收窄前后快照,Placed 描述最终放置,执行器按有效配置运行。这些快照记录实际策略处理;通用规则解释器不在当前实现范围内。
一个生命周期所有者¶
当前每次 PVisor::run 创建一个 Attempt,由 pvisor 中的 Session 统一持有和管理;Job、Run 与 Attempt 的身份区分见执行模型。
Session 负责驱动准备、Guest AgentCtl server、取消与超时、执行后清理、观察检查、Bundle 保存及终态公布。执行器返回 ExecutorOutput,不自行分配 Job/Attempt 身份或公布终态。RunHandle 提供状态、取消、checkpoint 和事件订阅;取消请求不等于执行已经停止。
Process/VM 清理其受管理的进程组;容器使用 runtime 的终止接口。进程组之外的后代和各平台隔离缺口见隔离设计。Guest AgentCtl 负责工作负载协作和 checkpoint 静默点,本身不是强制控制;Host AgentCtl 则是上方描述的独立宿主权限路径。
Event 是观察接口¶
外部观察的是 Event,不需要依赖 Session 的内部字段。事件链、身份、因果引用与记录边界见 Operation 与 Event。
当前实现先准备驱动,再提交启动事实,最后调用执行器。必要启动事实提交失败会阻止执行器派发并清理准备资源;准备阶段仍可能产生文件或 socket 副作用。
策略、控制与证据¶
策略、准入计划与执行后观察是三个独立层次,不能互相替代;ExecutorPlan/ExecutorObservations 的等级与证据口径见能力与证据。
core 共享文件/网络策略求值。user、workspace、session 和执行器基础策略共同约束权限,后面的 allow 不能覆盖前面的显式拒绝。实际授权、拦截和控制安装由 pvisor 与驱动落实。
记录与文件应用¶
| 记录 | 回答的问题 |
|---|---|
run.json |
这项 Job 的身份、状态、执行器及本地资源是什么? |
| Run Bundle | 结果、控制观察、产物及文件/网络摘要是什么? |
| Event Journal/Trace | 已发布了哪些事实,它们如何关联? |
| Overlay diff/preimage | 哪些文件仍待审查,应用时应检查什么原始状态? |
这些记录各有范围。Event 可以重建已观察到的操作过程,无法仅凭日志恢复全部外部状态。Agent 原生轨迹 replay 也不是任意副作用的确定性重放。
暂存文件先审查再应用。OverlayCore 负责目标校验、持久化应用意图、更新及恢复;一个批次不是原子文件系统事务。apply、drop 与文件检查点的恢复范围及不能撤销的外部副作用见能力与证据,操作流程见审查与应用。