从冯·诺依曼到 Agent 沙箱:虚拟化隔离技术栈的一次完整拆解

LLM Agent 正在把一个老问题重新推到台前:当代码由模型生成、且不可信时,你把它放在哪里执行?

容器的隔离撑不住对抗性负载;传统虚拟机又太重,扛不住每秒几十次的沙箱创建。于是一批沉寂多年的技术突然变得炙手可热——Firecracker、gVisor、Kata Containers、microVM。这些名词经常混在一起出现,但它们分处不同层次、解决不同问题。要真正理解它们的取舍,需要从最底层讲起:从冯·诺依曼架构的"核心与外围",一路讲到 Agent infra 的选型决策。

本文分四部分:先梳理虚拟化技术栈的分层关系,再深入 QEMU 的设备模型理解"外围设备"的本质,然后分析性能差异的根源,最后落到 Agent 沙箱的实战选型。


一、技术栈全景:谁在谁的上面

先用一句话给所有主角定位:

KVM 是 Linux 内核提供的硬件虚拟化基础设施;QEMU 是基于 KVM 的传统全功能虚拟机监视器;Firecracker 是基于 KVM、专为 Serverless 设计的轻量级 VMM;runc 是符合 OCI 规范的容器运行时,与前三者没有直接依赖关系;microVM 则是 Firecracker 这类 VMM 所实现的"轻量级虚拟机"产品形态。

分层关系一图看清:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│ 产品形态层 microVM(Firecracker 等提供的轻量 VM) │
├─────────────────────────────────────────────────────────────┤
│ 运行时层 runc(OCI 容器运行时,不依赖 KVM,共享宿主内核) │
├─────────────────────────────────────────────────────────────┤
│ VMM 层 QEMU(全功能) Firecracker(极简) │
├─────────────────────────────────────────────────────────────┤
│ 硬件虚拟化层 KVM(内核 Hypervisor,CPU/内存虚拟化) │
├─────────────────────────────────────────────────────────────┤
│ 硬件层 Intel VT-x / AMD-V │
└─────────────────────────────────────────────────────────────┘

交汇点:Kata Containers(容器接口 + 任选 VMM 的 microVM 隔离)
层次 组件 角色
硬件虚拟化层 KVM 内核 Hypervisor,CPU/内存虚拟化
VMM 层 QEMU 传统全功能 VMM
VMM 层 Firecracker 极简 microVM VMM
运行时层 runc OCI 容器运行时(不依赖 KVM)
产品形态 microVM Firecracker 等所提供的轻量 VM

逐个拆解:

  • KVM(Kernel-based Virtual Machine) 是 Linux 内核中的虚拟化模块,负责 CPU 与内存的硬件级虚拟化(依赖 Intel VT-x / AMD-V),把 Linux 内核本身变成一个 Hypervisor。它是整个虚拟化栈的最底层——关键在于,它本身不提供任何设备模拟
  • QEMU 是运行在用户态的虚拟机监视器(VMM),通常与 KVM 配合:KVM 负责 CPU/内存虚拟化,QEMU 负责模拟各种设备。功能全面,但体积大、启动慢。
  • Firecracker 是 AWS 用 Rust 编写的 VMM,同样基于 KVM,但只模拟极少数设备,追求极小内存占用(<5 MiB/VM)、超快启动(约 125ms)和高部署密度。它是 AWS Lambda 和 Fargate 的底层技术。
  • runc 是标准容器运行时,负责设置 namespace、cgroup。它完全不涉及虚拟化——容器内的进程与宿主机共享同一个内核。这也是容器隔离的天花板所在
  • microVM 不是软件,而是一类产品概念:只保留必要设备的轻量虚拟机,目标是"接近容器的开销 + 保留虚拟机的硬件级隔离"。

三条典型的调用链:

1
2
3
QEMU + KVM:        QEMU ──/dev/kvm──► KVM(CPU/内存) + QEMU 模拟外设   传统虚拟机
Firecracker + KVM: Firecracker ──/dev/kvm──► KVM + 极简 virtio 设备 Serverless microVM
runc: containerd/Docker ──► runc(namespace+cgroup) 普通容器,与 KVM 无关

