这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

优化

优化:启动加速和高密部署

1 - 概述

优化内容概述

1.1 - 启动加速

cloud hypervisor 启动加速

1.2 - 高密部署

cloud hypervisor 高密部署

尝试问了一下 gemini:

https://gemini.google.com/app/f36b99b24d1c84e0

问 copilot , gpt-5:

https://copilot.microsoft.com/chats/gcwFG4V9oasJEtHXg9nFE

2 - 虚拟化

cloud hypervisor 虚拟化优化

2.1 - LibOS

LibOS

2.1.1 - 概述

LibOS概述

介绍1

如果说 MicroVM 是对传统虚拟机的“瘦身”,那么 LibOS (Library OS) 或 Unikernel 则是对操作系统架构的“重构”。

结论是:LibOS 绝对能带来质的飞跃。 在追求极致高密部署(High Density)的道路上,LibOS 是目前理论和实践上的终极方案,它能将密度在 MicroVM 的基础上再提升一个数量级。

以下是 LibOS 为什么能提升密度,以及它如何适配您的 Cloud Hypervisor 方案的详细分析:


1. 核心原理:为什么 LibOS 密度更高?

在您目前的方案中(Host -> Cloud Hypervisor -> Linux MicroVM -> App),虽然 Linux Kernel 已经裁剪得很小,但它仍然是一个通用操作系统。它依然保留了多用户支持、进程调度、复杂的网络栈、文件系统权限管理等应用可能根本不需要的功能。

LibOS 的核心逻辑是:“应用即操作系统”。

  • 单一地址空间: LibOS 将“应用程序代码”与“必要的内核功能(如 TCP/IP 栈、内存分配)”编译成一个单一的二进制镜像。
  • 消除上下文切换: 在 LibOS 内部,没有 User Space 和 Kernel Space 的区分。系统调用(Syscall)变成了简单的函数调用(Function Call),开销极低。
  • 按需链接: 如果你的应用不需要读取磁盘,LibOS 就不包含文件系统驱动;不需要 USB,就不包含 USB 协议栈。

资源消耗对比

指标 传统 VM MicroVM (裁剪 Linux) LibOS / Unikernel
启动时间 秒级 100ms - 500ms 5ms - 50ms
内存底噪 几百 MB 20MB - 50MB 几百 KB - 5MB
镜像大小 GB 级 几十 MB 几 MB
单机并发数 几十 几百 - 上千 数千 - 上万

2. 技术结合:Cloud Hypervisor + LibOS

好消息是,您不需要更换 Cloud Hypervisor。

Cloud Hypervisor 是基于 KVM 的 VMM,而现代的 LibOS(如 Unikraft, OSv, RustyHermit)通常都支持直接运行在 KVM 上(通常通过 PVH 引导协议)。

您的架构演进路线图可以是:

  • 当前阶段: Cloud Hypervisor + Minimal Linux Kernel (MicroVM)
  • 下一阶段: Cloud Hypervisor + LibOS Image

在这种模式下,Cloud Hypervisor 依然负责虚拟化硬件接口(Virtio),但 Guest 内部不再运行笨重的 Linux,而是运行一个极简的 LibOS 二进制。

3. LibOS 方案的优缺点权衡

引入 LibOS 虽然能极大提升密度,但也带来了显著的工程挑战。

✅ 优势(Pros)

  1. 极致的内存密度: 很多无状态微服务的 LibOS 镜像运行起来只需 <5MB 内存。配合 KSM(内存合并),在 BMS 上部署数千个实例轻而易举。
  2. 安全性增强: 攻击面极小。LibOS 没有 Shell,没有 execve,甚至没有多进程概念,攻击者即使利用漏洞进入,也几乎无法进行横向移动或植入后门。
  3. 冷启动速度: 毫秒级启动,非常适合 Serverless / FaaS 场景。

❌ 挑战(Cons)

  1. 兼容性(最大的痛点):
    • LibOS 通常不支持 fork() 或 exec()。如果您的应用依赖多进程模型(如 Nginx 多进程模式),则无法直接运行。
    • 对解释型语言(Python, Java, Node.js)的支持需要特定的运行时适配,不如编译型语言(Go, Rust, C++)方便。
  2. 调试困难: 没有 Shell,无法 SSH 进去 top 或 ls。调试往往需要依赖日志输出或远程调试接口。
  3. 构建链改变: 需要将应用和 LibOS 内核源码一起编译(或者链接),这改变了标准的 CI/CD 流程(不再是 docker build 那么简单)。

4. 推荐关注的 LibOS 项目

如果您想在 Cloud Hypervisor 上尝试 LibOS,建议关注以下几个成熟度较高的项目:

  1. Unikraft (强力推荐):
    • 特点: 欧洲系统顶会(EuroSys)孵化的项目,致力于解决 Unikernel 的兼容性问题。它提供了 POSIX 兼容层,很多标准 Linux 应用(如 Nginx, Redis, SQLite)无需修改代码即可编译运行。
    • 适用性: 性能极高,社区活跃,非常适合高密场景。
  2. Gramine (原 Graphene):
    • 特点: 这是一个 Library OS,但它的思路是让未修改的 Linux 二进制文件直接运行。它更多是为了 SGX (机密计算) 设计的,但也具备 LibOS 的轻量特性。
    • 适用性: 如果不想重新编译应用源码,这是一个折中方案。
  3. Nanos (NanoVMs):
    • 特点: 商业化做得较好,工具链(OPS)非常完善,甚至能直接把现有的 ELF 二进制打包成 Unikernel 镜像。
    • 适用性: 易用性最好,适合快速验证。

总结建议

对于您的“高密部署”目标:

  1. 如果是通用业务(客户跑任意代码): 继续优化 MicroVM (Linux) 是最稳妥的。因为 LibOS 的兼容性还不足以支撑任意业务。
  2. 如果是特定业务(如网关、FaaS、特定微服务): 强烈建议引入 LibOS (特别是 Unikraft)。这将是您在单机 BMS 上突破性能瓶颈、实现成本碾压对手的关键技术。

介绍2

这是一个非常前沿且深入的技术话题。在 BMS (Bare Metal Server) + MicroVM (如 Firecracker, Cloud Hypervisor) 的架构中,引入 LibOS (Library OS,通常以 Unikernel 的形式落地) 技术,是将云计算的“密度”和“速度”推向极致的关键手段。

通常,MicroVM 已经通过精简设备模型和裁剪 Linux Kernel 实现了毫秒级启动和低内存占用,但在 Guest OS 层面,标准的 Linux(即便是裁剪过的 Alpine 或 Clear Linux)仍然存在由于通用操作系统设计带来的冗余。

LibOS 通过彻底改变 Guest OS 的架构,在 MicroVM 内部实现了更深层次的优化。以下是具体的优化逻辑和技术细节:


1. 核心理念差异:通用 OS vs. LibOS

要理解优化机制,首先需要对比两者的运行模式:

  • 通用 Linux Guest:
    • 多用户/多进程: 设计上支持多任务,即使只跑一个应用,也要启动 init 进程、systemd、日志守护进程等。
    • 隔离机制: 内核空间与用户空间隔离(Ring 0 vs Ring 3),需要昂贵的上下文切换(Context Switch)。
    • 通用性: 包含大量并不需要的驱动和子系统。
  • LibOS (Unikernel):
    • 单应用/单进程: 操作系统作为一个库(Library)与应用程序编译链接在一起,形成一个单一的二进制镜像。
    • 单一地址空间: 不区分内核态和用户态,所有代码运行在同一特权级(通常是 Ring 0)。
    • 定制化: 仅包含应用所需的 OS 功能(如只需 TCP 栈,就不编译文件系统模块)。

2. 优化 MicroVM 启动速度

在 BMS + MicroVM 架构中,LibOS 可以将启动时间从“几百毫秒”压缩到“十几毫秒”甚至更低,主要得益于以下几点:

A. 极小的镜像体积 (Image Size)

  • 原理: LibOS 利用静态链接和**死代码消除(Dead Code Elimination)**技术。构建时,编译器会自动剔除应用程序未调用的内核代码。
  • 效果: 一个 HTTP Server 的 LibOS 镜像可能只有 几百 KB 到 几 MB。
  • 启动优化: 在 MicroVM 启动时,VMM(如 Firecracker)需要将 Guest Kernel 加载到内存。加载 1MB 的 LibOS 远快于加载 20MB+ 的裁剪版 Linux Kernel。这减少了 I/O 开销和内存拷贝时间。

B. 消除初始化冗余 (No Init Process)

  • 原理: 传统 Linux 启动需要经过 BIOS/UEFI -> Bootloader -> Kernel Init -> User Space Init (systemd) -> App 的漫长链路。
  • LibOS 路径: VMM Entry -> Minimal Hardware Setup -> App Main Function。
  • 效果: 跳过了复杂的硬件探测(因为 MicroVM 提供的设备极少)、跳过了文件系统挂载(如果是内存文件系统)、跳过了多用户环境的初始化。应用的主函数几乎是上电即运行。

C. 就地执行 (Execute in Place, XIP) 的潜力

  • 由于镜像极小,LibOS 更容易利用 XIP 技术,或者通过 DAX (Direct Access) 直接映射宿主机内存,避免了传统 OS 启动时的解压和复杂的内存布局初始化过程。

3. 优化资源占用 (内存与 CPU)

在 BMS 上,资源密度直接决定了成本。LibOS 在 MicroVM 中的资源效率极高:

A. 内存占用 (Memory Footprint)

  • 单一地址空间 (Single Address Space):
    • 传统 OS: 每个进程都有页表,内核与用户态隔离需要重复映射,存在大量内存碎片和页表开销。
    • LibOS: 因为没有多进程,不需要维护复杂的页表结构,没有用户态/内核态的内存隔离墙。整个虚拟机可能只需要分配刚好够应用运行的物理内存(例如 10MB)。
  • 去除非必要组件: 没有 sshd,nfs,udev 等后台进程占用内存。
  • 结果: 在一台 128GB 内存的 BMS 上,如果用传统 VM 可能只能跑 1000 个实例,用 LibOS 可能可以跑 10,000 个实例。

B. CPU 效率与上下文切换

  • 系统调用消除 (Syscall Elimination):
    • 传统 OS: 应用程序读写网络需要通过 Syscall 陷入内核(Context Switch),伴随着 CPU 缓存(L1/L2 Cache)和 TLB 的刷新,开销巨大。
    • LibOS: read() 或 write() 只是普通的函数调用。应用直接操作网络栈结构,零开销。
  • 零拷贝 (Zero Copy): 由于应用和内核共享同一内存空间,网络数据包从网卡驱动上来后,不需要在内核缓冲区和用户缓冲区之间拷贝数据,直接处理。

C. 调度器优化

  • 在 MicroVM 场景下,BMS 宿主机的 CPU 调度由 KVM/Host OS 负责。
  • 如果在 Guest 内部再运行一个复杂的 Linux CFS 调度器,会出现双重调度 (Double Scheduling) 问题,浪费 CPU 周期。
  • LibOS 通常采用极简的协作式调度或单线程模型,将调度权完全交给宿主机(VMM),消除了 Guest 内部的调度开销。

4. 架构图解:BMS + MicroVM + LibOS

+---------------------------------------------------------------+
|                      Application Logic                        |
|   (Directly calls network stack, memory allocator, etc.)      |
+---------------------------------------------------------------+
|        LibOS Layer (Unikraft, RustyHermit, OSv, etc.)         |
|  [Net Stack] [FS Drivers] [Memory Mgmt] (Compiled as Lib)     |
+---------------------------------------------------------------+
|                 Single Binary (Unikernel)                     |
+===============================================================+
|            MicroVM Boundary (virtio-net, virtio-blk)          |
+---------------------------------------------------------------+
|         VMM (Firecracker / Cloud Hypervisor / QEMU)           |
+---------------------------------------------------------------+
|              Host Kernel (KVM) / Hypervisor                   |
+---------------------------------------------------------------+
|                   Bare Metal Server (BMS)                     |
+---------------------------------------------------------------+

5. 挑战与权衡

虽然优化效果显著,但在生产环境落地 LibOS 存在显著门槛:

  1. 调试困难: 没有 Shell,没有 SSH,无法登录进去 top 或 ls。调试依赖于外部观测(VMM 日志)或专门的调试构建。
  2. 兼容性(POSIX 支持): 许多 LibOS 对 POSIX 标准支持不完整(尽管 Unikraft 等项目正在努力解决)。这意味着现有的应用(如 Nginx, Redis)可能需要重新编译甚至修改代码才能运行。
  3. 安全性(攻击面): 虽然 MicroVM 提供了强隔离,但 LibOS 内部没有 Ring 3 保护。如果应用有缓冲区溢出漏洞,攻击者直接获得该 MicroVM 的最高权限(虽然仍被困在 MicroVM 内,但破坏性比普通 App 大)。

6. 总结:如何落地?

在 BMS + MicroVM 方案中,LibOS 是为了特定高并发、低延迟、高密度场景(如 Serverless 函数计算、NFV 网元)而生的终极优化。

  • 优化启动: 靠的是“减法”——减去内核初始化、减去用户态环境、减去动态链接。
  • 优化资源: 靠的是“融合”——融合内核与应用、融合地址空间、消除边界开销。

推荐关注的项目:

  • Unikraft: 目标是高性能且兼容 POSIX,目前最活跃的 LibOS 项目之一,非常适合配合 Firecracker。
  • RustyHermit: 基于 Rust 的 Unikernel,安全性更好。
  • Nanos: 试图让运行 Unikernel 像运行 Docker 一样简单。

2.2 - 裁剪

裁剪

2.2.1 - 概述

裁剪概述

设备模型最小化:只保留必需的 paravirt 设备(virtio-net、virtio-blk/virtio-fs、vsock),禁用未用的 ACPI/PCI 设备与中断源,减少 emulation 成本。

关闭不必要的设备模拟,如 pci 设备,复杂 I/O

使用 virtio 驱动,减少模拟开销

3 - Guest OS

cloud hypervisor Guest OS优化

3.1 - 概述

Guest OS 优化概述

精简 Guest OS

  • 使用 unikernel 或者精简 LInux 发行版 (alpine,busybox)

  • 移除不必要的服务和守护进程

3.2 - alpine

alpine rootfs 优化

背景

