已开启
feat: support incremental memory checkpoints for StratoVirt #155
Yekelu创建于 29 天前
feat: support incremental memory checkpoints for StratoVirt #155
已开启
Yekelu创建于 29 天前
Yekelu
Yekelu成员
29 天前

当前PR是否有AI参与:

[x] 否

[ ] 是
__1. AI Agent 平台:
__2. AI 模型:
__3. Prompt上下文 :

参考:openEuler社区《生成式AI工具使用与开源贡献政策》

PR功能描述 / 为什么需要这个合入**:

背景

upstream/dev 中 StratoVirt checkpoint 仅支持全量内存:

暂停 VM
→ 捕获完整 StratoVirt state 和 memory
→ 恢复 VM
→ 发布独立的全量 memory component

恢复时,StratoVirt 直接映射完整内存文件。每次 checkpoint 都会重新保存全部 Guest 内存,无法复用上一代 checkpoint,也不支持按需加载页面。

本 PR 在保留原有全量流程的基础上,新增:

  • conch-cow 增量内存恢复组件;
  • full、incremental、auto 三种全局内存模式;
  • full-v1、incremental-v1 两种 checkpoint 内存格式;
  • base/delta 内存 layer 和 manifest.json;
  • StratoVirt 外置内存捕获、内存 mappings、page state 和 dirty generation 接口;
  • 基于 memfd、UFFD 和 UFFDIO_WAKE 的按需页面恢复;
  • conchd 对 conch-cow 的进程生命周期管理。

full 仍为默认模式,因此默认行为与 upstream/dev 保持一致。

———

用户配置变化

在 conchd 配置文件的 sandbox 段新增:

sandbox:
# full、incremental 或 auto,默认 full
memory_mode: full

# 由 conchd 启动的 conch-cow 二进制
cow_binary: /usr/bin/conch-cow

# conchd 与 conch-cow 通信的 Unix Socket
cow_socket: /run/conch/cow.sock

约束如下:

  • memory_mode 只接受 full、incremental 和 auto;
  • cow_binary 必须是绝对路径;
  • cow_socket 必须是绝对路径;
  • 三个模式下 conchd 都会启动 conch-cow;
  • 本 PR 不增加 per-Sandbox 的 CLI 或 HTTP 模式参数。

三种模式

配置模式 full-v1 incremental-v1
full ✅ 支持 ❌ 拒绝
incremental ❌ 拒绝 ✅ 支持
auto 使用 full 恢复 使用 incremental 恢复

auto 的具体规则:

  • 冷启动且 COW 返回 supported:使用 incremental;
  • 冷启动且 COW 返回 unsupported:回退到 full;
  • COW 连接失败或返回 unknown:创建失败,不静默回退;
  • 恢复 full-v1:使用 full;
  • 恢复 incremental-v1:使用 incremental,并要求 COW 支持;
  • Sandbox 创建完成后,实际模式固定,后续 checkpoint 不会动态切换。

Cloud Hypervisor 始终使用 full 路径,不进入 StratoVirt 增量内存流程。

———

conch-cow 组件

组件职责

conch-cow 负责增量 checkpoint 的运行时内存恢复:

  • 探测宿主机是否满足增量内存要求;
  • 加载并固定 manifest.json 和所有内存 layer;
  • 创建作为 Guest 内存后端的 memfd;
  • 接收 StratoVirt 传递的 userfaultfd 和内存 mappings;
  • 根据缺页地址找到对应 Guest offset;
  • 从最终所属 layer 读取页面;
  • 将页面写入 memfd;
  • 使用 UFFDIO_WAKE 唤醒 StratoVirt 的缺页线程;
  • 在 Sandbox 删除或创建回滚时释放 attachment。

conch-cow 不管理 StratoVirt 进程,不持有 pidfd,也不负责停止 StratoVirt。

控制协议

conchd 通过本地 Unix Socket 调用以下接口:

接口 输入 返回或效果
Capabilities 无业务参数 返回 supportedunsupportedunknown,以及 missing_featuresprobe_error
Attach sandbox_idmemory_snapshot_root 返回 attachment token、UFFD socket、内存大小和 block 大小,并通过 SCM_RIGHTS 返回 memfd
WaitAttachmentReady tokensandbox_id 等待 StratoVirt 完成 UFFD 交接;不传递文件描述符
Detach token 停止对应的 UFFD worker,关闭 COW 持有的 memfd、UFFD 和 layer 文件

