Firecracker

文章目录

    • 前言
    • [1. 整体架构:Firecracker、KVM、microVM 各管什么](#1. 整体架构:Firecracker、KVM、microVM 各管什么)
      • [1.1 三个家伙不在一个层次](#1.1 三个家伙不在一个层次)
      • [1.2 创建 VM 和运行应用是两条路](#1.2 创建 VM 和运行应用是两条路)
      • [1.3 Firecracker 进程内部也有分工](#1.3 Firecracker 进程内部也有分工)
      • [1.4 设备清单必须标明实现归属](#1.4 设备清单必须标明实现归属)
    • [2. VirtIO:驱动、设备后端、共享队列怎么配合](#2. VirtIO:驱动、设备后端、共享队列怎么配合)
      • [2.1 半虚拟化的核心是签合同](#2.1 半虚拟化的核心是签合同)
      • [2.2 Virtqueue:一张双方共用的待办清单](#2.2 Virtqueue:一张双方共用的待办清单)
      • [2.3 VirtIO 省的到底是哪种切换](#2.3 VirtIO 省的到底是哪种切换)
      • [2.4 ioeventfd、irqfd 与事件循环怎么衔接](#2.4 ioeventfd、irqfd 与事件循环怎么衔接)
    • [3. virtio-net:网络报文怎么离开 microVM](#3. virtio-net:网络报文怎么离开 microVM)
      • [3.1 从一次 HTTP 请求追到宿主 TAP](#3.1 从一次 HTTP 请求追到宿主 TAP)
      • [3.2 收包要 Guest 先备好盘子](#3.2 收包要 Guest 先备好盘子)
      • [3.3 网卡、限流、授权是三件事](#3.3 网卡、限流、授权是三件事)
    • [4. virtio-blk:文件操作怎么变成宿主磁盘 I/O](#4. virtio-blk:文件操作怎么变成宿主磁盘 I/O)
      • [4.1 Guest 里的文件怎么存进镜像](#4.1 Guest 里的文件怎么存进镜像)
      • [4.2 一个块请求包含什么](#4.2 一个块请求包含什么)
      • [4.3 Sync、Async、vhost-user 是后端选择题](#4.3 Sync、Async、vhost-user 是后端选择题)
      • [4.4 写成功 ≠ 持久化成功](#4.4 写成功 ≠ 持久化成功)
    • [5. virtio-vsock:Guest 和宿主怎么绕过 IP 通信](#5. virtio-vsock:Guest 和宿主怎么绕过 IP 通信)
      • [5.1 它解决的是本机宿主和 Guest 的通信](#5.1 它解决的是本机宿主和 Guest 的通信)
      • [5.2 两个方向怎么建立连接](#5.2 两个方向怎么建立连接)
      • [5.3 vsock、VirtIO、vhost、vhost-user 别搞混](#5.3 vsock、VirtIO、vhost、vhost-user 别搞混)
      • [5.4 没有 IP 网络,照样有信任边界](#5.4 没有 IP 网络,照样有信任边界)
    • [6. PIT:谁负责在指定时刻触发中断](#6. PIT:谁负责在指定时刻触发中断)
      • [6.1 计时器负责产生事件](#6.1 计时器负责产生事件)
      • [6.2 Firecracker 请求 KVM 创建 PIT](#6.2 Firecracker 请求 KVM 创建 PIT)
      • [6.3 PIT 不是唯一的闹钟](#6.3 PIT 不是唯一的闹钟)
    • [7. KVM 时钟:Guest 怎么知道时间过了多久](#7. KVM 时钟:Guest 怎么知道时间过了多久)
      • [7.1 kvm-clock 解决高效读时间的问题](#7.1 kvm-clock 解决高效读时间的问题)
      • [7.2 kvm-clock、TSC、PIT 怎么区分](#7.2 kvm-clock、TSC、PIT 怎么区分)
      • [7.3 暂停恢复后,时间要单独处理](#7.3 暂停恢复后,时间要单独处理)
    • [8. 串口控制台:没有图形设备怎么观察和操作系统](#8. 串口控制台:没有图形设备怎么观察和操作系统)
      • [8.1 串口把 Guest 的字符输入输出接到宿主](#8.1 串口把 Guest 的字符输入输出接到宿主)
      • [8.2 串口设备、内核控制台、登录服务是三件事](#8.2 串口设备、内核控制台、登录服务是三件事)
      • [8.3 控制台该承担什么角色](#8.3 控制台该承担什么角色)
    • [9. i8042:有限的键盘控制器怎么参与重置与退出](#9. i8042:有限的键盘控制器怎么参与重置与退出)
      • [9.1 为什么精简虚拟机还留键盘控制器](#9.1 为什么精简虚拟机还留键盘控制器)
      • [9.2 Guest 发重置命令后发生什么](#9.2 Guest 发重置命令后发生什么)
      • [9.3 宿主发 Ctrl+Alt+Del 是另一个方向](#9.3 宿主发 Ctrl+Alt+Del 是另一个方向)
    • [10. 设计取舍:这些机制对执行平台意味着什么](#10. 设计取舍:这些机制对执行平台意味着什么)
      • [10.1 Firecracker 和 QEMU + KVM 差在哪](#10.1 Firecracker 和 QEMU + KVM 差在哪)
      • [10.2 从设备能力推导平台责任](#10.2 从设备能力推导平台责任)
      • [10.3 把整条路径连起来](#10.3 把整条路径连起来)

P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看, 传送门https://blog.csdn.net/qq_34419312

前言

microVM 可以理解成一台精简到只剩刚需的虚拟电脑。VM 是虚拟机的缩写,micro 强调它很轻------轻到什么程度呢,配置表里连个正经显卡都懒得给你写。

你在里面跑 Python、下依赖、写文件、等超时、打日志,感觉跟在自己家一样自由。其实住的是别人的房子:房东叫 KVM,中介叫 Firecracker。它背后并没有一整套模拟出来的 PC 硬件,就像外卖厨房------菜是热的,但你找不到一个正经灶台。

Firecracker 负责创建和管理这种虚拟机,KVM 是 Linux 内核提供的底层虚拟化机制。两者配合,让虚拟机里的程序用真实机器的 CPU、内存和存储。搞懂分工,才能回答两个实际问题:应用每次操作经过哪里,这条路径的性能和安全边界在哪。

约定一下称呼:跑 Firecracker 的系统叫 Host(宿主) ,虚拟机里面的系统叫 Guest(客户机)。一台 Linux 服务器里启动了另一套虚拟 Linux,外面那套是 Host,里面那套是 Guest。就这么简单。

本文先建架构,再拆网络、磁盘、虚拟机通信、计时器、时钟、串口和键盘控制器。设备地址和传统计时器部分以 x86_64 + KVM 为主。

1. 整体架构:Firecracker、KVM、microVM 各管什么

1.1 三个家伙不在一个层次

先把 Host 和 Guest 放进一个具体例子:

复制代码
Linux 服务器上的系统:Host / 宿主
└─ Firecracker 创建的 microVM:一台虚拟机
   └─ 虚拟机里的 Linux:Guest / 客户机系统
      └─ Agent、Python、Shell 等应用

**Guest 描述的是"位于虚拟机里面"这个位置,不是一个软件名,也不是登录用户。**Guest 内核就是里面那套 Linux 的内核,Guest 应用就是里面跑的程序。别把它当"请进来的客人",它是"住里面的房客"。

内核是操作系统里管 CPU、内存和设备的核心。Host 和 Guest 各有自己的内核;普通程序跑在用户态 ,内核跑在权限更高的内核态。注意:这两种状态在 Host 和 Guest 里都存在,不能把 Guest 等同于用户态、Host 等同于内核态。就像你不能因为人家住二楼,就说楼下的人全是物业。

对象 所在位置或形态 核心职责
KVM 宿主 Linux 内核中的虚拟化子系统 提供虚拟 CPU、内存映射和中断等机制,配合 CPU 硬件虚拟化运行 Guest
Firecracker 宿主机用户态的 VMM 进程 创建配置 VM、装载 Guest 内核、提供设备模型,协调启动、暂停、状态保存
microVM 创建出来的轻量虚拟机实例 拥有自己的 Guest 内核、vCPU、内存和设备,跑应用

vCPU 就是虚拟 CPU,VMM 是 Virtual Machine Monitor(虚拟机监控器)的缩写,设备模型就是用代码实现的设备行为。要启动一台 1 个 vCPU、512 MiB 内存的 microVM,Firecracker 负责准备内存、内核、设备,再通过 KVM 让虚拟 CPU 跑起来。一句话:Firecracker 负责组织这台机器,KVM 提供底层运行能力,microVM 是最终跑起来的实例。

它们的接口是 /dev/kvm,Firecracker 用 ioctl 向 KVM 喊话:"给我创建 VM""让 vCPU 跑起来"。系统调用是程序向内核请求服务的入口,ioctl 是其中控制设备的一种。

复制代码
microVM:Guest 执行环境
  应用(Agent / Python / Shell)──系统调用── Guest Linux 内核
  Guest 内核 ──虚拟设备请求── Firecracker
Linux 宿主机:
  Firecracker:用户态 VMM ──/dev/kvm + ioctl── KVM
  KVM ──配置与使用── CPU 硬件虚拟化
  Firecracker ──设备后端 I/O── 宿主资源(虚拟网口、磁盘文件、本机通信接口)

Guest 用的还是宿主 CPU 和内存,只是虚拟化让它有了独立执行环境,不用每台 microVM 都配一套物理硬件。

这也解释了运行条件:这条路径依赖 Linux 和可用的 KVM。macOS 原生提供不了这个接口;想在 Linux 虚拟机里再开虚拟机,还得确认平台开放了嵌套虚拟化------相当于在民宿里再开民宿,房东不一定同意。光有一个 Linux 用户空间或容器镜像,不够。

1.2 创建 VM 和运行应用是两条路

启动分四件事:

  1. 创建 VM :打开 /dev/kvm,检查能力,调用 KVM_CREATE_VM。
  2. 准备内存和内核:告诉 KVM 哪些宿主内存当 Guest 内存,再放入 Guest 内核和启动参数。
  3. 准备 CPU 与设备:创建 vCPU,设置启动状态,配置中断、磁盘、网卡。
  4. 开始执行 :vCPU 线程调用 KVM_RUN,让 CPU 进入 Guest 执行。

KVM_CREATE_VM 不会自动带来一套完整 Linux 系统,Guest 内核、内存布局、设备、启动状态都得自己准备。房东给了你钥匙,家具还得自己买。

跑起来之后,用三个动作区分执行路径:

Guest 中的动作 主要由谁处理
程序做加减运算 CPU 在硬件虚拟化机制下执行 Guest 指令
程序调用 write() 先进 Guest 内核;只写缓存的话暂时不碰虚拟磁盘
驱动访问需要宿主处理的设备寄存器 进入虚拟化处理路径,KVM 处理,必要时交给 Firecracker

**VM exit 是 CPU 暂时离开 Guest 去处理虚拟化事件,不是虚拟机退出,更不是关机。**别一看 exit 就以为它跑路了。有些事件 KVM 处理完就能继续,有些要返回 Firecracker。

需要返回 Firecracker 的包括部分 MMIO(通过特定内存地址访问设备寄存器)和 x86 端口 I/O。普通系统调用不等于 VM exit,VM exit 也不等于调用管理 API,三个概念别焊死。

1.3 Firecracker 进程内部也有分工

一个 Firecracker 进程封装一台 microVM,主要三个线程角色:

角色 处理什么 与应用执行的关系
API 线程 接收管理请求 配置和控制 VM,不是普通 I/O 的转发入口
VMM 线程 事件循环、设备事件、控制操作 处理设备请求队列的通知、网络数据到达等
vCPU 线程 每个 vCPU 对应一个线程,进 KVM_RUN 执行 Guest,处理需要返回用户态的退出事件

事件循环说白了就是"等着,有人按门铃再去开门",不是每件设备破事都得惊动它。

1.4 设备清单必须标明实现归属

Guest 能用一项设备功能,不代表它全由 Firecracker 实现。驱动可能在 Guest 内核,设备后端可能在 Firecracker,部分机制由 KVM 提供。

能力 Guest 中的使用者 主要实现位置
virtio-net / virtio-blk / virtio-vsock Guest 内核的对应 VirtIO 驱动 Firecracker 提供设备模型和后端连接
PIT(传统可编程间隔计时器) Guest 计时器相关代码 x86 路径由 Firecracker 请求 KVM 创建内核态 PIT 模型
kvm-clock Guest 中与 KVM 协作读时间的代码 KVM 与 Guest 协作,Firecracker 配置并协调状态保存恢复
串口控制台 Guest 串口驱动、内核日志、终端程序 Firecracker 的串口设备模型连接宿主输入输出
部分 i8042(传统键盘控制器) Guest 重置路径、键盘驱动 Firecracker 实现有限命令与按键注入,用于重置通知

源码里还有熵设备、balloon 设备等。快照是给虚拟机拍照片,恢复是从照片里活回来,但磁盘数据这种"身外之物"得配套另管。

2. VirtIO:驱动、设备后端、共享队列怎么配合

2.1 半虚拟化的核心是签合同

模拟真实网卡很累,要模仿寄存器、模仿各种硬件脾气。VirtIO 换了个活法:Guest 驱动和设备后端直接约定好,用统一格式提交"发这段数据""读这块磁盘"。

"半虚拟化"就是驱动知道自己在用专门为虚拟化设计的接口------明牌,不装。应用完全不用改:Python 照样读文件、发网络请求,底层交互由 Guest 内核里的驱动完成。它甚至不知道自己走了专用通道,跟坐地铁直达和打车绕路,终点一样,它只在乎结果。

准确分工是这样的:

  • Guest 前端驱动:在 Guest Linux 内核里,提交和回收 I/O 请求。
  • 设备模型与后端:主要在 Firecracker 用户态进程,处理队列,访问宿主资源。
  • 传输机制:完成设备发现、配置、队列通知和中断,比如 MMIO 或 PCI。

所以"Firecracker 实现了 virtio-net 驱动"这种说法不准确------Guest 驱动和 Firecracker 的设备端得分开说。

2.2 Virtqueue:一张双方共用的待办清单

**Virtqueue(虚拟队列)**可以理解成双方共用的一份工作清单:Guest 填任务,设备后端处理,再留完成记录。本文实现用的是 split virtqueue,就是把描述符、待办信息、完成信息分开保存:

结构 谁主要写入 内容
Descriptor table 描述符表 Guest 驱动 任务材料在哪:缓冲区地址、长度、读写方向、链中下一项编号
Available ring 可用环 Guest 驱动 哪些任务待处理:准备好的描述符链编号
Used ring 已用环 设备后端 哪些任务已完成:处理完的编号和完成信息

比如 Guest 要写 4 KiB 数据,准备好"请求头 → 数据 → 状态缓冲区"这条描述符链,链头编号是 7。它把 7 放进待处理列表;Firecracker 按编号找到请求和数据,处理完把 7 记入完成列表。Guest 看到完成记录,就能回收缓冲区。

注意:清单传的是编号,数据还在缓冲区里。"环"的意思是列表写到末尾可以绕回开头接着用------像食堂打菜循环播放,永远不用翻页。

复制代码
Guest VirtIO 驱动 → 准备缓冲区与描述符,发布 available 索引
                → 按协商规则发送队列通知
Firecracker 设备后端 → 取出并校验描述符链 → 提交实际 I/O
宿主 I/O 资源 → I/O 完成
Firecracker 设备后端 → 写入结果,更新 used 索引 → 按需通知 Guest
Guest VirtIO 驱动 → 回收完成的缓冲区

数据在缓冲区里,通知只负责告诉对方"该检查队列了"。一次通知可能对应多个请求,能不能省通知、怎么批量处理,要看协商的特性和队列状态。

还有一点:这份清单来自不可信的 Guest,后端必须验货------编号、地址、长度、读写方向,不能照单全收。共享的是约定的 Guest 内存,不是随便一块宿主内存。

发布顺序也很关键:**先填好任务,再宣布"任务准备好了"。**不然后端可能读到半份内容,就像外卖还没出锅你就点了送达。底层的内存屏障就是用来保证这种顺序的。

2.3 VirtIO 省的到底是哪种切换

"VirtIO 减少用户态和内核态切换所以更快"只说对了一部分。虚拟化 I/O 至少涉及三种不同边界:

边界 例子 VirtIO 带来的影响
Guest 用户态 ↔ Guest 内核态 应用调用 write()、send() 普通系统调用还在,VirtIO 不自动消除
Guest 执行 ↔ 宿主虚拟化处理 设备通知或受拦截的寄存器访问 共享队列、批处理、通知抑制可减少部分交互成本
宿主用户态 ↔ 宿主内核态 Firecracker 读写宿主虚拟网口、磁盘文件、本机通信接口 省不省取决于后端、批处理、异步机制

收益来自更简单的设备交互、共享队列、批量处理和通知优化,但它不自动消除系统调用、数据拷贝或所有 VM exit。翻译成人话:减肥药有效,但别指望躺着就瘦。

2.4 ioeventfd、irqfd 与事件循环怎么衔接

Virtqueue 是工作清单,通知机制就是门铃:清单里放任务,门铃只提醒对方来查看。Linux 的 eventfd 就是能被写入和监听的通知计数器(fd 是文件描述符,跟 VirtIO 里描述缓冲区的描述符不是一个概念)。

复制代码
Guest 更新队列并写通知寄存器
  → KVM 的 ioeventfd 机制发出通知
  → Firecracker 事件循环处理对应队列
  → 后端完成请求,更新 used ring
  → 通过 irqfd 等中断机制通知 Guest

ioeventfd 负责"Guest 有请求了",irqfd 把 eventfd 和 Guest 中断关联,负责"干完了,通知 Guest 来检查"。这省掉了一部分"vCPU 专门跑回来,只为转交一个通知"的步骤。但硬件层面的 VM exit 仍可能发生,后端 I/O 也仍要执行。门铃优化的是喊人方式,不是替你干活。

3. virtio-net:网络报文怎么离开 microVM

3.1 从一次 HTTP 请求追到宿主 TAP

Guest 里执行 curl,应用和网络栈先把请求变成报文,virtio-net 负责收发。TX 表示发送,RX 表示接收,TAP 是宿主上的虚拟网络接口。

复制代码
Guest 应用:curl / Agent
  → Guest socket 与 TCP/IP 协议栈
  → Guest virtio-net 驱动
  → TX Virtqueue:待发送报文
  → Firecracker 网络设备后端
  → 宿主 TAP
  → 宿主路由 / 网桥 / 网络策略
  → 目标服务

TAP 就是接进宿主网络的一个虚拟网口,交换的是以太网帧。Firecracker 只负责把报文交到 TAP,之后怎么路由、要不要 NAT、允许访问哪里,全是宿主网络的事。它就是个快递小哥,只负责送到楼下,不负责你楼上住的是谁。

Firecracker 还能处理特定的 MMDS 元数据请求(给 microVM 提供配置等元数据),但这条特殊路径不代表它会代理所有 HTTP 请求。

3.2 收包要 Guest 先备好盘子

接收时,Guest 驱动先预留内存,通过 RX 队列告诉后端"收到的报文放这里"。TAP 有数据可读后,Firecracker 填进这些缓冲区,记录完成结果,再通知 Guest。

没有可用接收缓冲区,后端不能把报文凭空塞进应用内存------先备好盘子再上菜,没盘子不能直接倒你兜里。队列容量、缓冲区供给、事件处理速度、宿主网络和 CPU 调度都会影响收发延迟。所以发现网络慢,只测物理网卡带宽根本定位不了问题。

3.3 网卡、限流、授权是三件事

virtio-net 解决"能不能收发报文";设备限流约束带宽和操作速率;但判断"这次请求有没有业务权限",限流不负责,DNS、网关、出站策略也得平台自己配。

举个例子:允许一个 Agent 下载依赖,要网络连通;禁止它访问宿主管理接口,要宿主防火墙或代理策略。Firecracker 官方把 Guest 出站流量视为不可信,过滤交给宿主层。

**给 Guest 一块网卡,只建立了通信能力;允许这块网卡连哪里,仍是平台的决定。**就像给你办了门禁卡,不等于所有房间你都能进。

4. virtio-blk:文件操作怎么变成宿主磁盘 I/O

4.1 Guest 里的文件怎么存进镜像

Guest 可以把 /dev/vda 当一块磁盘,在上面用 ext4 等文件系统。/dev/vda 是 Guest 里的磁盘设备名,不是宿主磁盘路径。

宿主侧,这块虚拟磁盘通常由一个镜像文件承载,比如 rootfs.ext4。磁盘镜像就是"用一个文件装下一块虚拟磁盘",这个承载文件也叫 backing file。rootfs 是根文件系统的简称,不等于宿主根目录。

复制代码
Guest 应用:读取 /workspace/main.py
  → Guest 文件系统与内存缓存
  → 需要块设备 I/O 时,Guest virtio-blk 驱动
  → Virtqueue:操作类型、扇区、缓冲区
  → Firecracker 块设备后端
  → 宿主磁盘镜像文件 rootfs.ext4
  → 宿主文件系统、缓存与存储设备

应用要读 main.py,Guest 文件系统先查出数据在虚拟磁盘哪些位置,再发块读取请求。Firecracker 按位置读 rootfs.ext4,完全不用理解 main.py 的文件名和目录结构------它不认人名,只认门牌号。

内存缓存也会影响路径:Guest 内存里已有内容,读取可以完全不碰虚拟磁盘;写入可能先进缓存,稍后再向下提交。

4.2 一个块请求包含什么

块设备提供的是按位置读写的接口,不直接认识文件名。一个块请求说三件事:**读还是写、访问磁盘哪里、数据放在哪里。**Guest virtio-blk 驱动组织成请求,Firecracker 校验后访问 backing file,再把完成状态写回。

读请求、写请求、状态区域的访问方向不同。设备不能因为地址落在 Guest 内存里,就忽略描述符的读写约定------写请求不能往只读缓冲区里灌水。

这条路径提供的是块存储,不是把任意宿主目录直接挂进 Guest。镜像从哪来、哪些磁盘只读、每个任务的写入存哪、多个实例会不会错误共用同一份可写镜像,都是平台要拍板的事。

4.3 Sync、Async、vhost-user 是后端选择题

Sync 是同步,Async 是异步。区别一句话:**发起读取后是否在原处等着。**同步 I/O 等结果返回;异步先提交请求,完成后再取结果,期间干别的。异步只是给你更多安排并发的空间,不是每次物理读写都自动变快。

Guest 用同一种 virtio-blk 接口,宿主可以选不同后端:

后端路径 请求由谁执行 主要取舍
内置 Sync Firecracker 用同步文件 I/O 路径直接,但阻塞 I/O 可能影响处理延迟
内置 Async Firecracker 用 io_uring 提交和回收 I/O 多个请求可同时等待完成,收益取决于负载、存储与 CPU
vhost-user block 外部用户态后端进程处理队列 可接不同存储实现,但要承担进程管理、共享内存、故障协调责任

在本文分析的源码版本里,Async 和 vhost-user block 文档还标着 Developer Preview(开发者预览)。它们也不是"开了就一定更快"的开关------初始化成本、小请求延迟、并发深度、实例密度都会影响结果。

Unix socket 用于同一操作系统内进程通信,不依赖 IP 路由。vhost-user block 用它交换配置、内存区域和通知 fd 等控制信息,块数据走共享内存里的队列和缓冲区。别把它跟后面 vsock 到 Unix socket 的桥接混为一谈------一个传的是块数据,一个传的是应用字节流。

4.4 写成功 ≠ 持久化成功

假设程序保存了 result.json,write() 也返回成功,然后宿主突然断电------文件可能还是丢了,因为数据可能只躺在某一层内存缓存里。

缓存像临时存放区,持久存储才负责断电后保留数据。要保证持久化的写入,得让数据经过 Guest 缓存、虚拟磁盘、宿主缓存,最终到达持久存储。fsync 是应用请求把文件修改同步到存储的系统调用;块设备 flush 是设备层的缓存刷新请求。两个层次不同,都在推动这条链路。

本文的块设备缓存策略有两种:

  • Unsafe(默认):不向 Guest 宣告 VirtIO flush 功能,不能依赖这条刷新链获得持久化保证。
  • Writeback :宣告 flush 功能;协商成功后,Guest 的刷新请求会由内置后端转成 backing file 的 fsync。

名字里的 Writeback 不意味着每次写都同步落盘,它只是给了你一条正确的刷新路径------就像给你一条安全通道,但你没走就没用。

5. virtio-vsock:Guest 和宿主怎么绕过 IP 通信

5.1 它解决的是本机宿主和 Guest 的通信

平台经常要给 Guest 里的执行服务发任务、收日志、拿退出码和健康状态。用 TCP 能实现,但要管 IP 地址、路由、端口,烦。

**vsock 是为虚拟机与宿主这类端点通信设计的 socket 机制。**它用 CID 和端口定位:CID 是通信端点的编号,端口标识端点上的服务------类似"找哪台机器,再找其中哪个服务"。注意,这是 vsock 自己的编号和端口,不是 IP 和 TCP 端口。

Guest 程序用 AF_VSOCK,Firecracker 在宿主侧连 AF_UNIX(同一宿主机内的 Unix socket)。这条路不需要给 Guest 配 IP 地址和路由------像公司内部对讲机,不用拨区号。

复制代码
Guest 应用(AF_VSOCK)
  → Guest vsock 协议栈 / virtio-vsock 驱动
  → Virtqueue
  → Firecracker vsock 设备与连接管理
  → 宿主 Unix socket(AF_UNIX)
  → 宿主服务

5.2 两个方向怎么建立连接

假设设备配置里 uds_path 是 /run/task/vsock.sock,应用端口用 8000。UDS 是 Unix Domain Socket 的缩写。

连接发起方 建立过程
宿主 → Guest Guest 服务监听 vsock 端口 8000;宿主连 /run/task/vsock.sock,发文本握手 CONNECT 8000\n,成功后收 OK <宿主侧端口>\n,再交换应用数据
Guest → 宿主 宿主服务监听 /run/task/vsock.sock_8000;Guest 连 CID=2、端口 8000;Firecracker 转接到对应 Unix socket

\n 是换行字节,CID=2 表示宿主端。实际路径要结合 jailer 和实例目录规划,别让不同实例抢同一个 socket 名。jailer 是给 Firecracker 设隔离环境、降运行权限的启动程序------相当于进小区前先搜身、换工牌。

比如宿主发 {"cmd":"pytest"},Guest 里的执行服务读懂了就跑测试,再返回结果。**vsock 负责送达消息,执行服务负责理解和执行消息。**就像外卖骑手只负责送到,怎么吃是你的事。Firecracker 管理 API 的 socket 是配置 VM 用的,跟这条应用通道分开。

5.3 vsock、VirtIO、vhost、vhost-user 别搞混

vsock 通道传递的是命令文本、日志这类应用数据。Firecracker 在用户态桥接两端 socket,这条实现路径绕过宿主 vhost 内核代码,也不向宿主传送或安装内核代码。

名称 表达的是什么
VirtIO 驱动与设备之间的标准接口和队列机制
virtio-vsock 基于 VirtIO 的一种设备类型,为 vsock 通信提供传输
内核 vhost(vhost-net、vhost-vsock) 把 VirtIO 后端处理放进宿主内核的实现方式
vhost-user 通过协议把后端队列处理交给另一个用户态进程

用 VirtIO 不等于必须用内核 vhost。Firecracker 内置 vsock 后端 + 可选 vhost-user block,正好说明"设备接口"和"后端放哪"是两个独立决定------点菜和在哪吃是两件事。

5.4 没有 IP 网络,照样有信任边界

vsock 让通信不依赖 Guest 的 IP 配置,但不自动完成业务授权。宿主服务要根据可信的实例映射和任务身份决定权限,校验消息、参数、资源范围。不能因为消息来自 vsock 通道,就允许它执行任意宿主命令------通道干净不等于人可靠。

连接生命周期也要管:快照恢复不能保证旧 vsock 连接还在。Guest 已有监听 socket 能保留并接受新连接,但已建立的连接要重连。任务协议得能处理重连、重复请求和结果确认------电话挂了再打,你不能指望对方记得上一通说到哪。

6. PIT:谁负责在指定时刻触发中断

6.1 计时器负责产生事件

先用手表和闹钟区分:**时钟告诉你现在几点了,计时器负责到点吵你。**PIT 是 Programmable Interval Timer(可编程间隔计时器)的缩写,干的是后者:Guest 设置计数值和模式,到达条件后通过中断提醒 Guest 内核。

比如要每隔约 10 毫秒触发一次事件,就选周期模式、设计数值。"可编程"指参数可以调,不是让你把一段程序塞给它执行。

底层按时钟脉冲计数:传统 i8254 PIT 基准频率约 1.193182 MHz,每秒约 119 万次脉冲;周期计数值取约 11932,对应约 10 毫秒。它有三个 16 位计数通道,通道 0 连 IRQ 0。理解日常路径,抓住"设置计数,到点触发事件"就够了。

6.2 Firecracker 请求 KVM 创建 PIT

x86 源码里,KvmVm::setup_irqchip() 先创建内核中断控制器,再调 create_pit2()。所以 PIT 的设备模型主要由 KVM 在宿主内核提供,Firecracker 负责初始化和状态协调。

复制代码
Firecracker 初始化 VM
  → 请求 KVM 创建中断控制器与 PIT
Guest 编程 PIT 的计数值与模式
  → KVM 维护虚拟 PIT 状态
  → 到期后通过虚拟中断机制通知 Guest
  → Guest 内核处理定时事件

源码里的 KVM_PIT_SPEAKER_DUMMY 让 KVM 对旧式扬声器端口做最低限度响应------就是"嗯嗯,知道了",没有真喇叭,也不提供完整音频功能。

6.3 PIT 不是唯一的闹钟

现代 x86 Guest 还可能有本地 APIC 定时器、TSC deadline 等机制。一次 sleep() 最后被哪个时钟事件设备叫醒,要看 Guest 内核、CPU 暴露能力和配置,不能直接说"Firecracker 的 PIT 每次唤醒应用"。ARM64 也有自己的体系结构定时器路径,套不了这套传统 PC 设备图。

还有,"到点提醒"不等于"应用立刻执行"。宿主 CPU 忙,vCPU 也得排队;Guest 内核收到提醒,应用还可能继续排队。定时 10 毫秒不保证应用恰好在第 10 毫秒继续跑------闹钟响了不代表你马上起床。

7. KVM 时钟:Guest 怎么知道时间过了多久

7.1 kvm-clock 解决高效读时间的问题

kvm-clock 是 x86 KVM 提供的半虚拟化时钟。思路是:KVM 给 Guest 一个时间基准和换算方法,Guest 自己根据 CPU 计数器的增量推算时间,不必每次都问设备"现在几点了"。

假设基准时刻是 10 秒,之后计数器增量换算成 2 毫秒,当前时间就约为 10.002 秒。换算关系简写:

复制代码
估算时间 ≈ 基准时间 + 换算(当前 TSC − 基准 TSC)

TSC 是 CPU 时间戳计数器,换算要用 KVM 提供的乘数和移位参数。底层过程:

  1. Guest 通过 KVM 定义的 MSR 寄存器接口,登记存放时间信息的内存地址。
  2. KVM 写入时间基准、TSC 基准和换算参数。
  3. Guest 读当前计数,完成换算,检查版本字段。

查版本是为了防止混读新旧数据:KVM 正在更新基准时,你不能拿旧基准配新换算参数------就像记账记到一半你抄走了,数字是拧巴的。

7.2 kvm-clock、TSC、PIT 怎么区分

机制 主要作用 归属
kvm-clock 以半虚拟化方式提供时间信息 KVM 与 Guest 时钟代码协作
TSC x86 CPU 时间戳计数器,可作时钟源基础 硬件与虚拟化机制支持,是否适用由 Guest 判断
PIT / 其他时钟事件设备 在设定条件下产生定时事件 由相应硬件虚拟化或设备模型提供

Firecracker 在 x86_64 上暴露 kvm-clock 和 tsc。但"支持 kvm-clock"不等于"始终用 kvm-clock",用哪个时钟源看内核判断。ARM64 走 arch_sys_counter。

应用还会区分"现在是几点"和"这活干了多久":墙上时间 表示日期和时刻,可以因校时而调整;单调时间 按自己的基准向前推进,不会因为改日期而倒退,适合算耗时。Guest 内核在底层计数器之上维护这些时间,date 命令输出的不是原始计数值。

7.3 暂停恢复后,时间要单独处理

10:00 创建快照时,某个令牌还有 5 分钟有效期。10:10 恢复快照,内存里令牌还在,但外部服务已经认为它过期。恢复 VM 状态不会延长外部授权,快照不是时光机。

符合条件的 x86 kvm-clock 路径上,可以通过 clock_realtime 选项推进恢复后的时钟,但可能带来 Guest 观察到的时间跳变。实际使用要核对时钟源、宿主支持和恢复参数。

恢复任务时,要重新检查令牌、任务取消状态和租约(一段时间内有效的资源使用权),不能只看快照里的旧状态。

8. 串口控制台:没有图形设备怎么观察和操作系统

8.1 串口把 Guest 的字符输入输出接到宿主

串口是一条不依赖 Guest 网络的文字通道。Firecracker 提供 UART(通用异步收发器)设备模型。Guest 用串口驱动,典型 x86 配置把 ttyS0 当控制台,输出启动日志和错误信息。

复制代码
Guest 内核日志 / 连接到串口的终端程序
  → Guest 串口驱动
  → 虚拟 UART 寄存器
  → Firecracker 串口模型
  → 宿主标准输出或配置的输出目标

x86 的 COM1(传统 PC 第一个串口)注册在 I/O 端口 0x3f8,串口中断用 IRQ 4。这些地址不能直接套 ARM64。串口像应急对讲机:网络还没起、登录服务还没起,它先能说话。

8.2 串口设备、内核控制台、登录服务是三件事

内核启动参数 console=ttyS0 选择串口控制台。但想要看到交互式登录提示,还得 Guest 用户空间启动相应的 getty 或其他终端服务。getty 是负责准备终端、显示登录提示、接入登录流程的程序。

所以,控制台能显示启动日志却没有 login: 提示,多半是 Guest 没启动串口登录服务,不是串口坏了。应用日志显不显示在这里,取决于标准输出和日志系统的配置。

宿主侧的输入也可以进入虚拟 UART,再由 Guest 读取。怎么接入日志收集,要结合进程启动方式和输出配置来确定。

8.3 控制台该承担什么角色

启动失败时,网络服务和 Guest 执行服务可能都不可用,串口却能提供更早的线索------这是精简执行环境还保留它的重要理由。

但长期的结构化任务通信,更适合走 vsock 或网络上的应用协议。串口输出是字符流,内核日志和命令输出混在一起,不好可靠判断请求归属和完成状态;高频串口输出还会消耗设备处理和日志资源。

串口负责让系统在早期和故障时仍可观察;任务协议负责可靠表达一次操作的输入、输出和结果。

9. i8042:有限的键盘控制器怎么参与重置与退出

9.1 为什么精简虚拟机还留键盘控制器

传统 x86 的 i8042 控制器除了键盘相关功能,还提供可触发 CPU 重置的接口。Guest 内核可以用这条传统路径表达"我要重启"。

Firecracker 保留了必要的控制寄存器、缓冲区、重置命令和有限按键注入能力。它不是面向桌面交互的完整键盘,"仅包含关机键的键盘"这种说法容易掩盖它的真正用途------它主业是重置,键盘只是兼职。

9.2 Guest 发重置命令后发生什么

这条路径里,Guest 表达"我要重启",Firecracker 的处理结果是结束当前 microVM。具体信号:Guest 向 i8042 命令端口 0x64 写重置命令 0xFE,设备模型触发退出事件。

复制代码
Guest 内核 → 向命令端口写入重置命令
KVM → 将需要用户态处理的端口 I/O 返回 VMM
Firecracker i8042 模型 → 触发退出 eventfd
VMM 事件处理 → 结束当前 microVM 的运行

这也是官方入门示例里,配置了相应重启路径后,Guest 内的 reboot 能结束 Firecracker 的原因。"重启"得到的是"结束",不是"自动再来一台新的"。平台想要重启后获得新实例,得自己安排再次启动------Guest 的重置命令不等于 VMM 自动重建整套流程。

9.3 宿主发 Ctrl+Alt+Del 是另一个方向

x86 上的 SendCtrlAltDel 管理操作,通过 i8042 注入 Ctrl、Alt、Delete 的扫描码(表示按键按下或释放的编码),触发相应键盘中断,让 Guest 的驱动和用户空间处理。

复制代码
宿主管理请求 SendCtrlAltDel
  → Firecracker 注入扫描码
  → Guest 键盘驱动接收
  → Guest 根据自身配置处理 Ctrl+Alt+Del
  → 如进入重启流程,再通过相应机制通知 VMM

这相当于给 Guest 递了个纸条:"请处理这组按键"。接下来做什么由 Guest 决定。如果 Guest 已经卡死、处理不了键盘事件,纸条也没用。

所以,正常退出、Guest 重置、强制终止宿主 VMM 进程要分别建模。正常退出给应用清理和刷盘的机会;强制终止就得接受未完成 I/O 和没打扫干净的现场。平台根据任务状态和超时策略决定走哪条路。

10. 设计取舍:这些机制对执行平台意味着什么

10.1 Firecracker 和 QEMU + KVM 差在哪

QEMU 是支持多种机器类型和设备的虚拟机软件,可以和 KVM 配合执行 Guest。它和 Firecracker 都能用 KVM 和 VirtIO,所以"用了 KVM"或"用了 VirtIO"本身解释不了 Firecracker 的价值。差异在于提供多大的设备与机器模型范围,以及围绕什么负载组织实现。

比较维度 Firecracker QEMU + KVM
目标 面向轻量隔离执行环境的 microVM 覆盖更广泛的虚拟机与硬件兼容需求
设备与机器模型 聚焦有限的设备集合,控制功能范围 提供丰富的机器类型、设备模型与后端配置
I/O 基础 可采用 VirtIO 与 KVM 通知机制 同样支持 VirtIO,有多种后端与加速配置
使用前要确认 目标 Guest、设备能力、生命周期接口能否满足负载 所选机器类型、设备与配置是否符合负载和运维需求

精简设备模型减少需要实现、维护、暴露给 Guest 的接口,降低部分资源和安全审计负担。但实际性能仍受 Guest 启动服务、镜像、网络、存储、调度和配置影响。"功能更少"推不出"所有场景更快"------轻装不等于跑赢所有比赛。

10.2 从设备能力推导平台责任

回到那个要下载依赖、修改项目、跑测试的 Agent:

应用需要做的事 底层主要机制 平台还需要完成的部分
执行代码 Guest 内核、vCPU、KVM 与硬件虚拟化 镜像准备、资源分配、身份与生命周期管理
访问外部服务 virtio-net 与 TAP 路由、DNS、出站策略与业务授权
保存项目文件 Guest 文件系统、virtio-blk 与宿主存储 写入隔离、刷新策略、持久化与磁盘一致性
发送任务、返回结果 virtio-vsock 与 Unix socket Guest 执行服务、消息协议、鉴权、重连与去重
处理超时 Guest 时间与定时事件机制 宿主侧截止时间、取消策略与恢复后状态核验
获取启动和故障线索 串口控制台 日志收集、限流、归属与访问控制
停止或重建环境 Guest 退出路径、i8042 等通知与 VMM 生命周期 优雅退出期限、强制回收、存储清理与实例重建

这种分工也决定安全设计:Firecracker 的设备后端会读 Guest 提供的队列和数据,所以后端仍处在处理不可信输入的边界上。设备校验、宿主进程降权、seccomp 和 jailer 要一起工作。seccomp 是 Linux 用来限制进程能用哪些系统调用、怎么用的机制,和 jailer 设的隔离环境配合,约束 Firecracker 进程能碰哪些宿主能力。减少设备数量只是其中一项措施。

10.3 把整条路径连起来

理解 Firecracker 与 KVM,记住四组关系:

  1. 运行关系:KVM 配合硬件提供虚拟化机制,Firecracker 组织 VM 与设备,microVM 承载 Guest 内核和应用。
  2. I/O 关系:Guest 驱动提交请求,VirtIO 队列描述缓冲区,设备后端完成宿主 I/O,通知机制让双方继续推进。
  3. 时间与控制关系:时钟提供时间信息,定时器产生事件,串口提供字符通道,i8042 保留有限的重置和按键通知功能。
  4. 平台关系:设备提供执行与通信能力,平台负责权限、持久化、恢复和任务语义。

沿着"应用接口 → Guest 内核 → 虚拟设备 → Firecracker / KVM → 宿主资源"追一次操作,就能判断问题发生在哪一层,也能说清某项优化到底改了哪段路径、又保留了哪些边界。下次虚拟机卡了、网络慢了、时间飘了,你至少知道该去敲谁的房门。

P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/qq_34419312

相关推荐
袖清暮雨1 小时前
机器学习之逻辑回归
人工智能·机器学习·ai
秦先生在广东1 小时前
解构 gstack:如何以 23 个 AI 角色重塑全栈开发工作流
人工智能
释厄6231 小时前
学术引用本体论——WorkBuddy 学术科学文化三违反
网络·人工智能·算法
一隅论数智1 小时前
RDF(Resource Description Framework)介绍和使用举例(二)
大数据·人工智能·经验分享·笔记·学习·架构·政务
ccstuck1 小时前
AI安全系列:开源RAG系统测试
人工智能·安全·开源·ai安全
视+老张1 小时前
AI导游怎样把一次问答转成可执行路线:地点实体与任务状态设计
人工智能·ar·增强现实
IT_陈寒1 小时前
SpringBoot自动配置的坑:你以为的捷径可能是弯路
前端·人工智能·后端
xsd202411181 小时前
Open-Magiviz:解决AI长视频角色不一致与画面跳跃
人工智能
XMAIPC_Robot1 小时前
RK3576/RK3588+FPGA+AI数据采集系统:实时性与边缘AI算力兼顾
人工智能·fpga开发·机器人·数据采集·rk3588+fpga·rk3576+fpga