由于我们为 microvm 场景定制的内核是 25.9MB 的全内置 (Monolithic) 内核,因此 Rootfs 制作将遵循一个核心原则:彻底剔除所有内核模块和硬件固件。

Alpine 使用 musl libc 和 BusyBox,是目前启动最快的 Linux。

定制脚本

mkdir -p /home/sky/work/code/cloud-hypervisor/rootfs/alphine
cd /home/sky/work/code/cloud-hypervisor/rootfs/alphine

vi build_agent_rootfs_erofs.sh

内容为:

#!/bin/bash
set -e

# --- 配置参数 ---
ROOTFS_NAME="alpine_rootfs.erofs"
WORKING_DIR="./mnt_alpine"
ALPINE_VERSION="3.20.3"
ALPINE_TAR="alpine-minirootfs-${ALPINE_VERSION}-x86_64.tar.gz"
ALPINE_URL="https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/${ALPINE_TAR}"

echo "🚀 开始自动化构建 EROFS + DAX 专用 Alpine Rootfs..."

# 1. 依赖检查
if ! command -v mkfs.erofs &> /dev/null; then
    echo "❌ 错误: 未安装 erofs-utils。请运行: sudo apt install erofs-utils"
    exit 1
fi

# 2. 下载原始 Rootfs 包
if [ ! -f "$ALPINE_TAR" ]; then
    echo "📥 下载 Alpine 原始包..."
    curl -L -O "$ALPINE_URL"
fi

# 3. 准备工作目录
echo "📂 准备构建目录..."
sudo rm -rf "$WORKING_DIR"
mkdir -p "$WORKING_DIR"
sudo tar -xzkf "$ALPINE_TAR" -C "$WORKING_DIR"

# 4. 进入 Chroot 进行内部定制
echo "🛠️ 开始内部定制..."
sudo cp /etc/resolv.conf "$WORKING_DIR/etc/resolv.conf"

sudo chroot "$WORKING_DIR" /bin/sh <<EOF
# 设置密码
echo "root:root" | chpasswd

# 1. 基础环境补全
mkdir -p /etc/init.d
mkdir -p /run/openrc
touch /run/openrc/softlevel

# 2. 优化终端:仅保留 hvc0
sed -i '/tty[0-9]/d' /etc/inittab
if ! grep -q "hvc0" /etc/inittab; then
    echo "hvc0::respawn:/sbin/getty -L hvc0 115200 vt100" >> /etc/inittab
fi

# 3. 换源并安装 OpenRC 
echo "https://mirrors.aliyun.com/alpine/v3.20/main" > /etc/apk/repositories
echo "https://mirrors.aliyun.com/alpine/v3.20/community" >> /etc/apk/repositories
apk add --no-cache openrc

# 4. 配置启动项
rc-update add devfs boot
rc-update add local boot
EOF

# 5. 注入 OverlayFS 极速启动逻辑 (针对 DAX 和写层优化)
echo "⚡ 注入 OverlayFS 启动逻辑..."
sudo tee "$WORKING_DIR/etc/init.d/rcS" <<EOF
#!/bin/sh
# 基础虚拟文件系统挂载
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev

# 准备写层 (假设 /dev/vda 是通过 --disk 传入的 Ext4 镜像)
mkdir -p /mnt/write_layer
if mount -t ext4 -o noatime /dev/vda /mnt/write_layer; then
    echo "✅ Writable layer (/dev/vda) detected."
    # 准备 OverlayFS 结构
    mkdir -p /mnt/write_layer/upper /mnt/write_layer/work
    # 将 /var 叠加到写层,使得日志和配置可持久化
    mount -t overlay overlay -o lowerdir=/var,upperdir=/mnt/write_layer/upper,workdir=/mnt/write_layer/work /var
else
    echo "⚠️ Warning: No writable disk detected, using tmpfs for /var."
    mount -t tmpfs tmpfs /var
fi

# 其他临时目录使用内存
mount -t tmpfs tmpfs /tmp
mount -t tmpfs tmpfs /run

echo "------------------------------------------------"
echo "  Welcome to Agent MicroVM (EROFS + DAX)       "
echo "  Status: Rootfs is Read-Only | /var is Writable"
echo "------------------------------------------------"

# 执行正常的 init 流程
exec /sbin/init
EOF

sudo chmod +x "$WORKING_DIR/etc/init.d/rcS"