token 用于标识一次 attachment,并关联后续的 WaitAttachmentReady 和 Detach。

能力探测

Capabilities 检查:

  • userfaultfd 是否可创建;
  • UFFD 是否支持:
    • uffd.missing_shmem
    • uffd.pagefault_flag_wp
    • uffd.wp_hugetlbfs_shmem
    • uffd.wp_async
  • Host page size 是否能够按 checkpoint block size 对齐;
  • /proc/self/pagemap 是否可以打开和读取。

返回语义:

  • supported:所有要求均满足;
  • unsupported:内核明确缺少能力,并通过 missing_features 返回缺失项;
  • unknown:探测过程发生运行错误,通过 probe_error 返回原因;pagemap 相关错误还会携带对应的 missing_features。
    conchd 启动时只要收到合法的三种能力状态之一,就认为 COW 控制协议已经就绪。具体是否允许创建 incremental Sandbox,由内存模式决策处理。

memfd 与 UFFD 交接

恢复 incremental checkpoint 时:
conchd
→ Attach(sandbox_id, memory_snapshot_root)
→ conch-cow 创建 memfd
→ SCM_RIGHTS 将 memfd 传给 conchd
→ conchd 将 memfd 作为继承 FD 传给 StratoVirt
→ StratoVirt 注册 Guest 内存 UFFD
→ StratoVirt 通过一次性 UFFD Socket 将 userfaultfd 和 mappings 传给 conch-cow
→ conchd 调用 WaitAttachmentReady
→ UFFD 交接完成后恢复 VM

memfd 使用 share=off。本方案不注册或调用 UFFDIO_COPY。

Guest 缺页时:

UFFD page fault
→ 根据 fault HVA 找到对应 mapping
→ 换算为 Guest memory offset
→ 根据 manifest.build_map 找到最终 layer
→ 从 layer 读取页面
→ pwrite(memfd)
→ UFFDIO_WAKE

SCM_RIGHTS 会在接收进程中创建独立的 FD 引用。因此 COW Detach 只会关闭 COW 持有的 memfd,conchd 和 StratoVirt 必须分别管理各自的 FD。

如果 Sandbox 在 StratoVirt 接管 memfd 之前创建失败,conchd 会关闭自己收到的 memfd,再调用 COW Detach,完成整个 attachment 回滚。

———

checkpoint 内存格式

在 Boot Index 引用的 mem-snapshot descriptor annotation 中新增:

io.conch.memory-format=full-v1
io.conch.memory-format=incremental-v1

annotation 位于 Boot Index 的 memory component descriptor 上,不写入 memory 文件,也不重复写到每一个 OCI layer。

full-v1

包含完整 StratoVirt state 和 memory,可以独立恢复。

incremental-v1

memory component 内部包含:

  • manifest.json;
  • 一个或多个不可变 OCI EROFS memory layer;
  • 描述最终页面所有权的 build_map。

Boot Index 仍只引用一个 mem-snapshot component。该 component 的 OCI manifest 保存完整 layer 列表。

Containerd 负责保存和解包不可变 OCI 内容;Conch 负责解释 manifest、维护 layer 所有权、构建运行时 memfd,并推进 checkpoint head。

本 PR 不兼容缺少 io.conch.memory-format 的旧 StratoVirt checkpoint。

———

full 模式完整流程

冷启动

Service.CreateSandbox
→ memorymode.Resolve(full)
→ Sandbox Manager
→ BootPreparer
→ StratoVirt 普通冷启动

full 业务路径不会调用 COW 的 Attach、WaitAttachmentReady 或 Detach。

与 upstream/dev 相比,仅新增创建前的模式解析;实际 VMM 冷启动流程保持不变。

生成 checkpoint A

暂停 VM
→ StratoVirt 生成完整 state 和 memory
→ 恢复 VM
→ 发布完整 mem-snapshot component
→ 标记 full-v1
→ 校验 Boot Index
→ 创建 checkpoint Template 并推进 checkpoint head

checkpoint A 可以独立恢复。

从 checkpoint A 恢复

检查 Boot Index
→ 识别 full-v1
→ 准备完整内存目录
→ StratoVirt 使用 mapped=true 恢复
→ ResumeVM

不创建 memfd,不建立 COW attachment,也不使用 UFFD。

恢复内存大小来自 checkpoint 中记录的 memory_size_mb,不会被创建请求中的 ram_mb 覆盖。

再次生成 checkpoint B