三者真正的交汇点是 Kata Containers——一个符合 OCI/CRI 规范的"容器运行时",但在底层为每个 Pod 启动一个轻量虚拟机,VMM 可以选用 QEMU、Cloud Hypervisor 或 Firecracker。这实现了"容器接口 + microVM 隔离"的混合形态。


二、深入 QEMU:它模拟的"外围设备"到底是什么?

上面反复出现一个词:设备模拟。这正是 QEMU 与 Firecracker 的核心差异——QEMU 模拟的设备越完整就越通用,但也越重;Firecracker 砍掉几乎所有设备,于是变得极轻。要理解这个取舍,得先回答:在 QEMU 的语境下,"外围设备"究竟指什么?

定义:在 QEMU 的语境下,"外围设备"指虚拟机中除 vCPU 与客户机内存之外、通过模拟总线挂载的所有设备模型——包括磁盘、网卡、显卡、USB、串口等。

"外围"相对什么而言?

“外围"的参照系是冯·诺依曼架构中以 CPU 和主存为中心的计算核心。CPU 负责取指、译码、执行,主存存放运行中的程序与数据,二者构成系统的"核心”;其余用于输入、输出、外存扩展和通信的设备,统统被称为外围设备。

这个划分的根源在于冯·诺依曼架构本身:CPU 与主存通过高速存储总线直连、紧密耦合;而外设功能各异、速度差异巨大,无法直接挂在处理器总线上,需要经过适配的控制器"外围式"地接入系统总线。CPU 访问外设必须通过统一的 I/O 机制(端口 I/O 或内存映射 I/O)。久而久之,"位于计算核心之外、通过总线挂载、经由 I/O 接口访问"就成了外围设备的结构性特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────── 冯·诺依曼架构 ───────────┐
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ CPU │═══>│ 主存 │ │ ← "核心"(紧密耦合)
│ └────┬────┘ └─────────┘ │
│ │ 高速存储总线 │
│ ▼ │
│ ┌──────────────┐ │
│ │ 系统总线/桥 │ │
│ └──┬───┬───┬───┘ │
│ │ │ │ I/O 控制器 │
│ ┌──┴┐┌──┴┐┌──┴┐ │
│ │磁盘││网卡││显卡│ ... │ ← "外围"(经总线挂载)
│ └───┘└───┘└───┘ │
└─────────────────────────────────────┘

QEMU 模拟一台完整的虚拟计算机时,完全复刻了这一结构:vCPU 是计算核心,-m 分配的客户机内存是主存,二者构成虚拟机的"核心";所有通过 -device 添加的设备模型,无论前端是 virtio 还是仿真硬件,都像真实外设一样挂载在模拟的 PCI/ISA/virtio-mmio 总线上,客户机操作系统通过常规 I/O 途径(PIO/MMIO/DMA)访问它们。

QEMU 模拟了哪些设备?

设备模型分两大类:

  • 真实硬件仿真(e1000 网卡、IDE/SATA 磁盘、VGA 显卡):让客户机无需安装特殊驱动即可运行;
  • virtio 半虚拟化设备(virtio-blk、virtio-net):专为虚拟化设计的标准化设备族,客户机运行知晓自身处于虚拟机的 virtio 驱动,与 QEMU 高效协作,性能远优于纯仿真。

每个设备由前端(呈现给客户机的形态,如 -device virtio-net-pci)和后端(宿主机资源如何被使用,如 -blockdev 指定镜像文件)组成,挂载在模拟的总线上。

按功能划分的常见类别:

外设类别 典型 QEMU 设备模型 说明
块存储 virtio-blk、IDE/SATA、NVMe、SCSI 后端接 qcow2/raw 镜像或 LVM
网络 virtio-net、e1000、rtl8139 后端接 tap/bridge/user 网络栈
显示 virtio-gpu、VGA 显卡/显示控制器
输入 USB tablet、PS/2 键盘鼠标 键盘、鼠标、数位板
串口/控制台 16550A UART、virtconsole 后端重定向到文件/socket
USB usb-host、usb-storage USB 控制器及外设
声卡 virtio-snd、intel-hda 音频设备
主机总线适配器 LSI SCSI、AHCI、PCIe 根桥 为其他设备提供额外总线

此外每块虚拟主板还固定模拟一些平台级设备——如 RISC-V virt 开发板包含中断控制器(CLINT、PLIC)、RTC、fw_cfg 等。严格说这些属于"平台/系统设备"而非纯外设,但它们同样处于"CPU 与内存之外、经总线挂载"的位置。