# 6. 极致清理
echo "🧹 执行最后瘦身清理..."
sudo rm -rf "$WORKING_DIR/lib/modules"/*
sudo rm -rf "$WORKING_DIR/lib/firmware"/*
sudo rm -rf "$WORKING_DIR/var/cache/apk"/*
sudo rm -f "$WORKING_DIR/etc/resolv.conf"

# 7. 构建 EROFS 镜像 (核心步骤)
# -d4: 4KB对齐,DAX 必需
# --fsid=rootfs: 方便内核识别
echo "💎 构建 EROFS 镜像..."
rm -f "$ROOTFS_NAME"
sudo mkfs.erofs -d4 "$ROOTFS_NAME" "$WORKING_DIR"

# 8. 完成
sudo rm -rf "$WORKING_DIR"
echo "✅ 构建完成!镜像文件: ${ROOTFS_NAME}"
ls -lh "$ROOTFS_NAME"

增加执行权限:

chmod +x build_agent_rootfs_erofs.sh

执行:

./build_agent_rootfs_erofs.sh

执行完成后,rootfs 文件会生成在当前目录下,大小为约 10MB。

-rw-r--r-- 1 root root 8.9M Feb 28 09:57 alpine_rootfs.erofs

测试

准备工作

准备工作目录和文件:

mkdir -p /home/sky/work/test/cloudhypervisor/agent
cd /home/sky/work/test/cloudhypervisor/agent
cp /home/sky/work/code/cloud-hypervisor/linux/vmlinux .
cp /home/sky/work/code/cloud-hypervisor/rootfs/alphine/alpine_rootfs.erofs .

准备可写层文件:

# 1. 创建一个 64MB 的原始镜像 (大小可根据日志量调整)
# 使用 seek 可以创建一个空洞文件,逻辑大小 64M,实际初始占用 0
dd if=/dev/zero of=write_layer_template.img bs=1M count=0 seek=64

# 2. 格式化为极致精简的 Ext4
# -O ^has_journal: 关键优化!禁用日志。
#   在只读根镜像+独立写层的架构下,日志会产生双倍 IO 开销,禁用它能大幅提升小文件写入速度。
# -O ^huge_file,^flex_bg: 移除针对大文件系统的冗余特性。
# -m 0: 将保留给 root 用户的 5% 空间设为 0,充分利用 64MB。
mkfs.ext4 -O ^has_journal,^huge_file,^flex_bg -m 0 write_layer_template.img

启动 microvm

mkdir microvm-1
cp write_layer_template.img microvm-1/write_layer.img

sudo /usr/local/bin/cloud-hypervisor \
    --kernel ./vmlinux \
    --memory size=1024M \
    --pmem file=./alpine_rootfs.erofs \
    --disk path=./microvm-1/write_layer.img \
    --cpus boot=1 \
    --console tty \
    --cmdline "console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS"

然后可以用帐号 root/root 登录(帐号密码在前面的脚本中有设置,可修改)。关机时注意 alphine Linux 默认不带 shutdown 命令,关机要用 poweroff 命令, 重启可以用普通的 reboot 命令。

如果想增加 shutdown 命令,可以:

apk update
apk add sysvinit
# 之后就能用 shutdown
shutdown -h now
shutdown -r now

先用 poweroff ,不额外安装了。

进程分析

这是启动单个 microvm 的进程信息:

ps -ef | grep cloud
root     1351503 1349610  0 14:50 pts/0    00:00:00 sudo /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS
root     1351505 1351503  0 14:50 pts/1    00:00:00 sudo /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS
root     1351506 1351505  1 14:50 pts/1    00:00:00 /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS
root     1351509 1351506  0 14:50 pts/1    00:00:00 /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS

这是一个多层级进程结构。虽然只运行了一个启动命令,但在 Linux 的 ps 输出中出现了 4 个相关的条目。这是由 sudo 的工作机制、Cloud Hypervisor 的内部架构以及 Linux 线程的处理方式共同决定的。

按照 PID (进程 ID) 和 PPID (父进程 ID) 的链路来拆解,进程链路树状:

PID PPID 用户 角色 状态描述
1351503 1349610 root Sudo Shell pts/0 输入命令后产生的第一个 sudo 进程。
1351505 1351503 root Sudo Runner sudo 内部为了安全执行目标程序而分叉(fork)出的运行环境。
1351506 1351505 root CH Main (核心) 这是真正的 MicroVM 主进程。它负责内存管理、设备初始化。
1351509 1351506 root CH Worker 这是一个子线程(被 ps 显示为进程),通常负责异步 I/O 或 API 处理。

由于存在多余的 sudo 进程,因此大规模部署时不建议用 sudo。考虑到直接用 root 也不够安全,因此最好的解决方式是给运行 cloud hypervisor 的非 root 帐号 和 cloud-hypervisor 二进制文件赋予需要的权限。

以 sky 帐号为例,具体需要的操作有:

# 赋予 ch 网络管理、原始 IO 和内存锁定权限
sudo setcap 'cap_net_admin,cap_sys_rawio,cap_ipc_lock+ep' /usr/local/bin/cloud-hypervisor

# 设置设备访问权限
# Cloud Hypervisor 需要访问 /dev/kvm(虚拟化加速)和 /dev/vhost-vsock 等设备
# 将帐号加入 kvm 组
sudo usermod -aG kvm sky
# 为了确保每次开机 /dev/kvm 等设备都能被 sky 访问,创建一条规则
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0660"' | sudo tee /etc/udev/rules.d/99-kvm.rules
echo 'KERNEL=="vhost-vsock", GROUP="kvm", MODE="0660"' | sudo tee -a /etc/udev/rules.d/99-kvm.rules
sudo udevadm control --reload-rules && sudo udevadm trigger

# 确保你的 vmlinux、alpine_rootfs.erofs 以及 microvm-1/ 目录下的所有文件,属主都是 sky
# sudo chown -R sky:sky /home/sky/work/test/cloudhypervisor/agent

去掉 sudo,用 sky 帐号启动:

/usr/local/bin/cloud-hypervisor \
    --kernel ./vmlinux \
    --memory size=1024M \
    --pmem file=./alpine_rootfs.erofs \
    --disk path=./microvm-1/write_layer.img \
    --cpus boot=1 \
    --console tty \
    --cmdline "console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS quiet"

ps的信息如下:

ps -ef | grep cloud

sky      1352498 1351990  0 15:10 pts/0    00:00:00 /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS

sky      1352501 1352498  0 15:10 pts/0    00:00:00 /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS

去掉 sudo 后,现在的 ps 输出变得非常清爽且符合预期。中间那两层中转进程彻底消失了,直接看到的是 Cloud Hypervisor 本身。

PID PPID 用户 角色 状态描述
1352498 1351990 sky 主进程 (Parent) 这是你手动启动的那个二进制文件,负责资源协调。
1352501 1352498 sky 工作线程 (Child/Thread) 它是主进程派生出的一个轻量级进程(LWP),通常处理 I/O 或信号。

内存分析

sudo pmap -x 1351506
1351506:   /usr/local/bin/cloud-hypervisor --kernel ./vmlinux --memory size=1024M --pmem file=./alpine_rootfs.erofs --disk path=./microvm-1/write_layer.img --cpus boot=1 --console tty --cmdline console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS
Address           Kbytes     RSS   Dirty Mode  Mapping
000055556aece000       4       0       0 -----   [ anon ]
000055556aecf000       8       8       8 rw---   [ anon ]
00007f8d66ff4000       8       0       0 -----   [ anon ]
00007f8d66ff6000    2052       4       4 rw---   [ anon ]
00007f8d671f7000       8       0       0 -----   [ anon ]
00007f8d671f9000    2052      20      20 rw---   [ anon ]
00007f8d673fa000       8       0       0 -----   [ anon ]
00007f8d673fc000    2052       4       4 rw---   [ anon ]
00007f8d675fd000       8       0       0 -----   [ anon ]
00007f8d675ff000    2052    2048    2048 rw---   [ anon ]
00007f8d67800000   10240    2752       0 rw-s- alpine_rootfs.erofs
00007f8d683fd000       8       0       0 -----   [ anon ]
00007f8d683ff000    2052    2048    2048 rw---   [ anon ]
00007f8d68600000 1048576   69632   69632 rw---   [ anon ]
00007f8da87fa000       8       0       0 -----   [ anon ]
00007f8da87fc000    2052       4       4 rw---   [ anon ]
00007f8da89fd000       8       0       0 -----   [ anon ]
00007f8da89ff000    2052    2048    2048 rw---   [ anon ]
00007f8da8c00000     368     368       0 r---- cloud-hypervisor
00007f8da8c5c000    3276    2940       0 r-x-- cloud-hypervisor
00007f8da8f8f000     576     384       0 r---- cloud-hypervisor
00007f8da901f000     600     260     260 rw--- cloud-hypervisor
00007f8da90b5000       8       8       8 rw---   [ anon ]
00007f8da910b000       4       4       4 rw---   [ anon ]
00007f8da910c000       4       0       0 -----   [ anon ]
00007f8da910d000       8       0       0 rw---   [ anon ]
00007f8da910f000       4       4       4 rw---   [ anon ]
00007f8da9110000       4       0       0 -----   [ anon ]
00007f8da9111000       8       0       0 rw---   [ anon ]
00007f8da9113000     124      56      56 rw---   [ anon ]
00007f8da9132000       8       8       8 rw-s-   [ anon ]
00007f8da9134000       8       8       4 rw-s-   [ anon ]
00007f8da9136000       4       0       0 -----   [ anon ]
00007f8da9137000       8       0       0 rw---   [ anon ]
00007f8da9139000      20      16      16 rw---   [ anon ]
00007f8da913e000       4       0       0 -----   [ anon ]
00007f8da913f000       8       0       0 rw---   [ anon ]
00007f8da9141000       4       0       0 -----   [ anon ]
00007f8da9142000       8       0       0 rw---   [ anon ]
00007f8da9144000      60      60      56 rw---   [ anon ]
00007f8da9153000      12       8       8 rw-s-   [ anon ]
00007f8da9156000       4       4       4 rw-s- zero (deleted)
00007f8da9157000      84      80      80 rw---   [ anon ]
00007f8da916c000       4       0       0 -----   [ anon ]
00007f8da916d000       8       0       0 rw---   [ anon ]
00007f8da916f000       4       4       4 rw---   [ anon ]
00007f8da9170000       4       0       0 -----   [ anon ]
00007f8da9171000       8       0       0 rw---   [ anon ]
00007f8da9173000      72      72      72 rw---   [ anon ]
00007f8da9185000       4       0       0 -----   [ anon ]
00007f8da9186000       8       0       0 rw---   [ anon ]
00007f8da9188000       4       4       4 rw---   [ anon ]
00007f8da9189000      16       0       0 r----   [ anon ]
00007f8da918d000       8       4       0 r-x--   [ anon ]
00007ffe2b7bf000     132      60      60 rw---   [ stack ]
---------------- ------- ------- ------- 
total kB         1078736   82920   76464

启动脚本优化

/usr/local/bin/cloud-hypervisor \
    --kernel ./vmlinux \
    --memory size=1024M,shared=on \
    --pmem file=./alpine_rootfs.erofs \
    --disk path=./microvm-1/write_layer.img \
    --cpus boot=1 \
    --console tty \
    --cmdline "console=hvc0 root=/dev/pmem0 ro rootfstype=erofs init=/etc/init.d/rcS quiet mitigations=off lpj=9676800 raid=noautodetect"

参考资料

和 gemini 的交互过程:

https://gemini.google.com/app/604671631189df20

3.3 - debian13

debian13 rootfs 优化

背景

由于我们为 microvm 场景定制的内核是 25.9MB 的全内置 (Monolithic) 内核,因此 Rootfs 制作将遵循一个核心原则:彻底剔除所有内核模块和硬件固件。

4 - 内核

cloud hypervisor 内核优化

4.1 - 概述

内核优化概述

内核参数调优:

  • 调整 vm.swappiness , dirty_ratio 参数,减少内存抖动

4.2 - 下载

cloud hypervisor官方优化内核下载

信息

cloud hypervisor官方优化内核的代码仓库为:

https://github.com/cloud-hypervisor/linux/

优化内核的二进制下载地址为:

https://github.com/cloud-hypervisor/linux/releases

选择

最新版本为 ch-release-v6.16.9-20251112, 这是 linux 6.16 内核。考虑到我目前用的 debian 13 的物理机的内核是 6.12 内核,因此我保持两者的一致,继续使用 linux 6.12 内核作为 guest os 的内核。

等以后 debian13 升级到 6.16 内核再考虑升级 guest os 的内核版本。

支持 x86-64, arm64 和 riscv, 以 x86-64 为例,有两个版本:

  • bzImage-x86_64: 7.99MB,标准 Linux 发行版用的压缩内核。虽然 Cloud Hypervisor 也支持,但它在启动时需要额外的解压过程,对内存优化场景不如 vmlinux 直接。

  • vmlinux-x86_64: 45.8MB, 原始的内核 ELF 文件(未压缩)。Cloud Hypervisor 可以直接解析它

推荐选择 vmlinux 版本: Cloud Hypervisor 属于“轻量级 VMM”,它支持 Direct Kernel Boot(直接内核引导)。与传统虚拟机需要 bzImage(带解压引导头的压缩镜像)不同,Cloud Hypervisor 更倾向于直接加载 ELF 格式的 vmlinux。这样启动速度最快,且内存占用最少。

下载

最新的 6.12 内核版本为:

https://github.com/cloud-hypervisor/linux/releases/tag/ch-release-v6.12.8-20250613

下载到特定目录,方便以后使用:

mkdir /home/sky/work/soft/cloudhypervisor/kernel/ch-6.12

cd /home/sky/work/soft/cloudhypervisor/kernel/ch-6.12

wget https://github.com/cloud-hypervisor/linux/releases/download/ch-release-v6.12.8-20250613/vmlinux-x86_64

cp vmlinux-x86_64 vmlinux

mv vmlinux-x86_64 vmlinux-x86_64-download

4.3 - 构建

定制用于 microvm 的极致精简内核

信息

cloud hypervisor官方优化内核的代码仓库为:

https://github.com/cloud-hypervisor/linux/

选择目前最稳定的分支版本: 6.12.8

构建

准备工作

先安装依赖包:

sudo apt install libelf-dev
sudo apt install bc

下载源码,切到 6.12.8 分支:

mkdir /home/sky/work/code/cloud-hypervisor
cd /home/sky/work/code/cloud-hypervisor

git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8
cd linux

默认构建

# 生成 Cloud Hypervisor 专属精简配置
make ch_defconfig

# 开始编译 (只生成 vmlinux,不浪费时间打包 bzImage)
make -j$(nproc) vmlinux

完成后,生成的 vmlinux 文件就放在根目录,大小为 48MB:

ls -lh vmlinux         
-rwxrwxr-x 1 sky sky 48M Feb 26 19:45 vmlinux

这个版本的内核还是太大了,包含了大量的调试信息和不必要的通用驱动。需要继续裁剪。

定制极简内核

可以通过菜单方式进行配置:

make menuconfig

但这个方式比较繁琐,而且不方便重用。因此我们采用命令行方式进行配置。

查找内核空间占用

首先找出来内核里最占空间的“罪魁祸首”是谁,比如这是执行 make ch_defconfig 之后构建的 linux 内核的空间占用结果:

nm --size-sort -r vmlinux | head -n 20

输出结果为:

0000000000580000 d _printk_rb_static_infos
0000000000400000 d sme_workarea
0000000000200000 b __log_buf
0000000000180000 d _printk_rb_static_descs
0000000000044000 b preferred_node_policy
0000000000040000 D early_dynamic_pgts
0000000000019000 d change_point_list
0000000000018000 b wait_table
0000000000010130 B hstates
0000000000010000 b stack_pools
0000000000010000 b __brk_early_pgt_alloc
0000000000010000 b __brk_dmi_alloc
0000000000010000 D __apicid_to_node
000000000000fa04 d e820_table_kexec_init
000000000000fa04 d e820_table_init
000000000000fa04 d e820_table_firmware_init
000000000000fa00 d new_entries
000000000000c800 B pfn_mapped
000000000000c800 d change_point
000000000000c008 b numa_reserved_meminfo

此时内核文件的大小为 48 MB:

ls -lh vmlinux         
-rwxrwxr-x 1 sky sky 48M Feb 26 19:45 vmlinux

我们开始逐个进行裁剪,看看内核大小的变化。

开启尺寸优化

开启 GCC 的 -Os 优化级别,显著减少内核体积(通常能砍掉 2MB - 5MB)。

./scripts/config --enable CC_OPTIMIZE_FOR_SIZE

make clean
make olddefconfig

make -j$(nproc) vmlinux
ls -lh vmlinux
nm --size-sort -r vmlinux | head -n 20

但比较奇怪的是,单独开启这个优化,内核文件大小反而从 48MB 增加到了 49MB。但没关系,后面陆续裁剪之后这个优化的效果会显露出来。

ls -lh vmlinux
-rwxrwxr-x 1 sky sky 49M Feb 26 19:46 vmlinux

缩小日志缓存区

排名第一的 _printk_rb_static_infos 占用了 5.5MB, __log_buf 占用 2MB。

0000000000580000 d _printk_rb_static_infos
0000000000200000 b __log_buf

内核体积大,很大程度上是因为日志缓冲区(Log Buffer)和打印信息设置得太大

路径: General setup -> Kernel log buffer size,默认值是 21,修改为 14.

检查 General setup -> CPU kernel log buffer size contribution,设置为 0.

为什么设为 0 是合适的?

在标准的 Linux 内核中,这个参数的作用是:根据 CPU 核心数动态增加日志缓冲区的大小。

公式大致为:Total Log Buffer = Default Buffer + (Num_CPUs * 2^Contribution)。

由于我的 MicroVM 通常只分配 1 到 4 个虚拟 CPU,这种动态增长几乎没有意义,反而会静态预留内存页。

将其设为 0 意味着禁用动态增长,强制内核仅使用设定的 2^14 (16KB) 的基础缓冲区。对于只跑特定应用的 MicroVM,16KB 的启动日志已经绰绰有余。

界面操作还是比较麻烦的,尤其有些配置隐藏的比较深,可以通过命令来执行:

# 极度缩小日志缓冲区 (从 2MB+ 压低到 16KB)
./scripts/config --set-val LOG_BUF_SHIFT 14
./scripts/config --set-val LOG_CPU_MAX_BUF_SHIFT 0

# 关闭调试索引和 5 级页表 (节省静态内存)
./scripts/config --disable PRINTK_INDEX
./scripts/config --disable X86_5LEVEL

# 开启 Retpoline 支持
./scripts/config --enable RETPOLINE

裁剪之后重新编译:

make olddefconfig
make clean
make -j$(nproc) vmlinux
ls -lh vmlinux
nm --size-sort -r vmlinux | head -n 20

编译之后的 linux 内核文件大小为41MB,和之前的49MB相比减少了8MB:

ls -lh vmlinux
-rwxrwxr-x 1 sky sky 41M Feb 26 19:48 vmlinux

对比之前,_printk_rb_static_infos 和 __log_buf 这两个空间占用第一和第三的项目消失了:

nm --size-sort -r vmlinux | head -n 20

0000000000400000 d sme_workarea
0000000000044000 b preferred_node_policy
0000000000040000 D early_dynamic_pgts
0000000000019000 d change_point_list

关闭 SME

继续裁剪SME,Secure Memory Encryption 是 AMD 处理器的内存加密特性,通常在 MicroVM 内部不需要这个功能。

0000000000400000 d sme_workarea
./scripts/config --disable AMD_MEM_ENCRYPT

make olddefconfig
make clean
make -j$(nproc) vmlinux
ls -lh vmlinux
ls -l vmlinux
nm --size-sort -r vmlinux | head -n 20

裁剪 SME 之后,内核文件大小从41MB降低到了30MB:

ls -lh vmlinux                            
-rwxrwxr-x 1 sky sky 30M Feb 26 19:50 vmlinux
ls -l vmlinux                            
-rwxrwxr-x 1 sky sky 31133144 Feb 26 19:50 vmlinux

裁剪非必要的子系统

对于 MicroVM 来说,有些子系统是不需要的,比如图形支持,声音支持等。

./scripts/config --disable DRM
./scripts/config --disable SOUND
./scripts/config --disable ETHERNET
./scripts/config --disable HID
./scripts/config --disable INPUT
./scripts/config --disable KVM
./scripts/config --disable KVM_INTEL
./scripts/config --disable KVM_AMD
./scripts/config --disable PERF_EVENTS
./scripts/config --disable PROFILING
./scripts/config --disable BPF_SYSCALL
./scripts/config --disable AUDIT

make olddefconfig
make clean
make -j$(nproc) vmlinux
ls -lh vmlinux
ls -l vmlinux
nm --size-sort -r vmlinux | head -n 20

裁剪非必要的子系统之后,内核文件大小从30MB降低到了26MB:

-rwxrwxr-x 1 sky sky 26M Feb 26 20:27 vmlinux
-rwxrwxr-x 1 sky sky 26301552 Feb 26 20:27 vmlinux

精简文件系统

文件系统是 MicroVM 中非常重要的一个部分,但是有些文件系统是不需要的,比如 NTFS, CIFS, NFS 等。

# --- 1. 核心调试与辅助系统 (体积与内存大头) ---
# DEBUG_FS 是内核中非常庞大的符号和接口库
./scripts/config --disable DEBUG_FS
./scripts/config --disable BLK_DEBUG_FS
./scripts/config --disable STACKTRACE
./scripts/config --disable KALLSYMS_ALL

# --- 2. 移除现代存储高级特性 (MicroVM 通常不需要) ---
# DAX 是为了持久性内存(Optane等)设计的,虚拟化环境极少用到
./scripts/config --disable FS_DAX
./scripts/config --disable FS_DAX_PMD
# 禁用内核级文件加密逻辑
./scripts/config --disable FS_ENCRYPTION
./scripts/config --disable FS_ENCRYPTION_ALGS
./scripts/config --disable FS_VERITY

# --- 3. 辅助文件系统逻辑清理 ---
./scripts/config --disable AUTOFS_FS
./scripts/config --disable EFIVAR_FS
./scripts/config --disable CONFIGFS_FS
./scripts/config --disable FSNOTIFY

# --- 4. Ext4 深度瘦身 (只保留最基础的读写逻辑) ---
# 禁用 ACL(访问控制列表) 和 Security Labels(如 SELinux 标签)
# 如果你的 Rootfs 只是简单的权限管理,这两项可以砍掉几百个符号
./scripts/config --disable EXT4_FS_POSIX_ACL
./scripts/config --disable EXT4_FS_SECURITY
./scripts/config --disable FS_POSIX_ACL
./scripts/config --disable QUOTA

# --- 5. 虚拟化文件系统按需清理 ---
# 如果你只用 virtio-blk (磁盘镜像),可以关掉下两项;
# 如果你用 Kata Containers 这种 virtio-fs 挂载模式,请保留它们。
./scripts/config --disable FUSE_FS
./scripts/config --disable VIRTIO_FS
./scripts/config --disable OVERLAY_FS

# --- 6. 常规文件系统裁剪清单 --
./scripts/config --disable XFS_FS
./scripts/config --disable BTRFS_FS
./scripts/config --disable F2FS_FS
./scripts/config --disable JFS_FS
./scripts/config --disable REISERFS_FS
./scripts/config --disable NTFS_FS
./scripts/config --disable NTFS3_FS
./scripts/config --disable CIFS
./scripts/config --disable SMB_SERVER
./scripts/config --disable NFS_FS
./scripts/config --disable GFS2_FS
./scripts/config --disable OCFS2_FS
./scripts/config --disable MINIX_FS
./scripts/config --disable HFS_FS
./scripts/config --disable HFSPLUS_FS
./scripts/config --disable ISO9660_FS
./scripts/config --disable UDF_FS
./scripts/config --disable FAT_FS
./scripts/config --disable VFAT_FS
./scripts/config --disable MSDOS_FS
./scripts/config --disable EROFS_FS
./scripts/config --disable SQUASHFS
./scripts/config --disable JOLIET
./scripts/config --disable ZISOFS

make olddefconfig
make clean
make -j$(nproc) vmlinux
ls -lh vmlinux
ls -l vmlinux
nm --size-sort -r vmlinux | head -n 20

裁剪文件系统之后,内核文件大小从26.30MB 降到 26.11MB,仅省下了约186KB。体积优化效果非常轻微:

#优化前
-rwxrwxr-x 1 sky sky 26301552 Feb 26 20:27 vmlinux

#优化后:
-rwxrwxr-x 1 sky sky 26115024 Feb 26 20:48 vmlinux

但是,虽然内核体积没减,但这个裁剪操作还有意义的,体现在以下几个方面:

  • 攻击面(Attack Surface)减小:文件系统代码是内核漏洞的重灾区。关掉 NTFS、JFS、CIFS 等,意味着黑客无法通过构造恶意的磁盘镜像或网络包来利用这些复杂的解析逻辑。

  • 启动稳定性:减少了内核启动时尝试探测不同文件系统类型的开销,虽然对 MicroVM 来说只是几毫秒,但更纯净。

  • 内存脚印(Runtime Footprint):虽然文件体积只差 28KB,但由于去掉了各种文件系统的初始化函数,内核启动后申请的 slab 缓存和全局变量会更少,运行时会更“轻”。

裁剪加密与压缩算法

nm --size-sort -r vmlinux | grep -i ' [tT] ' | head -n 20

可以看到有很多加密与压缩算法,这些算法在 MicroVM 中是不需要的:

0000000000004429 t __twofish_enc_blk8
0000000000004427 t __twofish_dec_blk8
0000000000002966 T intel_pmu_init
00000000000024c1 t sock_ops_convert_ctx_access
0000000000001ddd t ZSTD_decompressSequencesLong_default.constprop.0
0000000000001d86 t ZSTD_decompressSequencesLong_bmi2.constprop.0
0000000000001d85 T __twofish_enc_blk_3way
0000000000001d63 T twofish_dec_blk_3way
0000000000001b62 t ___bpf_prog_run
0000000000001a4c T x64_sys_call
000000000000187d T __twofish_setkey
000000000000183a t __dev_ethtool
0000000000001626 T aes_ctr_enc_256_avx_by8
0000000000001614 t HUF_decompress4X2_usingDTable_internal_default
0000000000001614 t HUF_decompress4X2_usingDTable_internal_bmi2
0000000000001519 T jbd2_journal_commit_transaction
00000000000014fa t ZSTD_decompressSequencesSplitLitBuffer_default.constprop.0
00000000000014a6 t ZSTD_decompressSequencesSplitLitBuffer_bmi2.constprop.0
0000000000001436 T aes_ctr_enc_192_avx_by8
0000000000001321 T blake2s_compress_generic
./scripts/config --disable CRYPTO_TWOFISH
./scripts/config --disable CRYPTO_TWOFISH_COMMON
./scripts/config --disable CRYPTO_TWOFISH_X86_64
./scripts/config --disable CRYPTO_TWOFISH_X86_64_3WAY
./scripts/config --disable CRYPTO_TWOFISH_AVX_X86_64
./scripts/config --disable CRYPTO_AES_NI_INTEL
./scripts/config --disable CRYPTO_MANAGER
./scripts/config --disable ZSTD_COMPRESS
./scripts/config --disable ZSTD_DECOMPRESS
./scripts/config --disable RD_ZSTD

make olddefconfig
make clean
make -j$(nproc) vmlinux
ls -lh vmlinux
ls -l vmlinux
nm --size-sort -r vmlinux | head -n 20
nm --size-sort -r vmlinux | grep -i ' [tT] ' | head -n 20

裁剪完成后体积减少了60KB:

#优化前
-rwxrwxr-x 1 sky sky 26115024 Feb 26 20:48 vmlinux
#优化后:
-rwxrwxr-x 1 sky sky 26055808 Feb 26 20:56 vmlinux

裁剪多国语言支持

NLS (Native Language Support)

在 ch_defconfig 中,内核通常为了兼容各种磁盘格式,编入了一大堆代码页(Codepages)。

grep "NLS" .config                           
CONFIG_NLS=y
CONFIG_NLS_DEFAULT="utf8"
CONFIG_NLS_CODEPAGE_437=y
CONFIG_NLS_CODEPAGE_737=y
CONFIG_NLS_CODEPAGE_775=y
CONFIG_NLS_CODEPAGE_850=y
CONFIG_NLS_CODEPAGE_852=y
CONFIG_NLS_CODEPAGE_855=y
CONFIG_NLS_CODEPAGE_857=y
CONFIG_NLS_CODEPAGE_860=y
CONFIG_NLS_CODEPAGE_861=y
CONFIG_NLS_CODEPAGE_862=y
CONFIG_NLS_CODEPAGE_863=y
CONFIG_NLS_CODEPAGE_864=y
CONFIG_NLS_CODEPAGE_865=y
CONFIG_NLS_CODEPAGE_866=y
CONFIG_NLS_CODEPAGE_869=y
CONFIG_NLS_CODEPAGE_936=y
CONFIG_NLS_CODEPAGE_950=y
CONFIG_NLS_CODEPAGE_932=y
CONFIG_NLS_CODEPAGE_949=y
CONFIG_NLS_CODEPAGE_874=y
CONFIG_NLS_ISO8859_8=y
CONFIG_NLS_CODEPAGE_1250=y
CONFIG_NLS_CODEPAGE_1251=y
CONFIG_NLS_ASCII=y
CONFIG_NLS_ISO8859_1=y
CONFIG_NLS_ISO8859_2=y
CONFIG_NLS_ISO8859_3=y
CONFIG_NLS_ISO8859_4=y
CONFIG_NLS_ISO8859_5=y
CONFIG_NLS_ISO8859_6=y
CONFIG_NLS_ISO8859_7=y
CONFIG_NLS_ISO8859_9=y
CONFIG_NLS_ISO8859_13=y
CONFIG_NLS_ISO8859_14=y
CONFIG_NLS_ISO8859_15=y
CONFIG_NLS_KOI8_R=y
CONFIG_NLS_KOI8_U=y
CONFIG_NLS_MAC_ROMAN=y
CONFIG_NLS_MAC_CELTIC=y
CONFIG_NLS_MAC_CENTEURO=y
CONFIG_NLS_MAC_CROATIAN=y
CONFIG_NLS_MAC_CYRILLIC=y
CONFIG_NLS_MAC_GAELIC=y
CONFIG_NLS_MAC_GREEK=y
CONFIG_NLS_MAC_ICELAND=y
CONFIG_NLS_MAC_INUIT=y
CONFIG_NLS_MAC_ROMANIAN=y
CONFIG_NLS_MAC_TURKISH=y
CONFIG_NLS_UTF8=y

对于只运行 Linux 现代文件系统(如 ext4/xfs)的 MicroVM,这些全是多余的。

# 彻底关闭 NLS 支持
# 6.1 强制关闭所有已有的 NLS 子项
grep "CONFIG_NLS_" .config | grep "=y" | cut -d'=' -f1 | while read line; do
    ./scripts/config --disable "$line"
done

# 6.2 重新开启必要的 UTF-8 和基础框架
./scripts/config --enable CONFIG_NLS
./scripts/config --enable CONFIG_NLS_UTF8
./scripts/config --enable CONFIG_NLS_ASCII
./scripts/config --enable CONFIG_NLS_ISO8859_1    # 西欧语言基石,必留
./scripts/config --enable CONFIG_NLS_CODEPAGE_936 # 简体中文支持
./scripts/config --set-str CONFIG_NLS_DEFAULT "utf8"
# 补充:确保基础的转换工具也被编译进去
./scripts/config --enable CONFIG_NLS_UCS2_UTILS

make olddefconfig
make clean
make -j$(nproc) vmlinux
ls -lh vmlinux
ls -l vmlinux
nm --size-sort -r vmlinux | head -n 20
nm --size-sort -r vmlinux | grep -i ' [tT] ' | head -n 20

NLS裁剪完成后体积减少了55KB:

#优化前
-rwxrwxr-x 1 sky sky 26055808 Feb 26 20:56 vmlinux
#优化后:
-rwxrwxr-x 1 sky sky 26000328 Feb 26 21:09 vmlinux

为了凑数,减少最后的 328 字节,我们再裁剪一下:

./scripts/config --disable UTS_NS

裁剪完成后体积减少了328字节:

#优化前
-rwxrwxr-x 1 sky sky 26000328 Feb 26 21:09 vmlinux
#优化后:
-rwxrwxr-x 1 sky sky 25999480 Feb 26 21:13 vmlinux

总结

把上面的裁剪汇总一下,这样以后如果需要裁剪内核,只需要执行下面的命令:

make clean
make ch_defconfig

./scripts/config --enable CC_OPTIMIZE_FOR_SIZE
./scripts/config --set-val LOG_BUF_SHIFT 14
./scripts/config --set-val LOG_CPU_MAX_BUF_SHIFT 0
./scripts/config --disable PRINTK_INDEX
./scripts/config --disable X86_5LEVEL
./scripts/config --enable RETPOLINE
./scripts/config --disable AMD_MEM_ENCRYPT

./scripts/config --disable DRM
./scripts/config --disable SOUND
./scripts/config --disable ETHERNET
./scripts/config --disable HID
./scripts/config --disable INPUT
./scripts/config --disable KVM
./scripts/config --disable KVM_INTEL
./scripts/config --disable KVM_AMD
./scripts/config --disable PERF_EVENTS
./scripts/config --disable PROFILING
./scripts/config --disable BPF_SYSCALL
./scripts/config --disable AUDIT

./scripts/config --disable DEBUG_FS
./scripts/config --disable BLK_DEBUG_FS
./scripts/config --disable STACKTRACE
./scripts/config --disable KALLSYMS_ALL
./scripts/config --disable FS_DAX
./scripts/config --disable FS_DAX_PMD
./scripts/config --disable FS_ENCRYPTION
./scripts/config --disable FS_ENCRYPTION_ALGS
./scripts/config --disable FS_VERITY
./scripts/config --disable AUTOFS_FS
./scripts/config --disable EFIVAR_FS
./scripts/config --disable CONFIGFS_FS
./scripts/config --disable FSNOTIFY
./scripts/config --disable EXT4_FS_POSIX_ACL
./scripts/config --disable EXT4_FS_SECURITY
./scripts/config --disable FS_POSIX_ACL
./scripts/config --disable QUOTA
./scripts/config --disable FUSE_FS
./scripts/config --disable VIRTIO_FS
./scripts/config --disable OVERLAY_FS
./scripts/config --disable XFS_FS
./scripts/config --disable BTRFS_FS
./scripts/config --disable F2FS_FS
./scripts/config --disable JFS_FS
./scripts/config --disable REISERFS_FS
./scripts/config --disable NTFS_FS
./scripts/config --disable NTFS3_FS
./scripts/config --disable CIFS
./scripts/config --disable SMB_SERVER
./scripts/config --disable NFS_FS
./scripts/config --disable GFS2_FS
./scripts/config --disable OCFS2_FS
./scripts/config --disable MINIX_FS
./scripts/config --disable HFS_FS
./scripts/config --disable HFSPLUS_FS
./scripts/config --disable ISO9660_FS
./scripts/config --disable UDF_FS
./scripts/config --disable FAT_FS
./scripts/config --disable VFAT_FS
./scripts/config --disable MSDOS_FS
./scripts/config --disable EROFS_FS
./scripts/config --disable SQUASHFS
./scripts/config --disable JOLIET
./scripts/config --disable ZISOFS

./scripts/config --disable CRYPTO_TWOFISH
./scripts/config --disable CRYPTO_TWOFISH_COMMON
./scripts/config --disable CRYPTO_TWOFISH_X86_64
./scripts/config --disable CRYPTO_TWOFISH_X86_64_3WAY
./scripts/config --disable CRYPTO_TWOFISH_AVX_X86_64
./scripts/config --disable CRYPTO_AES_NI_INTEL
./scripts/config --disable CRYPTO_MANAGER
./scripts/config --disable ZSTD_COMPRESS
./scripts/config --disable ZSTD_DECOMPRESS
./scripts/config --disable RD_ZSTD

grep "CONFIG_NLS_" .config | grep "=y" | cut -d'=' -f1 | while read line; do
    ./scripts/config --disable "$line"
done
./scripts/config --enable CONFIG_NLS
./scripts/config --enable CONFIG_NLS_UTF8
./scripts/config --enable CONFIG_NLS_ASCII
./scripts/config --enable CONFIG_NLS_ISO8859_1
./scripts/config --enable CONFIG_NLS_CODEPAGE_936 
./scripts/config --set-str CONFIG_NLS_DEFAULT "utf8"
./scripts/config --enable CONFIG_NLS_UCS2_UTILS
./scripts/config --disable UTS_NS

make clean
make olddefconfig
make -j$(nproc) vmlinux
ls -lh vmlinux
ls -l vmlinux
nm --size-sort -r vmlinux | head -n 20
nm --size-sort -r vmlinux | grep -i ' [tT] ' | head -n 20

最终内核大小为25M:

-rwxrwxr-x 1 sky sky 25M Feb 26 21:21 vmlinux
-rwxrwxr-x 1 sky sky 25999984 Feb 26 21:21 vmlinux

为了方便使用,创建脚本:

vi build_minimal_kernel.sh

内容为:

#!/bin/bash
set -e

echo "开始构建极致瘦身内核 (Target < 26MB)..."

# 1. 初始化配置
make clean
make ch_defconfig

# 2. 核心架构与编译器优化
./scripts/config --enable CC_OPTIMIZE_FOR_SIZE
./scripts/config --set-val LOG_BUF_SHIFT 14
./scripts/config --set-val LOG_CPU_MAX_BUF_SHIFT 0
./scripts/config --disable PRINTK_INDEX
./scripts/config --disable X86_5LEVEL
./scripts/config --enable RETPOLINE
./scripts/config --disable AMD_MEM_ENCRYPT
./scripts/config --disable UTS_NS

# 3. 剥离大型子系统 (显卡、声音、以太网、输入设备等)
./scripts/config --disable DRM
./scripts/config --disable SOUND
./scripts/config --disable ETHERNET
./scripts/config --disable HID
./scripts/config --disable INPUT
./scripts/config --disable KVM
./scripts/config --disable KVM_INTEL
./scripts/config --disable KVM_AMD
./scripts/config --disable PERF_EVENTS
./scripts/config --disable PROFILING
./scripts/config --disable BPF_SYSCALL
./scripts/config --disable AUDIT

# 4. 文件系统深度裁剪 (仅保留基础 Ext4 逻辑)
./scripts/config --disable DEBUG_FS
./scripts/config --disable BLK_DEBUG_FS
./scripts/config --disable STACKTRACE
./scripts/config --disable KALLSYMS_ALL
./scripts/config --disable FS_DAX
./scripts/config --disable FS_DAX_PMD
./scripts/config --disable FS_ENCRYPTION
./scripts/config --disable FS_ENCRYPTION_ALGS
./scripts/config --disable FS_VERITY
./scripts/config --disable AUTOFS_FS
./scripts/config --disable EFIVAR_FS
./scripts/config --disable CONFIGFS_FS
./scripts/config --disable FSNOTIFY
./scripts/config --disable EXT4_FS_POSIX_ACL
./scripts/config --disable EXT4_FS_SECURITY
./scripts/config --disable FS_POSIX_ACL
./scripts/config --disable QUOTA
./scripts/config --disable FUSE_FS
./scripts/config --disable VIRTIO_FS
./scripts/config --disable OVERLAY_FS

# 批量关闭其他不常用文件系统
for fs in XFS_FS BTRFS_FS F2FS_FS JFS_FS REISERFS_FS NTFS_FS NTFS3_FS CIFS SMB_SERVER \
          NFS_FS GFS2_FS OCFS2_FS MINIX_FS HFS_FS HFSPLUS_FS ISO9660_FS UDF_FS \
          FAT_FS VFAT_FS MSDOS_FS EROFS_FS SQUASHFS JOLIET ZISOFS; do
    ./scripts/config --disable $fs
done

# 5. 加密与压缩算法裁剪 (剔除汇编优化重灾区)
./scripts/config --disable CRYPTO_TWOFISH
./scripts/config --disable CRYPTO_TWOFISH_COMMON
./scripts/config --disable CRYPTO_TWOFISH_X86_64
./scripts/config --disable CRYPTO_TWOFISH_X86_64_3WAY
./scripts/config --disable CRYPTO_TWOFISH_AVX_X86_64
./scripts/config --disable CRYPTO_AES_NI_INTEL
./scripts/config --disable CRYPTO_MANAGER
./scripts/config --disable ZSTD_COMPRESS
./scripts/config --disable ZSTD_DECOMPRESS
./scripts/config --disable RD_ZSTD

# 6. 国际化 NLS 精简与简体中文/西欧补全
# 先清空所有 CodePage
grep "CONFIG_NLS_" .config | grep "=y" | cut -d'=' -f1 | while read line; do
    ./scripts/config --disable "$line"
done
# 精准开启
./scripts/config --enable CONFIG_NLS
./scripts/config --enable CONFIG_NLS_UTF8
./scripts/config --enable CONFIG_NLS_ASCII
./scripts/config --enable CONFIG_NLS_ISO8859_1
./scripts/config --enable CONFIG_NLS_CODEPAGE_936 
./scripts/config --set-str CONFIG_NLS_DEFAULT "utf8"
./scripts/config --enable CONFIG_NLS_UCS2_UTILS

# 7. 应用配置并编译
make olddefconfig
make -j$(nproc) vmlinux

# 8. 成果展示
echo "------------------------------------------"
ls -lh vmlinux
ls -l vmlinux
echo "Top 10 Functions by Size:"
nm --size-sort -r vmlinux | head -n 10
echo "------------------------------------------"
echo "构建完成!"

增加执行权限:

chmod +x build_minimal_kernel.sh

执行:

./build_minimal_kernel.sh

执行完成后,内核文件会生成在当前目录下,大小为25M。

定制agent专属内核

在上面定制的极简内核的基础上,针对 agent 的特殊使用场景,进行定制。主要的改动有:

  • 启动 cloud hypervisor 时, 内核和 rootfs 都是只读方式
  • 内核文件通过直接内核加载 (Direct Kernel Boot) 读入,跳过 BIOS/UEFI 阶段
  • 使用 virtio-pmem 的方式挂载 rootfs,并开启 DAX 功能,以最大化利用 Host 的 Page Cache
  • 宿主机开启 KSM (Kernel Samepage Merging) 功能,以最大化利用内存

为此,内核需要进行改动:

  • 磁盘格式由 ext4 修改为 erofs
  • 需要支持 overlayfs 功能,以支持 rootfs 和可写层
  • 需要支持 dax 功能,以最大化利用 Host 的 Page Cache

构建脚本

为了方便使用,创建脚本:

vi build_agent_kernel.sh

内容为:

#!/bin/bash
set -e

echo "开始构建 agent 专属内核 (EROFS + DAX + Ext4 Writable)..."

# 1. 初始化配置
make clean
make ch_defconfig

# 2. 核心架构与编译器优化
./scripts/config --enable CC_OPTIMIZE_FOR_SIZE
./scripts/config --set-val LOG_BUF_SHIFT 14
./scripts/config --set-val LOG_CPU_MAX_BUF_SHIFT 0
./scripts/config --disable PRINTK_INDEX
./scripts/config --disable X86_5LEVEL
./scripts/config --enable RETPOLINE
./scripts/config --disable AMD_MEM_ENCRYPT
./scripts/config --disable UTS_NS

# 3. 剥离大型子系统
./scripts/config --disable DRM
./scripts/config --disable SOUND
./scripts/config --disable ETHERNET
./scripts/config --disable HID
./scripts/config --disable INPUT
./scripts/config --disable KVM
./scripts/config --disable KVM_INTEL
./scripts/config --disable KVM_AMD
./scripts/config --disable PERF_EVENTS
./scripts/config --disable PROFILING
./scripts/config --disable BPF_SYSCALL
./scripts/config --disable AUDIT

# 4. 文件系统基础裁剪 (先关掉不需要的)
./scripts/config --disable DEBUG_FS
./scripts/config --disable BLK_DEBUG_FS
./scripts/config --disable STACKTRACE
./scripts/config --disable KALLSYMS_ALL
./scripts/config --disable FS_ENCRYPTION
./scripts/config --disable FS_ENCRYPTION_ALGS
./scripts/config --disable FS_VERITY
./scripts/config --disable AUTOFS_FS
./scripts/config --disable EFIVAR_FS
./scripts/config --disable CONFIGFS_FS
./scripts/config --disable FSNOTIFY
./scripts/config --disable QUOTA
./scripts/config --disable FUSE_FS
./scripts/config --disable VIRTIO_FS

# 批量关闭不常用文件系统
for fs in XFS_FS BTRFS_FS F2FS_FS JFS_FS REISERFS_FS NTFS_FS NTFS3_FS CIFS SMB_SERVER \
          NFS_FS GFS2_FS OCFS2_FS MINIX_FS HFS_FS HFSPLUS_FS ISO9660_FS UDF_FS \
          FAT_FS VFAT_FS MSDOS_FS SQUASHFS JOLIET ZISOFS; do
    ./scripts/config --disable $fs
done

# 5. 加密与压缩算法裁剪
./scripts/config --disable CRYPTO_TWOFISH
./scripts/config --disable CRYPTO_TWOFISH_COMMON
./scripts/config --disable CRYPTO_TWOFISH_X86_64
./scripts/config --disable CRYPTO_TWOFISH_X86_64_3WAY
./scripts/config --disable CRYPTO_TWOFISH_AVX_X86_64
./scripts/config --disable CRYPTO_AES_NI_INTEL
./scripts/config --disable CRYPTO_MANAGER
./scripts/config --disable ZSTD_COMPRESS
./scripts/config --disable ZSTD_DECOMPRESS
./scripts/config --disable RD_ZSTD

# 6. NLS 保持 (UTF-8 + 中文支持)
grep "CONFIG_NLS_" .config | grep "=y" | cut -d'=' -f1 | while read line; do
    ./scripts/config --disable "$line"
done
./scripts/config --enable CONFIG_NLS
./scripts/config --enable CONFIG_NLS_UTF8
./scripts/config --enable CONFIG_NLS_ASCII
./scripts/config --enable CONFIG_NLS_ISO8859_1
./scripts/config --enable CONFIG_NLS_CODEPAGE_936 
./scripts/config --set-str CONFIG_NLS_DEFAULT "utf8"
./scripts/config --enable CONFIG_NLS_UCS2_UTILS

# 7. 开启 DAX 与内存管理 (EROFS DAX 必备)
./scripts/config --enable FS_DAX
./scripts/config --enable FS_DAX_PMD
./scripts/config --enable DAX
./scripts/config --enable ZONE_DEVICE
./scripts/config --enable MEMORY_HOTPLUG
./scripts/config --enable MEMORY_HOTREMOVE

# 8. 开启设备驱动 (virtio-pmem 用于 Rootfs, virtio-blk 用于写层)
./scripts/config --enable VIRTIO_PMEM
./scripts/config --enable LIBNVDIMM
./scripts/config --enable BLK_DEV_PMEM
./scripts/config --enable VIRTIO_BLK

# 9. 开启 EROFS (只读 Rootfs 格式)
./scripts/config --enable EROFS_FS
./scripts/config --enable EROFS_FS_XATTR
./scripts/config --enable EROFS_FS_POSIX_ACL
./scripts/config --enable EROFS_FS_ZIP

# 10. 开启 OverlayFS
./scripts/config --enable OVERLAY_FS

# 11. 优化 Ext4 (写层专用)
./scripts/config --enable EXT4_FS
./scripts/config --disable EXT4_FS_POSIX_ACL
./scripts/config --disable EXT4_FS_SECURITY
./scripts/config --disable EXT4_DEBUG
./scripts/config --disable EXT4_KUNIT_TESTS

# 12. 应用配置并编译
make olddefconfig
make -j$(nproc) vmlinux

# 13. 成果展示
echo "------------------------------------------"
ls -lh vmlinux
echo "Top 10 Functions by Size:"
nm --size-sort -r vmlinux | head -n 10
echo "------------------------------------------"
echo "agent 专属内核构建完成!"

增加执行权限:

chmod +x build_agent_kernel.sh

执行:

./build_agent_kernel.sh

执行完成后,内核文件会生成在当前目录下,大小为25M。

-rwxrwxr-x 1 sky sky 25M Feb 28 09:43 vmlinux

构建脚本改进

根据启动时的 dmesg 信息进行优化改进:

vi build_agent_kernel2.sh

内容为:

#!/bin/bash
set -e

echo "开始构建 agent 专属内核 (EROFS + DAX + Ext4 Writable)..."

# 1. 初始化配置
make clean
make ch_defconfig

# 2. 核心架构与编译器优化
./scripts/config --enable CC_OPTIMIZE_FOR_SIZE
./scripts/config --set-val LOG_BUF_SHIFT 14
./scripts/config --set-val LOG_CPU_MAX_BUF_SHIFT 0
./scripts/config --disable PRINTK_INDEX
./scripts/config --disable X86_5LEVEL
./scripts/config --enable RETPOLINE
./scripts/config --disable AMD_MEM_ENCRYPT
./scripts/config --disable UTS_NS

# 3. 剥离大型子系统
./scripts/config --disable DRM
./scripts/config --disable SOUND
./scripts/config --disable ETHERNET
./scripts/config --disable HID
./scripts/config --disable INPUT
./scripts/config --disable KVM
./scripts/config --disable KVM_INTEL
./scripts/config --disable KVM_AMD
./scripts/config --disable PERF_EVENTS
./scripts/config --disable PROFILING
./scripts/config --disable BPF_SYSCALL
./scripts/config --disable AUDIT

# 4. 文件系统基础裁剪 (先关掉不需要的)
./scripts/config --disable DEBUG_FS
./scripts/config --disable BLK_DEBUG_FS
./scripts/config --disable STACKTRACE
./scripts/config --disable KALLSYMS_ALL
./scripts/config --disable FS_ENCRYPTION
./scripts/config --disable FS_ENCRYPTION_ALGS
./scripts/config --disable FS_VERITY
./scripts/config --disable AUTOFS_FS
./scripts/config --disable EFIVAR_FS
./scripts/config --disable CONFIGFS_FS
./scripts/config --disable FSNOTIFY
./scripts/config --disable QUOTA
./scripts/config --disable FUSE_FS
./scripts/config --disable VIRTIO_FS

# 批量关闭不常用文件系统
for fs in XFS_FS BTRFS_FS F2FS_FS JFS_FS REISERFS_FS NTFS_FS NTFS3_FS CIFS SMB_SERVER \
          NFS_FS GFS2_FS OCFS2_FS MINIX_FS HFS_FS HFSPLUS_FS ISO9660_FS UDF_FS \
          FAT_FS VFAT_FS MSDOS_FS SQUASHFS JOLIET ZISOFS; do
    ./scripts/config --disable $fs
done

# 5. 加密与压缩算法裁剪
./scripts/config --disable CRYPTO_TWOFISH
./scripts/config --disable CRYPTO_TWOFISH_COMMON
./scripts/config --disable CRYPTO_TWOFISH_X86_64
./scripts/config --disable CRYPTO_TWOFISH_X86_64_3WAY
./scripts/config --disable CRYPTO_TWOFISH_AVX_X86_64
./scripts/config --disable CRYPTO_AES_NI_INTEL
./scripts/config --disable CRYPTO_MANAGER
./scripts/config --disable ZSTD_COMPRESS
./scripts/config --disable ZSTD_DECOMPRESS
./scripts/config --disable RD_ZSTD

# 6. NLS 保持 (UTF-8 + 中文支持)
grep "CONFIG_NLS_" .config | grep "=y" | cut -d'=' -f1 | while read line; do
    ./scripts/config --disable "$line"
done
./scripts/config --enable CONFIG_NLS
./scripts/config --enable CONFIG_NLS_UTF8
./scripts/config --enable CONFIG_NLS_ASCII
./scripts/config --enable CONFIG_NLS_ISO8859_1
./scripts/config --enable CONFIG_NLS_CODEPAGE_936 
./scripts/config --set-str CONFIG_NLS_DEFAULT "utf8"
./scripts/config --enable CONFIG_NLS_UCS2_UTILS

# 7. 开启 DAX 与内存管理 (EROFS DAX 必备)
./scripts/config --enable FS_DAX
./scripts/config --enable FS_DAX_PMD
./scripts/config --enable DAX
./scripts/config --enable ZONE_DEVICE
./scripts/config --enable MEMORY_HOTPLUG
./scripts/config --enable MEMORY_HOTREMOVE

# 8. 开启设备驱动 (virtio-pmem 用于 Rootfs, virtio-blk 用于写层)
./scripts/config --enable VIRTIO_PMEM
./scripts/config --enable LIBNVDIMM
./scripts/config --enable BLK_DEV_PMEM
./scripts/config --enable VIRTIO_BLK

# 9. 开启 EROFS (只读 Rootfs 格式)
./scripts/config --enable EROFS_FS
./scripts/config --enable EROFS_FS_XATTR
./scripts/config --enable EROFS_FS_POSIX_ACL
./scripts/config --enable EROFS_FS_ZIP

# 10. 开启 OverlayFS
./scripts/config --enable OVERLAY_FS

# 11. 优化 Ext4 (写层专用)
./scripts/config --enable EXT4_FS
./scripts/config --disable EXT4_FS_POSIX_ACL
./scripts/config --disable EXT4_FS_SECURITY
./scripts/config --disable EXT4_DEBUG
./scripts/config --disable EXT4_KUNIT_TESTS

# 12. 应用配置并编译
make olddefconfig


if ! grep -q "CONFIG_EROFS_FS=y" .config; then
    echo "❌ 严重错误: 依赖冲突导致 EROFS 无法内置!"
    echo "当前冲突点可能在: $(grep -E "CONFIG_EROFS_FS|DAX|NETFS" .config)"
    exit 1
fi

make -j$(nproc) vmlinux

# 13. 成果展示
echo "------------------------------------------"
ls -lh vmlinux
echo "Top 10 Functions by Size:"
nm --size-sort -r vmlinux | head -n 10
echo "------------------------------------------"
echo "agent 专属内核构建完成!"

5 - CPU优化

cloud hypervisor CPU优化

5.1 - 概述

cpu 优化概述

高密部署意味着 CPU 必然是超分 (Overcommit) 的。

  • vCPU 拓扑:

    建议 MicroVM 配置为 1 vCPU。对于微服务或函数计算场景,单核足以处理,且调度开销最小。

  • Host 调度器调整:

不要在 Host 上对 MicroVM 进行严格的物理核绑定 (Pinning),因为高密场景下这会导致碎片化。依赖 Linux CFS (Completely Fair Scheduler) 进行调度。

Halt Polling: 调整 KVM 的 halt_poll_ns。对于高密且 I/O 频繁的场景,适当降低该值,让 vCPU 空闲时尽快释放物理 CPU 资源给其他 VM,而不是空转等待唤醒。

  • VMM (Virtual Machine Monitor) 进程开销:

    Cloud Hypervisor 是多线程模型。确保其辅助线程(如 vhost-user 线程, API 线程)不会抢占过多的计算资源。

6 - 内存优化

cloud hypervisor 内存优化

内核参数调优:

  • 调整 vm.swappiness , dirty_ratio 参数,减少内存抖动

6.1 - 概述

内存优化概述

内核参数调优:

  • 调整 vm.swappiness , dirty_ratio 参数,减少内存抖动

6.2 - DAX

Derect Access 直接访问

Virtio-fs + DAX (Direct Access): 这是高密部署的“杀手锏”。

  • 原理: 传统的虚拟化中,Guest 读取文件会将其缓存在 Guest Page Cache 中,Host 也会有一份缓存,造成双重内存浪费。

  • 方案: 使用 Virtio-fs 将 Host 的文件系统直接透传给 Guest。开启 DAX Window 功能,允许 Guest 直接映射 Host 的文件内容到自己的内存地址空间,绕过 Guest 的 Page Cache。

  • 效果: 多个 MicroVM 共享同一个只读 Rootfs(底包),这些文件在物理内存中只有一份副本。这可以极大地节省内存。

6.2.1 - 知识背景

Derect Access 知识背景

6.2.1.1 - kernel docs

linux kernel docs 中对 dax 的介绍

https://docs.kernel.org/filesystems/dax.html

Direct Access for files


动机

页面缓存(page cache)通常用于缓冲对文件的读写操作。它还用于通过调用 mmap 映射到用户空间的页面。

对于类内存的块设备,页缓存页面(page cache pages)将是原始存储的不必要副本。DAX 代码通过直接对存储设备执行读写操作来消除这个额外副本。对于文件映射,存储设备会直接映射到用户空间。

用法

如果你有一个支持 DAX 的块设备,可以像往常一样在其上创建文件系统。目前 DAX 代码仅支持块大小等于内核 PAGE_SIZE 的文件,因此在创建文件系统时可能需要指定块大小。

目前有 5 个文件系统支持 DAX:ext2、ext4、xfs、virtiofs 和 erofs。在这些文件系统上启用 DAX 的方式各不相同。

在 ext2 和 erofs 上启用 DAX

挂载文件系统时,在命令行上使用 -o dax 选项或在 /etc/fstab 的选项中添加 ‘dax’。这可以启用文件系统中所有文件的 DAX 功能。它等同于下面的 -o dax=always 行为。

在 xfs 和 ext4 上启用 DAX

摘要

  1. 存在一个内核文件访问模式标志 S_DAX,对应于 statx 标志 STATX_ATTR_DAX。有关此访问模式的详细信息,请参阅 statx(2) 的手册页。

  2. 存在一个持久的标志 FS_XFLAG_DAX,它可以应用于常规文件和目录。这个建议标志可以在任何时候设置或清除,但这样做不会立即影响 S_DAX 状态。

  3. 如果在目录上设置了持久的 FS_XFLAG_DAX 标志,那么随后在此目录中创建的所有常规文件和子目录都将继承此标志。在父目录上设置或清除此标志时已存在的文件和子目录不会受到此父目录修改的影响。

  4. 存在一些 dax 挂载选项可以覆盖 FS_XFLAG_DAX 在 S_DAX 标志设置中的值。假设底层存储支持 DAX,则以下情况成立:

    • -o dax=inode 表示"遵循 FS_XFLAG_DAX",这是默认设置。
    • -o dax=never 表示"永不设置 S_DAX,忽略 FS_XFLAG_DAX。"
    • -o dax=always 表示"始终设置 S_DAX 忽略 FS_XFLAG_DAX。"
    • -o dax 是一个传统选项,它是 dax=always 的别名。

    警告: 选项 -o dax 可能会在未来被移除,因此 -o dax=always 是指定此行为的推荐方法。

    注意: 即使文件系统以 dax 选项挂载,FS_XFLAG_DAX 的修改和继承行为也保持不变。但是,直到文件系统以 dax=inode 重新挂载并且 inode 从内核内存中驱逐之前,内存中的 inode 状态(S_DAX)将被覆盖。

  5. 可以通过以下方式更改 S_DAX 策略:

    • 在创建文件前根据需要设置父目录的 FS_XFLAG_DAX 标志

    • 设置适当的 dax=“foo” 挂载选项

    • 更改现有常规文件和目录上的 FS_XFLAG_DAX 标志。这具有下文 6) 中描述的运行时限制和约束。

  6. 通过切换持久化的 FS_XFLAG_DAX 标志来更改 S_DAX 策略时,对现有常规文件的更改只有在所有进程关闭这些文件后才会生效。

详情

每个文件有 2 个 dax 标志。一个是持久的 inode 设置(FS_XFLAG_DAX),另一个是表示该功能活动状态的易失性标志(S_DAX)。

FS_XFLAG_DAX 在文件系统中被保留。这个持久性配置设置可以使用 FS_IOC_FS[GS]`ETXATTR ioctl(参见 ioctl_xfs_fsgetxattr(2))或诸如 ‘xfs_io’ 之类的工具进行设置、清除和/或查询。

新文件和目录在创建时会自动从其父目录继承 FS_XFLAG_DAX。因此,在创建目录时设置 FS_XFLAG_DAX 可以为整个子树设置默认行为。

为阐明继承关系,以下是 3 个示例:

Example A: 示例 A:

mkdir -p a/b/c
xfs_io -c 'chattr +x' a
mkdir a/b/c/d
mkdir a/e

------[outcome]------

dax: a,e
no dax: b,c,d

Example B: 示例 B:

mkdir a
xfs_io -c 'chattr +x' a
mkdir -p a/b/c/d

------[outcome]------

dax: a,b,c,d
no dax:

Example C: 示例 C:

mkdir -p a/b/c
xfs_io -c 'chattr +x' c
mkdir a/b/c/d

------[outcome]------

dax: c,d
no dax: a,b

当前启用状态(S_DAX)是在内核将文件 inode 实例化到内存时设置的。该状态基于底层媒体支持、FS_XFLAG_DAX 的值以及文件系统的 dax 挂载选项进行设置。

statx 可用于查询 S_DAX。

即使底层介质不支持 dax 和/或文件系统被挂载选项覆盖,设置 FS_XFLAG_DAX 标志(无论是直接设置还是通过继承)仍然会发生。

在 virtiofs 上启用 DAX

virtiofs 上 DAX 的语义基本上与 ext4 和 xfs 上的语义相同,只是当指定了’-o dax=inode’时,virtiofs 客户端通过 FUSE 协议从 virtiofs 服务器获取是否启用 DAX 的提示,而不是持久的 FS_XFLAG_DAX 标志。也就是说,是否启用 DAX 完全由 virtiofs 服务器决定,而 virtiofs 服务器本身可能部署各种算法来做出此决定,例如取决于主机上的持久 FS_XFLAG_DAX 标志。

在客户机内部设置或清除持久性 FS_XFLAG_DAX 标志仍然受支持,但不能保证相应文件会启用或禁用 DAX。客户机内的用户仍需调用 statx(2) 并检查 statx 标志 STATX_ATTR_DAX,以查看是否为此文件启用了 DAX。

块驱动程序编写者的实现技巧

要在块驱动程序中支持 DAX,请实现’direct_access’块设备操作。该操作用于将扇区号(以 512 字节扇区为单位)转换为标识内存物理页面的页框号(pfn)。它还会返回一个可用于访问内存的内核虚拟地址。

direct_access 方法接受一个 ‘size’ 参数,表示请求的字节数。该函数应返回在该偏移量处可以连续访问的字节数。如果发生错误,它也可以返回一个负的 errno。

为了支持此方法,存储必须始终能够被 CPU 字节访问。如果您的设备使用分页技术通过较小的窗口暴露大量内存,那么您就无法实现 direct_access。同样,如果您的设备偶尔会使 CPU 长时间停滞,您也不应尝试实现 direct_access。

这些块设备可作为参考: - pmem:NVDIMM 持久内存驱动

文件系统编写者的实现技巧

文件系统支持包括:

  • 通过在 i_flags 中设置 S_DAX 标志来添加将 inode 标记为 DAX 的支持
  • 实现 ->read_iter 和 ->write_iter 操作,当 inode 设置了 S_DAX 标志时使用 dax_iomap_rw()
  • 为 DAX 文件实现一个 mmap 文件操作,该操作在 VMA 上设置 VM_MIXEDMAP 和 VM_HUGEPAGE 标志,并将 vm_ops 设置为包含 fault、pmd_fault、page_mkwrite 和 pfn_mkwrite 的处理程序。这些处理程序可能应该调用 dax_iomap_fault() ,并传递适当的故障大小和 iomap 操作。
  • 调用 iomap_zero_range() 为 DAX 文件传递适当的 iomap 操作,而不是 block_truncate_page()
  • 确保在读取、写入、截断和缺页之间有足够的锁机制

用于分配块的 iomap 处理程序必须确保分配的块在返回前已清零并转换为已写入范围,以避免通过 mmap 暴露未初始化的数据。

处理介质错误

libnvdimm 子系统为每个 pmem 块设备(在 gendisk->badblocks 中)存储已知介质错误位置的记录。如果我们在此类位置或存在尚未发现的潜在错误的位置发生故障,应用程序可以预期会收到 SIGBUS。libnvdimm 还允许通过简单写入受影响的扇区来清除这些错误(通过 pmem 驱动,并且如果底层 NVDIMM 支持 ACPI 定义的 clear_poison DSM)。

由于 DAX IO 通常不会经过 driver/bio 路径,应用程序或系统管理员可以通过以下方式从先前的 backup/inbuilt 冗余中恢复丢失的数据:

  1. 删除受影响的文件,并从备份中恢复(系统管理员方式):这将释放文件占用的文件系统块,下次分配这些块时,它们将被先清零,这个过程通过驱动程序完成,可以清除坏扇区。
  2. 截断或打孔处理文件中含有坏块的部分(至少需要打孔处理整个对齐的扇区,但不一定是整个文件系统块)。

这是两条基本路径,它们允许 DAX 文件系统在媒体错误存在的情况下继续运行。未来可以在此基础上构建更强大的错误恢复机制,例如,通过 DM 在块层提供的冗余/镜像,或者额外地在文件系统级别提供。这些机制必须依赖于上述两个基本原则:错误清除可以通过驱动程序发送 IO 来完成,或者通过清零(同样通过驱动程序)来完成。

不足之处

即使内核或其模块存储在支持 DAX 的文件系统上,该文件系统位于支持 DAX 的块设备上,它们仍然会被复制到 RAM 中。

DAX 代码在具有虚拟映射缓存的架构(如 ARM、MIPS 和 SPARC)上无法正常工作。

对从 DAX 文件 mmapped 的用户内存范围调用 get_user_pages() ,当没有 struct page 来描述这些页面时会失败。这个问题已经在一些设备驱动程序中得到解决,通过添加可选的 struct page 支持来支持驱动程序控制的页面(参见 drivers/nvdimm 中的 CONFIG_NVDIMM_PFN 示例,了解如何实现这一点)。在非 struct page 情况下,从非 DAX 文件对这些内存范围进行 O_DIRECT 读取/写入将会失败

6.2.1.2 - aistudio.google

咨询 aistudio.google 关于Derect Access的知识

https://aistudio.google.com/prompts/1xw2bDNLkdRaZq5u8OqOhAuIZm5Nwox1j

输入:

我在设计 bms 机器上启动 cloud hypervisor 作为 microvm 运行 ai agent 的方案。现在我想实现高密部署,其中有一个优化策略是降低每个 microvm 实例的内存使用。我想深入了解一下 Virtio-fs + DAX (Direct Access) 在这方面的作用,另外详细介绍一下这个技术的原理。


这是一个非常专业且切中要害的问题。在基于 BMS(裸金属服务器)+ Cloud Hypervisor + MicroVM 的架构下,Virtio-fs + DAX 是实现高密度部署(High Density)、尤其是 AI Agent 这种依赖大文件(模型、Python 库)场景的核心优化技术。

简单来说,它的核心价值在于消除了 Host 和 Guest 之间的“双重缓存”(Double Caching),实现了内存的真正共享。

以下是关于 Virtio-fs + DAX 的深入原理解析及其在 AI Agent 场景下的作用。


1. 核心痛点:传统的“双重缓存”问题

在没有 DAX 的传统虚拟化文件系统(如 virtio-9p 或未开启 DAX 的 virtio-fs)中,当 MicroVM 需要读取一个文件(例如 500MB 的 PyTorch 库或 2GB 的 LLM 模型)时,数据流向如下:

  1. Host Page Cache: 宿主机内核从磁盘读取文件,缓存在宿主机的 Page Cache 中。
  2. Copy Operation: 数据通过 virtio 通道从宿主机内存拷贝到 Guest 的内存中。
  3. Guest Page Cache: Guest 内核将这份数据再次缓存在自己的 Page Cache 中。

后果:

  • 内存浪费: 同一份数据在物理内存中存在了两份(一份在 Host,一份在 Guest)。
  • 无法复用: 如果你启动了 100 个 MicroVM,每个 VM 都要加载 PyTorch,那么内存中就会有 100 + 1 份 PyTorch 的拷贝。这是高密部署的噩梦。

2. Virtio-fs + DAX 的解决方案:共享 Page Cache

DAX (Direct Access) 技术允许 Guest VM 的文件系统绕过自己的 Page Cache,直接映射宿主机的文件缓存。

原理机制(Technical Deep Dive)

Virtio-fs + DAX 的工作流程利用了 PCI BAR (Base Address Register) 和 内存映射(mmap)技术。

  1. PCI BAR 窗口(DAX Window):

    • Cloud Hypervisor 在启动时,会为 Virtio-fs 设备分配一块巨大的内存区域(例如 4GB 或更大),这块区域在 Guest 看来是 PCI 设备的一块内存空间(PCI BAR)。
    • 在 Host 侧,这块区域实际上并没有立即分配物理 RAM,而是作为一个虚拟地址窗口。
  2. 文件映射(The Mapping):

    • 当 Guest 应用(如 AI Agent 的 Python 进程)发起 read 请求读取 pytorch.so 时。
    • Guest 内核检测到该文件系统支持 DAX,它不会发起传统的 I/O 请求将数据复制进来。
    • 相反,Guest 内核发送一个 FUSE 请求给 Host 端的 virtiofsd(后端守护进程),请求建立映射(FUSE_SETUPMAPPING)。
  3. 建立页表关联(The Magic):

    • Host 端的 virtiofsd 收到请求后,会把宿主机上该文件对应的 Page Cache(Host 物理内存)mmap 到 Cloud Hypervisor 的 DAX Window 中。
    • KVM 更新 EPT(Extended Page Tables,扩展页表)。
    • 结果: Guest 的物理地址空间(GPA)直接指向了 Host 的物理地址空间(HPA)中存放文件内容的页面。
  4. 直接访问:

    • Guest 应用程序读取内存时,实际上是直接读取了 Host 的 Page Cache。

3. 图解对比

传统模式 (Without DAX):

Host Disk  ->  Host RAM (Page Cache)  ->  (Copy)  ->  Guest RAM (Page Cache)  ->  Guest App

100 个 VM = 100 份 Guest Cache + 1 份 Host Cache

Virtio-fs + DAX 模式:

Host Disk  ->  Host RAM (Page Cache)  <-- (Map/Ptr) -- Guest App (直接读取)

100 个 VM = 0 份 Guest Cache + 1 份 Host Cache


4. 在 AI Agent 高密部署中的具体作用

对于 AI Agent 场景,Virtio-fs + DAX 的收益是巨大的,主要体现在以下三个方面:

A. 共享 Python 环境与依赖库 (Huge Win)

AI Agent 通常运行在 Python 环境中,依赖 torch, numpy, tensorflow 等巨型库。

  • 场景: torch 库解压后可能占用 500MB~1GB 空间。
  • 无 DAX: 启动 50 个 Agent,仅加载 torch 就需要消耗 50GB 内存。
  • 有 DAX: Host 加载一次 torch 占用 1GB 内存。50 个 Agent 全部通过 DAX 映射读取这同一份物理内存。内存开销接近于 0 的增长。

B. 共享 AI 模型权重 (Massive Win)

如果你的 Agent 是同质化的(例如都调用同一个本地 Embedding 模型或小型 LLM)。

  • 场景: 模型文件大小 2GB。
  • 有 DAX: 所有 Agent 共享这 2GB 的物理内存。你可以轻松在 64GB 内存的服务器上跑几十个加载了 2GB 模型的 MicroVM,只要 CPU 撑得住。

C. 极速启动 (Fast Boot)

  • 由于不需要将数据从 Host 拷贝到 Guest 内存,也不需要 Guest 内核进行磁盘解压或加载操作,应用启动速度(Cold Start)会显著加快。MicroVM 启动时,并没有真正加载文件,只是建立了内存映射指针,通过“缺页异常”(Page Fault)按需加载,速度极快。

5. Cloud Hypervisor 配置指南

在 Cloud Hypervisor 中启用 Virtio-fs DAX 需要配合 virtiofsd 使用。

1. 启动 virtiofsd (Host 端): 你需要先运行 virtio-fs 的后端守护进程。

# --cache=auto 或 always 配合 DAX 效果最好
# --socket-path 指定通信 socket
# --shared-dir 指定宿主机共享目录(存放 rootfs, models, python envs)
./virtiofsd --socket-path=/tmp/vhost-user.sock --shared-dir=/path/to/shared/agents --cache=auto

2. 配置 Cloud Hypervisor (JSON 或 CLI): 在启动 Cloud Hypervisor 时,需要指定 DAX 缓存窗口的大小。这个大小限制了 Guest 可以同时映射多少文件内容,建议设置得大一些(覆盖你的模型+库的大小)。

{
  "fs": [
    {
      "tag": "myfs",
      "socket": "/tmp/vhost-user.sock",
      "num_request_queues": 1,
      "queue_size": 1024,
      "cache_size": "4G"  <-- 这里开启 DAX,设置 DAX Window 大小为 4GB
    }
  ],
  "kernel": "...",
  "cpus": "...",
  "memory": "..."
}

或者 CLI 参数:

./cloud-hypervisor \
    ... \
    --fs tag=myfs,socket=/tmp/vhost-user.sock,num_request_queues=1,queue_size=1024,cache_size=4G

3. Guest 内部挂载: Guest 启动后,挂载文件系统需要指定 dax 选项:

mount -t virtiofs -o dax myfs /mnt/shared

6. 总结与建议

Virtio-fs + DAX 是 AI Agent MicroVM 高密部署的基石。

  • 原理: 利用 Host Page Cache 作为唯一的真实数据源,Guest 通过 PCI BAR 直接映射访问,消除数据拷贝。
  • 收益: 将 O(N) 的内存复杂度降低为 O(1)(针对只读数据)。
  • 策略建议:
    1. 构建统一的 Base Image/Rootfs: 将 Python 环境、CUDA 库、常用模型文件放在 Host 的共享目录中。
    2. 只读挂载: 尽量以只读方式挂载代码和模型目录,这样能最大化利用共享页缓存。
    3. 计算 DAX Window: cache_size 不要太小,至少要大于“常用热点文件总和”(如 OS 基础库 + Python 解释器 + PyTorch + 模型权重)。

这一方案能让你在有限的物理内存上,将 MicroVM 的密度提高 3-10 倍(取决于 AI 负载中静态资源的占比)。

6.2.1.3 - arm64 支持

咨询 aistudio.google 关于 arm64 的平台支持

https://aistudio.google.com/prompts/1xw2bDNLkdRaZq5u8OqOhAuIZm5Nwox1j

输入:

有一个关键问题,Virtio-fs + DAX 在 arm64 平台上可以使用吗?


这是一个非常关键且实际的问题。因为 ARM64(AArch64)架构在云原生和 AI 推理场景(如 AWS Graviton, Huawei Kunpeng, Ampere Altra)中越来越普及,但很多虚拟化特性最初都是围绕 x86 开发的。

结论是:可以使用。Virtio-fs + DAX 目前已经支持 ARM64 平台,但有严格的版本要求和配置限制(尤其是页大小对齐)。

以下是针对 ARM64 平台上使用 Virtio-fs + DAX 的详细现状、限制和注意事项:


1. 支持现状与版本要求

Virtio-fs 的 DAX 支持引入 ARM64 架构比 x86 晚一些,主要涉及到 ARM 架构下的内存管理(MMU)、缓存一致性(Cache Coherency)和 PCI BAR 映射机制的差异。

要使其正常工作,你需要满足以下软件栈要求:

  • Host Kernel(宿主机内核): 建议 Linux 5.13+,最好是 5.15 LTS 或更高。
    • 早期的内核在 ARM64 上处理 virtio-fs 的 DAX 映射时存在 bug,或者不支持通过 PCI BAR 进行大内存窗口映射。
  • Guest Kernel(MicroVM 内核): 同样建议 Linux 5.13+。
    • Guest 内核必须编译开启 CONFIG_DAX, CONFIG_FS_DAX, CONFIG_VIRTIO_FS。
  • Cloud Hypervisor: 建议使用 v26.0 或更高版本(越新越好)。
    • Cloud Hypervisor 社区对 ARM64 的支持非常积极,它是目前在 ARM64 上运行 MicroVM 的首选 VMM 之一。

2. 核心大坑:页大小(Page Size)对齐问题

这是在 ARM64 上使用 DAX 最容易踩的坑,也是与 x86 最大的不同点。

  • x86: 几乎所有通用 Linux 发行版默认 Page Size 都是 4KB。Host 和 Guest 天然一致。
  • ARM64: Linux 内核支持 4KB 和 64KB 页大小。
    • 很多 ARM 服务器发行版(如某些版本的 CentOS/RHEL for ARM)默认可能开启 64KB 页以提升性能。
    • Android 或某些嵌入式环境通常是 4KB。

DAX 的硬性限制:Host 和 Guest 的 Page Size 必须一致。

因为 DAX 机制是通过 mmap 将 Host 的物理页直接映射到 Guest 的物理地址空间。如果页大小不一致(例如 Host 4KB,Guest 64KB),Guest 内核在建立页表映射时会因为对齐(Alignment)问题而失败,或者回退到无 DAX 模式。

避坑指南:

  1. 检查 Host: 执行 getconf PAGESIZE,确认是否为 4096 (4KB)。
  2. 检查 Guest Kernel Config: 确保 MicroVM 的内核编译选项 CONFIG_ARM64_4K_PAGES=y。
  3. 强制一致: 强烈建议 Host 和 Guest 都统一使用 4KB 页大小。虽然 64KB/64KB 理论上可行,但在虚拟化大页分配和内存碎片上可能会遇到边缘 Case。

3. PCI BAR 窗口大小限制

在 ARM64 架构上,PCI 设备(这里指 Virtio-fs 设备)映射的 MMIO 空间(DAX Window)大小可能会受到物理地址布局的限制。

  • x86: 我们可以比较随意地给 cache_size 设置 8G、16G。
  • ARM64: 取决于 Cloud Hypervisor 定义的 Guest 物理内存布局(Memory Layout)。在 ARM64 虚拟化中,用于 PCI MMIO 的地址空间是有限的(通常在 highmem 区域)。
    • 如果你设置了非常大的 DAX Window(例如 32GB),可能会导致 Cloud Hypervisor 启动失败,提示无法分配足够的 PCI 资源或地址空间冲突。
    • 建议: 开始时不要设得过大,例如先从 2GB 或 4GB 开始测试,够用即可。

4. 缓存一致性 (Cache Coherency)

ARM64 是弱内存序(Weakly Ordered)架构,且不同 SoC 对 DMA 一致性的处理不同。

  • 在 Virtio-fs DAX 模式下,CPU 直接访问 Host 内存。
  • Cloud Hypervisor 和 KVM 在 ARM64 上已经处理了大部分一致性问题(通过将映射区域标记为合适的内存属性)。
  • 注意: 确保你的 ARM 服务器(BMS)是标准的服务器级芯片(如 Kunpeng 920, Yitian 710, Graviton),它们通常有硬件支持的 IO Coherency。如果在一些嵌入式 ARM 板卡(如 Jetson, Raspberry Pi)上尝试,可能会遇到数据损坏问题,但在 BMS 场景下通常不需要担心。

5. 验证命令

在 ARM64 机器上部署后,务必在 Guest 内部验证 DAX 是否真正生效:

  1. 启动 Guest 后检查挂载信息:

    # 在 Guest 内部
    mount | grep virtiofs
    # 输出应该包含 "dax" 关键字
    # myfs on /mnt type virtiofs (rw,relatime,dax)
    
  2. 查看内核日志(dmesg): 如果 DAX 失败,内核日志通常会非常诚实地报错:

    dmesg | grep -i "virtio-fs"
    # 如果看到类似 "DAX unsupported" 或 "alignment error" 的警告,说明降级到了传统模式。
    

总结方案

在 ARM64 BMS 上设计高密 AI Agent:

  1. 可行性: 完全可行,且效果显著。
  2. Host OS: 使用 Ubuntu 22.04 LTS (Kernel 5.15) 或更高,确认为 4KB Page Size。
  3. Guest Kernel: 编译专门的 MicroVM Kernel(推荐 5.15 或 6.1),务必开启 4KB Page Size。
  4. 配置: Cloud Hypervisor 启动参数开启 cache_size,初始设置为 4GB。
  5. 收益: 与 x86 一样,可以实现 Python 库和 AI 模型内存的零拷贝共享。

6.2.1.4 - DAX机制的功能和实现

DAX机制的功能和实现介绍

https://www.cnblogs.com/dream397/p/13900936.html

https://blog.csdn.net/maokelong95/article/details/107195192

DAX 简述

直接访问(Direct Access,DAX) 机制是一种支持用户态软件直接访问存储于持久内存(Persistent Memory,PM) 的文件的机制,用户态软件无需先将文件数据拷贝到页高速缓存(Page Cache)。

上述描述对应到下面这张图(Typical NVDIMM Software Architecture)中,就是说(File)和(Memory)这两条 IO 路径都能绕过页高速缓存。

  • 其中 File 路径(下称普通文件路径)表示,用户态软件通过标准文件接口(Standard File API)访问持久内存文件系统。
  • 其中 Memory 路径(下称映射文件路径)表示,用户态软件通过映射文件(Memory-mapped File)直接访问 PM。

DAX 的原理

以下将结合 Linux v5.8-rc1 中的 XFS 为例进行介绍。

普通文件路径如何旁路页缓存

以文件写路径为例,其由 xfs_file_write_iter 提供,该函数部分代码如下所示:

STATIC ssize_t
xfs_file_write_iter(
	struct kiocb		*iocb,
	struct iov_iter		*from)
{
	struct file		*file = iocb->ki_filp;
	struct address_space	*mapping = file->f_mapping;
	struct inode		*inode = mapping->host;
	ssize_t			ret;

	if (IS_DAX(inode))
		return xfs_file_dax_write(iocb, from);

	if (iocb->ki_flags & IOCB_DIRECT) {
		ret = xfs_file_dio_aio_write(iocb, from);
		if (ret != -EREMCHG)
			return ret;
	}

	return xfs_file_buffered_aio_write(iocb, from);
}

这段代码在执行写操作的时候,将分别处理三种情况:

  • DAX
    • 在将文件系统挂载到 PM 设备时,若设置 DAX 标识(mount -o dax),则持久内存文件系统将为所有写操作采用该路径;
    • 该路径主要调用 dax_iomap_rw。该函数在通过 dax_direct_access 获取目标物理内存的地址后,通过 dax_copy_from_iter 调用 NVDIMM 驱动直接把数据拷贝到目标物理内存中,并冲洗(Flush)相应缓存行(Cache Line)。
  • DIO(Direct IO):
    • 在打开文件时,若设置直接 IO 标识(O_DIRECT) ,则文件操作将采用该路径;
    • 该路径主要调用 iomap_dio_rw。该函数仍通过传统存储栈(Storage Stack)访问设备,即通过构造 bio,将请求传递到块设备层(Block Device Layer),再由块设备层调用驱动从而访问设备。
  • 正常 IO:

映射文件路径如何旁路页缓存

调用 mmap 时,文件系统仅仅在进程的 mm_struct 中注册了一段使用虚拟内存区域(Virtual Memory Area,VMA)描述的虚拟地址。后续当用户态软件首次访问映射文件时,内存管理单元(Memory Managment Unit)发现页表项(Page Table Entry,PTE)为空,于是触发 14 号故障,即页故障(Page Fault),使得操作系统开始执行请求调页(Demand Paging)。此时,由虚拟内存管理器(Virtual Memory Manager)和文件系统共同管理页表,以建立虚拟内存到物理内存之间的映射关系。注意,以上为同步过程,而非异步过程,因为页故障是一个异常(Exception)而非软/硬件中断(Software/Hardware Interrupt)。

调用 mmap 时发生了什么

在执行 mmap 系统调用时,主要执行 do_mmap 中的 mmap_region,其根据用户态软件的请求,返回一个用于描述一段可用进程虚拟地址空间的 VMA,接着通过 call_mmap 执行文件系统注册的 mmap 实现,最后将该段 VMA 之添加在进程的 mm_struct 中。

XFS 中 mmap 由 xfs_file_mmap 实现,其中主要语句就一个:vma->vm_ops = &xfs_file_vm_ops;。它告诉异常处理例程(Exception Handler)应该调用 xfs_file_vm_ops 中相应的函数处理页错误。

请求调页时发生了什么

请求调页主要执行 __xfs_filemap_fault,其代码如下所示:

static vm_fault_t
__xfs_filemap_fault(
	struct vm_fault		*vmf,
	enum page_entry_size	pe_size,
	bool			write_fault)
{
	struct inode		*inode = file_inode(vmf->vma->vm_file);
	struct xfs_inode	*ip = XFS_I(inode);
	vm_fault_t		ret;

	trace_xfs_filemap_fault(ip, pe_size, write_fault);

	if (write_fault) {
		sb_start_pagefault(inode->i_sb);
		file_update_time(vmf->vma->vm_file);
	}

	xfs_ilock(XFS_I(inode), XFS_MMAPLOCK_SHARED);
	if (IS_DAX(inode)) {
		pfn_t pfn;

		ret = dax_iomap_fault(vmf, pe_size, &pfn, NULL,
				(write_fault && !vmf->cow_page) ?
				 &xfs_direct_write_iomap_ops :
				 &xfs_read_iomap_ops);
		if (ret & VM_FAULT_NEEDDSYNC)
			ret = dax_finish_sync_fault(vmf, pe_size, pfn);
	} else {
		if (write_fault)
			ret = iomap_page_mkwrite(vmf,
					&xfs_buffered_write_iomap_ops);
		else
			ret = filemap_fault(vmf);
	}
	xfs_iunlock(XFS_I(inode), XFS_MMAPLOCK_SHARED);

	if (write_fault)
		sb_end_pagefault(inode->i_sb);
	return ret;
}

显然,这段代码有两条分支:

  • DAX: 该路径主要调用 dax_iomap_fault。该函数首先通过 grab_mapping_entry 获取页缓存中的 DAX Exception Entry(详见3),接着通过 xfs_bmbt_to_iomap 准备一个名为 struct iomap 的数据结构。

    struct iomap {
    	u64			addr; /* disk offset of mapping, bytes */
    	loff_t			offset;	/* file offset of mapping, bytes */
    	u64			length;	/* length of mapping, bytes */
    	u16			type;	/* type of mapping */
    	u16			flags;	/* flags for mapping */
    	struct block_device	*bdev;	/* block device for I/O */
    	struct dax_device	*dax_dev; /* dax_dev for dax operations */
    	void			*inline_data;
    	void			*private; /* filesystem private */
    	const struct iomap_page_ops *page_ops;
    };
    

    准备好 struct iomap 之后,通过 dax_iomap_pfn ,结合 struct iomap 所提供的信息,获取目标 PM 页的物理页号(Physical Page Number,pfn)。之后由 dax_insert_entry 将与该页相关联的 DAX Exception Entry 添加到用于维护页缓存的数据结构 XArray 中。最后调用 vmf_insert_mixed_mkwrite 将从 DAX Exception Entry 中缓存的 pfn 填写到对应虚拟页的 PTE 中。

  • 正常请求调页

    该路径主要调用 filemap_fault。该函数首先通过 do_sync_mmap_readahead 试图同步地预读文件数据(预读行为可受 madvise 系统调用的影响,因此也可能完全不读取),接着通过 pagecache_get_page 分配页缓存,再通过 xfs_vm_readpage 将读取块设备数据的请求发送到块设备层,从而将文件数据读取到页缓存中。在将文件数据拷贝到页缓存之后,取决于映射文件的类型(MAP_SHARED、MAP_PRIVATE)执行不同的分支。最后返回的页保存在 vmf->page 中。

    • 当是 MAP_SHARED,主要调用 do_shared_fault 函数,该函数:

      1. 调用 xfs_file_vm_ops 中注册的 xfs_filemap_page_mkwrite,从而调用 iomap_page_create 在 vmf->page 对应的 struct page 的 private 字段中塞进去一个 struct iomap_page。

        /*
        * Structure allocated for each page when block size < PAGE_SIZE to track
        * sub-page uptodate status and I/O completions.
        */
        struct iomap_page {
        	atomic_t		read_count;
        	atomic_t		write_count;
        	spinlock_t		uptodate_lock;
        	DECLARE_BITMAP(uptodate, PAGE_SIZE / 512);
        };
        

      finish_fault

      alloc_set_pte

      信息以支持空闲页回收。

    • 当是 MAP_PRIVATE,调用 do_cow_fault。该函数首先通过 alloc_page_vma 为 VMA 分配一个页,该页保存在 vmf->cow_page 中。接着通过 copy_user_highpage 将 vmf->page 中的数据拷贝到 vmf->cow_page 中。最后通过 finish_fault 将 vmf->cow_page 映射到虚拟页上。