仍走完整全量捕获流程。B 包含独立的完整 state 和 memory,不依赖 A。

———

incremental 模式完整流程

冷启动

Service.CreateSandbox
→ memorymode.Resolve(incremental)
→ cow.Capabilities
→ Sandbox Manager
→ StratoVirt 普通冷启动

此时:

  • 只检查 COW 能力;
  • 不调用 Attach;
  • 不创建恢复 memfd;
  • 不建立 UFFD attachment;
  • Sandbox 运行时对象记录有效模式、内存大小和 block 大小。

冷启动后的第一个 checkpoint:创建 base A

暂停 VM
→ checkpointPoisoned=true
→ 捕获外置 StratoVirt state
→ 查询 Guest memory mappings
→ 查询 page state
→ 从 /proc//mem 导出页面
→ 创建稀疏 base layer
→ 写 manifest.json
→ 恢复 VM
→ 发布 incremental-v1 memory component
→ 校验 Boot Index
→ 创建 checkpoint Template 并推进 checkpoint head
→ checkpointPoisoned=false

因为冷启动实例没有父 manifest,第一个 checkpoint 必须建立能够覆盖完整 Guest 地址空间的逻辑 base。

这不代表必须物理写满整个内存文件:确定为零或未驻留的页面可以保持稀疏。

从 base A 恢复

containerd 解包 memory component
→ conchd 调用 COW Attach
→ COW 加载并固定 manifest/layers
→ COW 创建 memfd 和 UFFD Socket
→ memfd 通过 SCM_RIGHTS 返回 conchd
→ conchd 启动 StratoVirt
→ StratoVirt 将 UFFD 和 mappings 交给 COW
→ conchd 调用 WaitAttachmentReady
→ 恢复 VM
→ 缺页时由 COW 按需填充 memfd

Sandbox 删除时,conchd 调用 Detach 释放 attachment。

基于 A 生成 delta B

暂停 VM
→ checkpointPoisoned=true
→ 捕获外置 StratoVirt state
→ 查询 Guest memory mappings
→ 查询带 generation 的 dirty bitmap
→ 只导出本代脏页
→ 创建新的 delta layer 和 manifest
→ 清除本次读取的准确 dirty generation
→ 恢复 VM
→ 向父 memory component 追加一个 OCI layer
→ 校验 Boot Index
→ 原子创建 checkpoint Template 并推进 bbolt head
→ checkpointPoisoned=false

B 的 memory component 包含:

  • A 的原 OCI layer;
  • B 新增的 delta layer;
  • 更新后的 manifest 和 build_map。

build_map 直接记录每段 Guest 内存最终应从哪个 layer 读取,因此恢复时不需要从 base 开始逐层重放。

checkpointPoisoned 只标记 Sandbox Manager 持有的运行时对象,不写入 bbolt,也不是用户可见的 Sandbox 状态。只有 OCI 发布、Boot Index 校验和 checkpoint head 推进全部成功后,才允许该运行实例再次
checkpoint。

———

conch-cow 生命周期与 systemd

本 PR 不新增 conch-cow.service。

systemd 只管理 conchd.service,conchd 根据配置启动:

/usr/bin/conch-cow --socket /run/conch/cow.sock

conchd 启动顺序:

启动 conch-cow
→ 轮询 Capabilities,等待控制协议就绪
→ 初始化其他 conchd 子系统
→ 向 systemd 发送 READY=1

正常关闭顺序:

停止 HTTP 服务
→ 删除全部 Sandbox,并执行 Detach
→ 向 conch-cow 发送 SIGTERM
→ Wait 回收 conch-cow
→ 关闭嵌入式 containerd
→ 关闭状态库

conchd.service 继续使用 KillMode=process。正常停止时 systemd 只向 conchd 主进程发送信号,StratoVirt 和 conch-cow 由 conchd 的正常清理流程处理。

本 PR不处理:

  • conchd 崩溃或被 SIGKILL;
  • conch-cow 自动重启;
  • Pdeathsig;
  • 子进程重新接管;
  • SIGTERM 超时后的 SIGKILL 兜底。

Makefile 新增:

make build-conch-cow

输出为 bin/conch-cow。

———

对外接口变化

CLI

CLI 命令及参数结构不变:

conch sandbox create ...
conch sandbox checkpoint

不增加 per-Sandbox memory mode 参数。

HTTP

创建和 checkpoint 请求结构不变:

POST /api/v1/sandboxes
POST /api/sandbox/checkpoint