小结

QEMU 模拟的外围设备,本质上是宿主机用户态进程中运行的一组软件设备模型,它们在虚拟机内以真实外设的面貌出现,让客户机操作系统能用标准驱动程序驱动它们。理解了这一点,Firecracker 的设计就一目了然——它是对这套设备模型的激进裁剪


三、性能差异的根源:syscall 走了什么路径

现在把 gVisor 拉进来,看三种主流沙箱方案的性能本质。先上总表:

维度 gVisor (runsc) Firecracker Kata Containers
隔离机制 用户态内核拦截 syscall KVM microVM 编排层,底层 VMM 可选 CLH/Firecracker/QEMU
硬件虚拟化要求 不需要 必须有 KVM 必须有(含嵌套虚拟化)
启动时间 毫秒级 ~125ms(单机可 150 VM/秒) ~150–300ms
内存开销 每 sandbox 一个 Sentry,较低 <5 MiB/VM VMM + 客户机内核,中等
CPU 密集型 开销极小 接近原生 接近原生
I/O 密集型 慢 10–30%,极端 syscall 场景可达 2–11 倍 良好 接近原生
GPU 直通 不支持 不支持(PCIe 工作已暂停) 可(QEMU+VFIO 路径)
K8s 集成 RuntimeClass runsc 无原生,需自建编排 原生 CRI

三者性能差异的根源,在于 syscall 走的路径不一样:

1
2
3
4
5
6
gVisor:        app syscall ──► Sentry(Go 用户态内核)──► 白名单 syscall ──► 宿主内核
(多一跳 + 重新实现 Linux ABI + Gofer 处理文件 I/O)

Firecracker: app syscall ──► VM 内真实 Linux 内核(VMM 只 ~5 万行 Rust + virtio 设备)

Kata: app syscall ──► VM 内真实 Linux 内核(后端 VMM 可换,走标准 CRI)
  • gVisor:应用 syscall → Sentry(Go 实现的用户态内核)→ 有限的白名单 syscall 到宿主内核。每条 syscall都多了一跳,且 Sentry 重新实现 Linux ABI,文件 I/O 还要经过 Gofer 进程——I/O 慢的部分全在这里。学术 benchmark 曾测出极端 syscall 密集场景下综合性能仅为 Docker 的 ~10%,但这是最坏情况;生产环境的经验值是 I/O 密集任务慢 10–30%,CPU 密集任务几乎无感。
  • Firecracker:syscall 由 VM 里的真实 Linux 内核处理,VMM 本身只有约 5 万行 Rust、只模拟 virtio 设备,路径极短、攻击面极小。代价是设备模型太精简——不支持 PCIe/GPU 直通,这一工作已暂停。
  • Kata:性能特性取决于所选的 VMM 后端。它的差异化价值不在性能,而在于把 microVM 塞进了标准 K8s CRI 工作流——客户机内核直接处理 syscall,所以 I/O 接近原生;需要 GPU 时切到支持 VFIO 的后端即可。

数字需要校准的地方:这些 benchmark 受内核版本、VMM 配置、guest 镜像裁剪程度影响很大。公开数据只能定方向,真正决定选型的是拿自己的工作负载(pip install、浏览器渲染、代码执行)实测。


四、Agent Infra 实战:三种沙箱的选型逻辑

Agent 沙箱的典型工作负载是:LLM 生成代码后,在隔离环境里执行 Python/Shell/浏览器操作、安装依赖、访问网络、读写文件,且按次计费、生命周期短、并发密度高。这决定了选型的三个核心指标:冷启动、隔离强度、GPU/工具链支持

Firecracker:高密度短任务的默认答案

最适合执行不可信生成代码的短生命周期沙箱——代码解释器、临时 shell、FaaS 化的 agent 工具调用。~125ms 冷启动 + <5MiB 内存开销意味着单机可以堆很高的并发密度,硬件级隔离又能扛住对抗性负载。

行业典型是 E2B(Firecracker microVM,~150ms 冷启动,专注 ephemeral 代码执行),以及 AWS Lambda/Fargate、Fly.io