附录 1:术语表

  • 持久内存(Persistent Memory,PM):指能通过访存指令(区别于系统调用)访问、可按字节寻址的(区别于块)非易失存储器(Non-volatile Memory,NVM)。其中可按字节寻址(Byte-addressable)表示每个寻址单位对应一个 PM 单元(而非字或块)。

  • 持久内存系统(PM-aware File System):指支持 DAX 机制的文件系统。该词的常见表述也包括「Persistent Memory File System」、「DAX Enabled File System」。

  • 映射文件(Memory-mapped File):是一段虚内存逐字节对应于一个文件或类文件的资源,使得应用程序处理映射部分如同访问主内存。进程地址空间由映射文件和匿名内存(Anonymous Memory)组成。

附录 2:DAX 历史沿革

  • 2015,Carsten Otte 在 Linux v2.6 中引入 XIP(Execute-in-place)机制。 XIP 原本用于嵌入式系统,它摒弃了存储栈中的通用块层及驱动层,并旁路了页高速缓存,使得进程可以直接访问只读存储器或基于 Flash 的内存。
  • 2014 年,Subramanya R Dulloor 等人在 PMFS 中基于 XIP 机制管理 PM。
  • 同年,Matthew Wilcox 改进了 XIP 并提出了名为 DAX 的子系统。 他在尝试将 XIP 集成到 Ext4 文件系统时,发现 XIP 无法很好地应对竞争条件(Race Conditions)。当多个线程需要同时访问共享资源,且结果依赖于它们执行的相对速度时,便出现了竞争条件。他所做出的最主要的变动,就是使用文件系统的 get_block 路径替代 struct address_space_operations 中的 get_xip_mem操作。