新增行为:

  • 模式与 checkpoint 格式冲突时返回 HTTP 412;
  • incremental 模式能力不满足时返回前置条件错误。

本地 COW 协议

新增 conchd 与 conch-cow 之间的 Unix Socket 协议:

  • Capabilities
  • Attach
  • WaitAttachmentReady
  • Detach

该协议不是对外 HTTP API。

OCI 格式

新发布的 StratoVirt checkpoint 必须携带:

io.conch.memory-format=full-v1

或:

io.conch.memory-format=incremental-v1

———

单元测试

新增或调整的测试覆盖以下内容。

配置与模式决策

  • memory_mode、cow_binary 和 cow_socket 的默认值及合法性校验;
  • full、incremental、auto 的完整决策矩阵;
  • Cloud Hypervisor 始终选择 full;
  • 模式与 checkpoint 格式冲突;
  • HTTP 412 错误映射;
  • 创建 Sandbox 前完成模式解析。

COW 控制协议

  • 分段 frame 读取;
  • 超长 frame、尾随 JSON 和非法字段拒绝;
  • 各操作所需字段和 FD 数量;
  • Attach 只返回一个 memfd;
  • WaitAttachmentReady 使用 token 和 sandbox_id,不传递 FD;
  • 非法 incremental_memory 能力状态拒绝;
  • Detach 幂等;
  • 独立 attachment 使用独立 memfd。

能力探测和页面恢复

  • UFFD 能力缺失与运行错误分类;
  • missing_features 和 probe_error;
  • Host page size 兼容性;
  • UFFD mappings 校验;
  • fault HVA 到 Guest offset 的转换;
  • build_map layer 选择;
  • pwrite(memfd) 后调用 UFFDIO_WAKE。

base、delta 和 OCI

  • base 逻辑覆盖完整 Guest 内存;
  • delta 只替换脏页所有权;
  • manifest 原子写入和重新加载;
  • layer 文件在 attachment 生命周期内保持打开;
  • 每个 checkpoint epoch 只追加一个 OCI layer;
  • memory format annotation 的发布与解析;
  • checkpoint Template 父子关系和 head 推进。

Sandbox 与生命周期

  • incremental restore 的 Attach 和 WaitAttachmentReady 调用;
  • Attach 返回内存几何与 Boot Index 的一致性校验;
  • Sandbox 清理链执行 Detach;
  • Sandbox 在 StratoVirt 接管 memfd 前创建失败时,关闭 conchd 本地 FD并执行 Detach;
  • conchd 启动 COW、等待就绪,并在正常关闭时发送 SIGTERM 和 Wait;
  • COW 在就绪前退出时 conchd 启动失败;
  • checkpoint head 成功推进后才清除 checkpointPoisoned。

该PR关联的issue

(格式为fixes #<issue号>, 或者resolves #<issue号>): fixes #

希望检视人员了解:

likedislike
合并受阻
YekeluYekelu成员
29 天前 创建了 pull request,commit 645e60d8
atomgit-bot
atomgit-bot
29 天前 评论:

变更摘要

本 PR 为 StratoVirt 虚拟机新增增量内存 checkpoint 能力,并引入独立的 conch-cow 常驻服务在恢复阶段通过 memfduserfaultfd 按需填充 Guest 内存页面,避免每次 checkpoint 重复保存完整内存。同时保留 Cloud Hypervisor 原有全量内存流程,通过全局配置 sandbox.memory_modefull/incremental/auto)控制模式选择,并在 OCI 镜像标注 io.conch.memory-format 注解以区分 full-v1incremental-v1 格式。