两个明确的坑:不支持 GPU 直通,推理/训练型工作负载走不通;没有现成编排,自建生产系统要搞定 jailer、guest 内核、rootfs、snapshot 生命周期,工程量不小。

gVisor:轻量高密度、无 KVM 环境的首选

最适合算力密集、I/O 不重的 agent 工作负载,以及嵌套虚拟化不可用的宿主机——很多云 VM 默认不开 KVM,gVisor 的 Systrap 平台零依赖就能跑。毫秒级启动对高频短任务更友好。行业典型是 Modal Sandboxes(gVisor 底座)、Daytona,以及 Google 自家的 Cloud Run / App Engine。

坑在 I/O:agent 场景常见的 pip install(海量小文件)、频繁 fork 的测试框架都是 syscall 密集操作,在 gVisor 上会明显变慢;部分冷门 syscall 可能有兼容性缺口,上线前务必用真实工作负载压测。

Kata Containers:K8s 原生 + 长驻工作区 + 需要 GPU

最适合需要标准 Kubernetes 工作流的 agent 平台——多租户 K8s、合规要求高的行业、"每个 agent 一个长驻工作区"的形态。它原生走 CRI,Pod 视角下就是普通容器,底层却是真 VM。

官方的 Agent Sandbox 项目专门针对 agent 运行时做了抽象:WarmPools(预热 Pod 池,消掉冷启动延迟)、Python SDK、自动清理调度。代价是运维复杂度——要维护 VMM、要宿主机支持嵌套虚拟化,比 gVisor 的"drop-in RuntimeClass"重得多。

决策清单

1
2
3
4
5
6
7
8
9
10
11
12
每次执行一段不可信代码、用完即弃(代码解释器、工具调用)
└─► Firecracker(自建)或 E2B(托管)

算力密集、I/O 少、宿主机无 KVM(模型预处理、批量计算)
└─► gVisor(自建)或 Modal(托管)

长驻 agent 工作区、要 K8s 编排、可能要 GPU(browser-use agent、深度研究 agent)
└─► Kata Containers(冷启动敏感就开 WarmPools)

多租户平台、负载异构
└─► 混合方案是行业常态——按 workload 在 Kata+CLH / Firecracker / gVisor 之间路由
(Northflank 的做法)

关联阅读:选好隔离技术后,沙箱是怎么被 agent 领养、后续命令又是怎么找到正确沙箱的?见同日 一次 create_sandbox 调用背后:Agent Sandbox 的控制面、数据面与句柄机制详解——讲清 WarmPool 预热→Claim 领养→Router header 寻址的完整链路。本文讲"用什么隔离",那篇讲"怎么调度路由",两者共同构成 Agent 沙箱的隔离层与调度层。


结语:留多少"外围"的艺术

把全文串起来,可以看到一幅连贯的图景:

  • 冯·诺依曼架构把计算机划分为"核心"(CPU + 主存)与"外围"(经总线挂载的一切设备);
  • KVM 只虚拟化了"核心"——vCPU 与客户机内存,对外设一无所知;
  • QEMU 完整复刻了一台真实计算机:用一整套软件设备模型撑起"外围",从磁盘、网卡到 USB、声卡,让客户机能用标准驱动驱动它们——这正是它"全功能"的代价与价值;
  • Firecracker 做减法:保留 KVM 提供的核心,外围只留最小集合,于是得到启动快、内存省、攻击面小的 microVM;
  • gVisor 走了另一条路:不造机器,造一个用户态内核,在 syscall 层面拦截一切;
  • Kata 则是编排的艺术:把上面这些 VMM 包进标准容器接口,让 K8s 用户无感地获得硬件级隔离。

从冯·诺依曼的"核心/外围"二元划分,到 QEMU 对一台完整计算机的软件复刻,再到 Firecracker 对外围设备的激进裁剪、gVisor 对内核本身的重新实现——整部虚拟化技术演进史,某种意义上就是在"核心"之外,"外围"留多少、留什么的艺术

而在 Agent 时代,这道选择题有了新的评分标准:冷启动毫秒数、单机并发密度、能否扛住一段由 LLM 生成的、可能出错的、甚至恶意对抗的代码。答案依然是那句老话——没有最好的隔离技术,只有最匹配工作负载的隔离技术。在拿真实负载实测之前,任何 benchmark 都只是方向,不是结论。


参考资料