6.3 - KSM

Kernel Same-page Merging 内核页面合并

KSM (Kernel Same-page Merging):

  • 方案: 在 Host (BMS) 内核开启 KSM。扫描内存页,将内容相同的页合并为只读页。

  • 适用场景: 如果你的 MicroVM 运行着相同的应用程序或库,KSM 能进一步合并代码段和数据段内存。

注意: KSM 消耗 CPU 资源,需权衡扫描频率(sleep_millisecs)和 CPU 占用。

利用 ksm 合并系统内存页

6.4 - Hugepages

Hugepages 大页内存

Hugepages (大页内存):

方案: 在 Host 上为 Cloud Hypervisor 进程分配 2MB 或 1GB 的 Hugepages。

效果: 减少 Host CPU 的 TLB (Translation Lookaside Buffer) Miss,降低页表本身的内存占用(Page Table Overhead)。对于高密场景,海量的 4KB 页表项会占用大量内存,使用 2MB 大页是必须的。

优化 Hugepage 使用,减少 TLB miss。

6.5 - 去除固件

Direct Kernel Boot 去除固件

去除固件 (Direct Kernel Boot):

方案: Cloud Hypervisor 支持直接加载 Linux Kernel (vmlinux),跳过 BIOS/UEFI 阶段。

效果: 既加快了启动速度,又节省了用于模拟固件的内存开销。