主要改动

  • 新增 conch-cow 守护进程与 Unix Socket 协议层: 新增 cmd/conch-cow/main.go 入口和 internal/cow 包(含 ServerClientprotocol.gofdpass.goprobe.gouffd.go),实现 Capabilities/Attach/WaitAttachmentReady/Detach 四个内部接口,通过 SCM_RIGHTS 传递 memfd、UFFD 和 pidfd 文件描述符;缺页时读取 manifest 对应 layer 页面后 pwrite 写入 memfd 并调用 UFFDIO_WAKE,而非使用 UFFDIO_COPY

  • 新增增量内存快照格式与 checkpoint 捕获: 新增 internal/memsnap 包定义 Manifest/BuildMap/BuildRange 结构和 base/delta layer 的创建与原子写入;新增 internal/sandbox/incremental_checkpoint.goIncrementalCheckpointCapture),通过 QMP 查询 StratoVirt 的 dirty bitmap 或 page state,结合 internal/vmm/memory_export.go/proc/<pid>/mem 按页导出,生成 delta layer 并更新 BuildMap 所有权。

  • 新增内存模式决策与 OCI 格式标注: 新增 internal/memorymode 包(Resolve 函数),根据 RequestedModefull/incremental/auto)、cow 能力探测结果和 artifact 格式进行模式决策;在 internal/image/types.go 定义 AnnotationMemoryFormat 常量,BootIndexInfoResolvedBoot 结构传播 MemoryFormat 字段,模式与 checkpoint 格式冲突时返回 HTTP 412。

  • StratoVirt 适配增量恢复与内存适配器接口: 在 internal/vmm/stratovirt/stratovirt.go 新增 incrementalRestoreScriptStratovirt 模板(memory-backend-memfd + share=off + uffd_sock),新增 QMP 命令 query-mem-mappings/query-mem-page-state/query-mem-dirty-bitmap/clear-mem-dirty-bitmapCreateExternalMemorySnapshot;在 internal/vmm/driver/types.go 定义 IncrementalMemoryAdapter 接口和 MemoryMapping/MemoryPageState/MemoryDirtyBitmap 数据结构。

  • Sandbox 生命周期集成与 checkpoint poison 保护: Manager.Create 中集成 prepareIncrementalMemory(cow Attach)和 incrementalMemoryAttachment 生命周期;增量 checkpoint 时通过 checkpointPoisoned 原子标记防止失败后重复 checkpoint,CompleteCheckpoint 在 OCI 内容发布与 head 推进后清除标记;IncrementalCheckpointCapture 保证 pause → poison → capture → clear dirty → resume 的事务顺序。

likedislike
不准确?
atomgit-bot
atomgit-bot
29 天前 评论:

🤖 AI Code Review

⚠️ 审查未能完成

本次代码审查未能完成,可能是临时性故障。

你可以在评论区输入 /ai review 重新发起审查。若多次失败,请联系管理员并附上下方追踪 ID。

追踪 ID: task-176530-run-169660

likedislike
不准确?
openeuler-ci-botopeneuler-ci-bot成员
29 天前 将weibaohui,yangzhao_kl,jimmieme,duguhaotian,wonleing,zmr_2020,gaodaweiky,lu-wei-army,zhaoyifan28设为审查人
openeuler-ci-botopeneuler-ci-bot成员
29 天前 添加了label:stat/needs-squash
此处折叠了41条消息 查看更多
zhaoyifan28成员21 天前进行代码检视1
internal/cow/protocol.go
@@ -0,0 +14,4 @@
14+ RequestDetach = "Detach"
15+)
16+ 
17+const (
zhaoyifan2821 天前评论:

Capability探测并没有必须在conch-cow做的理由,建议放到conchd来做降低复杂度

likedislike
zhaoyifan28成员21 天前进行代码检视1
internal/cow/protocol.go
@@ -0,0 +17,4 @@
17+const (
18+ CapabilitySupported = "supported"
19+ CapabilityUnsupported = "unsupported"
20+ CapabilityUnknown = "unknown"
zhaoyifan2821 天前评论:

同上,unknown不用单独区分,可以直接和unsupported一起导致报错退出

likedislike
zhaoyifan28成员21 天前进行代码检视1
config/config.yaml
@@ -42,6 +42,12 @@ vmm:
4242 binary: /usr/bin/stratovirt
4343 
4444sandbox:
45+ # Global StratoVirt memory policy: full, auto, or incremental. Defaults to full.
zhaoyifan2821 天前评论:

暂不实现auto,让用户显式指定,不支持就报错

likedislike
zhaoyifan28成员21 天前进行代码检视1
internal/memorymode/resolver.go
@@ -0,0 +26,4 @@
26+ EffectiveIncremental EffectiveMode = "incremental"
27+)
28+ 
29+const (
zhaoyifan2821 天前评论:

这里的定义和image/types.go重复

likedislike
zhaoyifan28成员20 天前进行代码检视1
internal/cow/server.go
@@ -0,0 +458,4 @@
458+ return removeControlSocketIfOwned(path, identity)
459+}
460+ 
461+func removeControlSocketIfOwned(path string, expected unixSocketIdentity) error {
zhaoyifan2820 天前评论:

这里为了处理socket被其他进程占用的边界情况引入过多复杂度,建议删除

likedislike