6.6 - ballooning

ballooning 动态调整内存

通过 ballooning 动态调整内存

6.7 - virtio-mem

Kernel Same-page Merging 内核页面合并

通过 virtio-mem 动态内存热插拔

7 - 存储

cloud hypervisor 存储优化

7.1 - 概述

存储优化概述

使用 vhost-blk 或者 virtio-scsi ,避免复杂的磁盘模拟

7.2 - 共享只读文件

Shared Read-Only Rootfs

Shared Read-Only Rootfs (共享只读根文件系统):

方案: 所有 MicroVM 共享同一个基础镜像作为只读层(通过 Virtio-fs DAX 挂载)。

写操作: 对于需要写的目录(如 /var, /tmp),挂载一个极小的、基于内存的 tmpfs,或者使用 OverlayFS 挂载一个空的 Host 目录。

效果: 磁盘 I/O 几乎全部转化为内存映射操作,极大降低存储带宽压力。

7.3 - 精简 Guest Kernel

精简 Guest Kernel

精简 Guest Kernel:

方案: 定制 Linux Kernel。移除所有不需要的驱动(USB, GPU, Sound, 传统的 PCI 等)。只保留 virtio 相关的驱动(virtio-net, virtio-blk, virtio-fs, virtio-console)。

效果: 减小 Kernel 体积(建议压缩后小于 5MB),减少启动时的内存申请,加快启动速度。

7.4 - CoW

CoW写时复制

对只读镜像采用共享基础镜像 + 写时复制(CoW),减少磁盘占用。

8 - 网络

cloud hypervisor 网络优化

8.1 - 概述

网络优化概述

网络中断风暴是高密场景的隐形杀手。

Vhost-net / Vhost-user:

方案: 务必使用 vhost 后端。它将数据平面的处理下沉到 Host 内核(vhost-net),减少了用户态(Cloud Hypervisor)和内核态之间的上下文切换。

高级方案: 如果 BMS 网卡支持 SRIOV 且数量足够(通常不够高密),可以使用直通。更通用的高密方案是使用 Tap 设备 + Bridge 或者 MacVTap。

被动模式与中断合并:

在 Guest 内核中开启 NAPI,减少每个数据包的中断触发频率。

高效网络栈:

  • 使用 vhost-net / vhost-user 加速网络 I/O

  • 配合 DPDK 或者 SR-IOV 提升网络吞吐

9 - 调度

cloud hypervisor 调度优化

9.1 - 概述

调度优化概述

numa 感知: 在多 cpu NUMA 架构下,合理绑定 vCPU 与内存节点,避免跨 NUMA 访问带来的延迟。

cpu pinning: 将 vCPU 固定在物理 CPU 核心上,减少调度抖动

overcommit 超卖策略

  • 在 cpu 层面适度超卖,但需要监控负载

  • 内存层面依赖 KSM 和 ballooning,避免 OOM

和容器结合

  • 在 microVM 内运行容器,进一步提升密度